cadmus_core/view/
top_bar.rs

1use crate::context::Context;
2use crate::font::Fonts;
3use crate::framebuffer::{Framebuffer, UpdateMode};
4use crate::geom::Rectangle;
5use crate::gesture::GestureEvent;
6use crate::input::DeviceEvent;
7use crate::view::battery::Battery;
8use crate::view::clock::Clock;
9use crate::view::icon::Icon;
10use crate::view::label::Label;
11use crate::view::{Align, Bus, Event, Hub, Id, RenderData, RenderQueue, View, ViewId, ID_FEEDER};
12
13/// Defines the behavior and appearance of the left icon in the top bar
14#[derive(Debug, Clone)]
15pub enum TopBarVariant {
16    /// Shows a back arrow icon and emits Event::Back when clicked
17    Back,
18    /// Shows a cancel/close icon and emits the specified event when clicked
19    Cancel(Event),
20    /// Shows a search icon and emits the specified event when clicked (typically Event::Toggle(ViewId::SearchBar))
21    Search(Event),
22}
23
24pub struct TopBar {
25    id: Id,
26    rect: Rectangle,
27    children: Vec<Box<dyn View>>,
28}
29
30impl TopBar {
31    pub fn new(
32        rect: Rectangle,
33        variant: TopBarVariant,
34        title: String,
35        context: &mut Context,
36    ) -> TopBar {
37        let id = ID_FEEDER.next();
38        let mut children = Vec::new();
39
40        let side = rect.height() as i32;
41        let (icon_name, root_event) = match variant {
42            TopBarVariant::Back => ("back", Event::Back),
43            TopBarVariant::Cancel(event) => ("close", event),
44            TopBarVariant::Search(event) => ("search", event),
45        };
46
47        let root_icon = Icon::new(icon_name, rect![rect.min, rect.min + side], root_event);
48        children.push(Box::new(root_icon) as Box<dyn View>);
49
50        let mut clock_rect = rect![rect.max - pt!(4 * side, side), rect.max - pt!(3 * side, 0)];
51        let clock_label = Clock::new(&mut clock_rect, context);
52        let title_rect = rect![rect.min.x + side, rect.min.y, clock_rect.min.x, rect.max.y];
53        let title_label = Label::new(title_rect, title, Align::Center)
54            .event(Some(Event::ToggleNear(ViewId::TitleMenu, title_rect)));
55        children.push(Box::new(title_label) as Box<dyn View>);
56        children.push(Box::new(clock_label) as Box<dyn View>);
57
58        let capacity = context.battery.capacity().map_or(0.0, |v| v[0]);
59        let status = context
60            .battery
61            .status()
62            .map_or(crate::battery::Status::Discharging, |v| v[0]);
63        let battery_widget = Battery::new(
64            rect![rect.max - pt!(3 * side, side), rect.max - pt!(2 * side, 0)],
65            capacity,
66            status,
67        );
68        children.push(Box::new(battery_widget) as Box<dyn View>);
69
70        let name = if context.settings.frontlight {
71            "frontlight"
72        } else {
73            "frontlight-disabled"
74        };
75        let frontlight_icon = Icon::new(
76            name,
77            rect![rect.max - pt!(2 * side, side), rect.max - pt!(side, 0)],
78            Event::Show(ViewId::Frontlight),
79        );
80        children.push(Box::new(frontlight_icon) as Box<dyn View>);
81
82        let menu_rect = rect![rect.max - side, rect.max];
83        let menu_icon = Icon::new(
84            "menu",
85            menu_rect,
86            Event::ToggleNear(ViewId::MainMenu, menu_rect),
87        );
88        children.push(Box::new(menu_icon) as Box<dyn View>);
89
90        TopBar { id, rect, children }
91    }
92
93    pub fn update_root_icon(&mut self, name: &str, rq: &mut RenderQueue) {
94        let icon = self.child_mut(0).downcast_mut::<Icon>().unwrap();
95        if icon.name != name {
96            icon.name = name.to_string();
97            rq.add(RenderData::new(icon.id(), *icon.rect(), UpdateMode::Gui));
98        }
99    }
100
101    pub fn update_title_label(&mut self, title: &str, rq: &mut RenderQueue) {
102        let title_label = self.children[1].as_mut().downcast_mut::<Label>().unwrap();
103        title_label.update(title, rq);
104    }
105
106    pub fn update_frontlight_icon(&mut self, rq: &mut RenderQueue, context: &mut Context) {
107        let name = if context.settings.frontlight {
108            "frontlight"
109        } else {
110            "frontlight-disabled"
111        };
112        let icon = self.child_mut(4).downcast_mut::<Icon>().unwrap();
113        icon.name = name.to_string();
114        rq.add(RenderData::new(icon.id(), *icon.rect(), UpdateMode::Gui));
115    }
116
117    pub fn update_clock_label(&mut self, rq: &mut RenderQueue) {
118        if let Some(clock_label) = self.children[2].downcast_mut::<Clock>() {
119            clock_label.update(rq);
120        }
121    }
122
123    pub fn update_battery_widget(&mut self, rq: &mut RenderQueue, context: &mut Context) {
124        if let Some(battery_widget) = self.children[3].downcast_mut::<Battery>() {
125            battery_widget.update(rq, context);
126        }
127    }
128
129    pub fn reseed(&mut self, rq: &mut RenderQueue, context: &mut Context) {
130        self.update_frontlight_icon(rq, context);
131        self.update_clock_label(rq);
132        self.update_battery_widget(rq, context);
133    }
134}
135
136impl View for TopBar {
137    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, _bus, _rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
138    fn handle_event(
139        &mut self,
140        evt: &Event,
141        _hub: &Hub,
142        _bus: &mut Bus,
143        _rq: &mut RenderQueue,
144        _context: &mut Context,
145    ) -> bool {
146        match *evt {
147            Event::Gesture(GestureEvent::Tap(center))
148            | Event::Gesture(GestureEvent::HoldFingerShort(center, ..))
149                if self.rect.includes(center) =>
150            {
151                true
152            }
153            Event::Gesture(GestureEvent::Swipe { start, end, .. })
154                if self.rect.includes(start) && self.rect.includes(end) =>
155            {
156                true
157            }
158            Event::Device(DeviceEvent::Finger { position, .. }) if self.rect.includes(position) => {
159                true
160            }
161            _ => false,
162        }
163    }
164
165    #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts, _rect), fields(rect = ?_rect)))]
166    fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {}
167
168    fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) {
169        let side = rect.height() as i32;
170        self.children[0].resize(rect![rect.min, rect.min + side], hub, rq, context);
171        let clock_width = self.children[2].rect().width() as i32;
172        let clock_rect = rect![
173            rect.max - pt!(3 * side + clock_width, side),
174            rect.max - pt!(3 * side, 0)
175        ];
176        self.children[1].resize(
177            rect![rect.min.x + side, rect.min.y, clock_rect.min.x, rect.max.y],
178            hub,
179            rq,
180            context,
181        );
182        self.children[2].resize(clock_rect, hub, rq, context);
183        self.children[3].resize(
184            rect![rect.max - pt!(3 * side, side), rect.max - pt!(2 * side, 0)],
185            hub,
186            rq,
187            context,
188        );
189        self.children[4].resize(
190            rect![rect.max - pt!(2 * side, side), rect.max - pt!(side, 0)],
191            hub,
192            rq,
193            context,
194        );
195        self.children[5].resize(rect![rect.max - side, rect.max], hub, rq, context);
196        self.rect = rect;
197    }
198
199    fn rect(&self) -> &Rectangle {
200        &self.rect
201    }
202
203    fn rect_mut(&mut self) -> &mut Rectangle {
204        &mut self.rect
205    }
206
207    fn children(&self) -> &Vec<Box<dyn View>> {
208        &self.children
209    }
210
211    fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
212        &mut self.children
213    }
214
215    fn id(&self) -> Id {
216        self.id
217    }
218}