cadmus_core/view/settings_editor/
bottom_bar.rs1use crate::color::WHITE;
2use crate::font::Fonts;
3use crate::framebuffer::Framebuffer;
4use crate::geom::{CycleDir, Rectangle};
5use crate::view::filler::Filler;
6use crate::view::icon::Icon;
7use crate::view::{Event, Id, View, ID_FEEDER};
8
9#[derive(Debug, Clone)]
11pub enum BottomBarVariant {
12 SingleButton {
14 event: Event,
16 icon: &'static str,
18 },
19 TwoButtons {
21 left_event: Event,
23 left_icon: &'static str,
25 right_event: Event,
27 right_icon: &'static str,
29 },
30 PaginationWithButton {
33 prev_enabled: bool,
34 next_enabled: bool,
35 center_event: Event,
36 center_icon: &'static str,
37 },
38 Pagination {
41 prev_enabled: bool,
42 next_enabled: bool,
43 },
44}
45
46pub struct SettingsEditorBottomBar {
51 id: Id,
52 rect: Rectangle,
53 children: Vec<Box<dyn View>>,
54}
55
56impl SettingsEditorBottomBar {
57 #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
85 pub fn new(rect: Rectangle, variant: BottomBarVariant) -> Self {
86 let id = ID_FEEDER.next();
87 let mut children = Vec::new();
88
89 let background = Filler::new(rect, WHITE);
90 children.push(Box::new(background) as Box<dyn View>);
91
92 match variant {
93 BottomBarVariant::SingleButton {
94 event,
95 icon: icon_name,
96 } => {
97 let icon = Icon::new(icon_name, rect, event);
98 children.push(Box::new(icon) as Box<dyn View>);
99 }
100 BottomBarVariant::TwoButtons {
101 left_event,
102 left_icon,
103 right_event,
104 right_icon,
105 } => {
106 let button_width = rect.width() as i32 / 2;
107
108 let left_rect = rect![
109 rect.min.x,
110 rect.min.y,
111 rect.min.x + button_width,
112 rect.max.y
113 ];
114 let left_button = Icon::new(left_icon, left_rect, left_event);
115 children.push(Box::new(left_button) as Box<dyn View>);
116
117 let right_rect = rect![
118 rect.min.x + button_width,
119 rect.min.y,
120 rect.max.x,
121 rect.max.y
122 ];
123 let right_button = Icon::new(right_icon, right_rect, right_event);
124 children.push(Box::new(right_button) as Box<dyn View>);
125 }
126 BottomBarVariant::PaginationWithButton {
127 prev_enabled,
128 next_enabled,
129 center_event,
130 center_icon,
131 } => {
132 let (left_rect, center_rect, right_rect) = Self::pagination_rects(rect);
133 Self::push_prev_arrow(&mut children, left_rect, prev_enabled);
134 children
135 .push(Box::new(Icon::new(center_icon, center_rect, center_event))
136 as Box<dyn View>);
137 Self::push_next_arrow(&mut children, right_rect, next_enabled);
138 }
139 BottomBarVariant::Pagination {
140 prev_enabled,
141 next_enabled,
142 } => {
143 let (left_rect, center_rect, right_rect) = Self::pagination_rects(rect);
144 Self::push_prev_arrow(&mut children, left_rect, prev_enabled);
145 children.push(Box::new(Filler::new(center_rect, WHITE)) as Box<dyn View>);
146 Self::push_next_arrow(&mut children, right_rect, next_enabled);
147 }
148 }
149
150 SettingsEditorBottomBar { id, rect, children }
151 }
152
153 fn pagination_rects(rect: Rectangle) -> (Rectangle, Rectangle, Rectangle) {
155 let third_width = rect.width() as i32 / 3;
156 let left_rect = rect![rect.min.x, rect.min.y, rect.min.x + third_width, rect.max.y];
157 let center_rect = rect![
158 rect.min.x + third_width,
159 rect.min.y,
160 rect.max.x - third_width,
161 rect.max.y
162 ];
163 let right_rect = rect![rect.max.x - third_width, rect.min.y, rect.max.x, rect.max.y];
164 (left_rect, center_rect, right_rect)
165 }
166
167 fn push_prev_arrow(children: &mut Vec<Box<dyn View>>, rect: Rectangle, enabled: bool) {
168 if enabled {
169 children.push(Box::new(Icon::new(
170 "arrow-left",
171 rect,
172 Event::Page(CycleDir::Previous),
173 )) as Box<dyn View>);
174 } else {
175 children.push(Box::new(Filler::new(rect, WHITE)) as Box<dyn View>);
176 }
177 }
178
179 fn push_next_arrow(children: &mut Vec<Box<dyn View>>, rect: Rectangle, enabled: bool) {
180 if enabled {
181 children.push(
182 Box::new(Icon::new("arrow-right", rect, Event::Page(CycleDir::Next)))
183 as Box<dyn View>,
184 );
185 } else {
186 children.push(Box::new(Filler::new(rect, WHITE)) as Box<dyn View>);
187 }
188 }
189}
190
191impl View for SettingsEditorBottomBar {
192 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _hub, _bus, _rq, _context), fields(event = ?_evt), ret(level=tracing::Level::TRACE)))]
193 fn handle_event(
194 &mut self,
195 _evt: &Event,
196 _hub: &crate::view::Hub,
197 _bus: &mut crate::view::Bus,
198 _rq: &mut crate::view::RenderQueue,
199 _context: &mut crate::context::Context,
200 ) -> bool {
201 false
202 }
203
204 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _fb, _fonts), fields(rect = ?_rect)))]
205 fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
206
207 fn rect(&self) -> &Rectangle {
208 &self.rect
209 }
210
211 fn rect_mut(&mut self) -> &mut Rectangle {
212 &mut self.rect
213 }
214
215 fn children(&self) -> &Vec<Box<dyn View>> {
216 &self.children
217 }
218
219 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
220 &mut self.children
221 }
222
223 fn id(&self) -> Id {
224 self.id
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::geom::Point;
232
233 #[test]
234 fn test_single_button_creates_two_children() {
235 let rect = Rectangle::new(Point { x: 0, y: 0 }, Point { x: 100, y: 50 });
236
237 let bottom_bar = SettingsEditorBottomBar::new(
238 rect,
239 BottomBarVariant::SingleButton {
240 event: Event::Back,
241 icon: "back",
242 },
243 );
244
245 assert_eq!(
246 bottom_bar.children().len(),
247 2,
248 "SingleButton variant should have 2 children: background filler and icon"
249 );
250 }
251
252 #[test]
253 fn test_two_buttons_creates_three_children() {
254 let rect = Rectangle::new(Point { x: 0, y: 0 }, Point { x: 100, y: 50 });
255
256 let bottom_bar = SettingsEditorBottomBar::new(
257 rect,
258 BottomBarVariant::TwoButtons {
259 left_event: Event::Back,
260 left_icon: "back",
261 right_event: Event::Validate,
262 right_icon: "check_mark",
263 },
264 );
265
266 assert_eq!(
267 bottom_bar.children().len(),
268 3,
269 "TwoButtons variant should have 3 children: background filler, left icon, and right icon"
270 );
271 }
272
273 #[test]
274 fn test_two_buttons_split_width_evenly() {
275 let rect = Rectangle::new(Point { x: 0, y: 0 }, Point { x: 200, y: 50 });
276
277 let bottom_bar = SettingsEditorBottomBar::new(
278 rect,
279 BottomBarVariant::TwoButtons {
280 left_event: Event::Back,
281 left_icon: "back",
282 right_event: Event::Validate,
283 right_icon: "check_mark",
284 },
285 );
286
287 let children = bottom_bar.children();
288 let left_button_rect = children[1].rect();
289 let right_button_rect = children[2].rect();
290
291 assert_eq!(
292 left_button_rect.width(),
293 100,
294 "Left button should be 100 units wide"
295 );
296 assert_eq!(
297 right_button_rect.width(),
298 100,
299 "Right button should be 100 units wide"
300 );
301 assert_eq!(left_button_rect.min.x, 0);
302 assert_eq!(left_button_rect.max.x, 100);
303 assert_eq!(right_button_rect.min.x, 100);
304 assert_eq!(right_button_rect.max.x, 200);
305 }
306}