1use super::label::Label;
2use super::{Align, Bus, Event, Hub, Id, RenderQueue, View, ID_FEEDER};
3use crate::color::{TEXT_INVERTED_HARD, TEXT_NORMAL};
4use crate::context::Context;
5use crate::framebuffer::Framebuffer;
6use crate::geom::Rectangle;
7use crate::input::{DeviceEvent, FingerStatus};
8
9pub struct ActionLabel {
16 id: Id,
17 rect: Rectangle,
18 children: Vec<Box<dyn View>>,
19 active: bool,
20}
21
22impl ActionLabel {
23 pub fn new(rect: Rectangle, text: String, align: Align) -> ActionLabel {
24 let label = Label::new(rect, text, align);
25
26 ActionLabel {
27 id: ID_FEEDER.next(),
28 rect,
29 children: vec![Box::new(label)],
30 active: false,
31 }
32 }
33
34 pub fn event(mut self, event: Option<Event>) -> ActionLabel {
36 if let Some(label) = self.children[0].downcast_mut::<Label>() {
37 label.set_event(event);
38 }
39 self
40 }
41
42 pub fn set_event(&mut self, event: Option<Event>) {
44 if let Some(label) = self.children[0].downcast_mut::<Label>() {
45 label.set_event(event);
46 }
47 }
48
49 pub fn update(&mut self, text: &str, rq: &mut RenderQueue) {
51 if let Some(label) = self.children[0].downcast_mut::<Label>() {
52 label.update(text, rq);
53 }
54 }
55
56 pub fn value(&self) -> String {
58 if let Some(label) = self.children[0].downcast_ref::<Label>() {
59 label.text().to_string()
60 } else {
61 String::new()
62 }
63 }
64
65 fn update_label_scheme(&mut self, rq: &mut RenderQueue) {
67 let scheme = if self.active {
68 TEXT_INVERTED_HARD
69 } else {
70 TEXT_NORMAL
71 };
72
73 if let Some(label) = self.children[0].downcast_mut::<Label>() {
74 label.set_scheme(scheme, rq);
75 }
76 }
77}
78
79impl View for ActionLabel {
80 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, _bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
93 fn handle_event(
94 &mut self,
95 evt: &Event,
96 _hub: &Hub,
97 _bus: &mut Bus,
98 rq: &mut RenderQueue,
99 _context: &mut Context,
100 ) -> bool {
101 match *evt {
102 Event::Device(DeviceEvent::Finger {
103 status, position, ..
104 }) => match status {
105 FingerStatus::Down if self.rect.includes(position) => {
106 self.active = true;
107 self.update_label_scheme(rq);
108 true
109 }
110 FingerStatus::Up if self.active => {
111 self.active = false;
112 self.update_label_scheme(rq);
113 true
114 }
115 _ => false,
116 },
117 _ => false,
118 }
119 }
120
121 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _fb, _fonts), fields(rect = ?_rect)))]
122 fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut crate::font::Fonts) {
123 }
124
125 fn rect(&self) -> &Rectangle {
126 &self.rect
127 }
128
129 fn rect_mut(&mut self) -> &mut Rectangle {
130 &mut self.rect
131 }
132
133 fn children(&self) -> &Vec<Box<dyn View>> {
134 &self.children
135 }
136
137 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
138 &mut self.children
139 }
140
141 fn id(&self) -> Id {
142 self.id
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::geom::Point;
150 use std::collections::VecDeque;
151 use std::sync::mpsc::channel;
152
153 fn create_test_context() -> Context {
154 Context::new(
155 Box::new(crate::framebuffer::Pixmap::new(600, 800, 1)),
156 None,
157 crate::library::Library::new(
158 std::path::Path::new("/tmp"),
159 crate::settings::LibraryMode::Database,
160 )
161 .unwrap(),
162 crate::settings::Settings::default(),
163 crate::font::Fonts::load_from(
164 std::path::Path::new(
165 &std::env::var("TEST_ROOT_DIR")
166 .expect("TEST_ROOT_DIR must be set for this test."),
167 )
168 .to_path_buf(),
169 )
170 .expect("Failed to load fonts"),
171 Box::new(crate::battery::FakeBattery::new()),
172 Box::new(crate::frontlight::LightLevels::default()),
173 Box::new(0u16),
174 )
175 }
176
177 #[test]
178 fn test_new_creates_with_label_child() {
179 let rect = rect![0, 0, 200, 50];
180 let action_label = ActionLabel::new(rect, "Test".to_string(), Align::Right(10));
181
182 assert_eq!(action_label.children.len(), 1);
183 assert!(!action_label.active);
184 }
185
186 #[test]
187 fn test_finger_down_activates() {
188 let rect = rect![0, 0, 200, 50];
189 let mut action_label = ActionLabel::new(rect, "Test".to_string(), Align::Right(10));
190 let (hub, _receiver) = channel();
191 let mut bus = VecDeque::new();
192 let mut rq = RenderQueue::new();
193 let mut context = create_test_context();
194
195 assert!(!action_label.active);
196
197 let point = Point::new(100, 25);
198 let event = Event::Device(DeviceEvent::Finger {
199 status: FingerStatus::Down,
200 position: point,
201 id: 0,
202 time: 0.0,
203 });
204 let handled = action_label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
205
206 assert!(handled);
207 assert!(action_label.active);
208 assert!(!rq.is_empty());
209 }
210
211 #[test]
212 fn test_finger_up_deactivates() {
213 let rect = rect![0, 0, 200, 50];
214 let mut action_label = ActionLabel::new(rect, "Test".to_string(), Align::Right(10));
215
216 let (hub, _receiver) = channel();
217 let mut bus = VecDeque::new();
218 let mut rq = RenderQueue::new();
219
220 action_label.active = true;
222 if let Some(label) = action_label.children[0].downcast_mut::<Label>() {
223 label.set_scheme(TEXT_INVERTED_HARD, &mut rq);
224 }
225 rq.clear();
226 let mut context = create_test_context();
227
228 let point = Point::new(100, 25);
229 let event = Event::Device(DeviceEvent::Finger {
230 status: FingerStatus::Up,
231 position: point,
232 id: 0,
233 time: 0.0,
234 });
235 let handled = action_label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
236
237 assert!(handled);
238 assert!(!action_label.active);
239 assert!(!rq.is_empty());
240 }
241
242 #[test]
243 fn test_finger_down_outside_rect_ignored() {
244 let rect = rect![0, 0, 200, 50];
245 let mut action_label = ActionLabel::new(rect, "Test".to_string(), Align::Right(10));
246 let (hub, _receiver) = channel();
247 let mut bus = VecDeque::new();
248 let mut rq = RenderQueue::new();
249 let mut context = create_test_context();
250
251 let point = Point::new(300, 100);
252 let event = Event::Device(DeviceEvent::Finger {
253 status: FingerStatus::Down,
254 position: point,
255 id: 0,
256 time: 0.0,
257 });
258 let handled = action_label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
259
260 assert!(!handled);
261 assert!(!action_label.active);
262 }
263
264 #[test]
265 fn test_update_changes_label_text() {
266 let rect = rect![0, 0, 200, 50];
267 let mut action_label = ActionLabel::new(rect, "Initial".to_string(), Align::Right(10));
268 let mut rq = RenderQueue::new();
269
270 action_label.update("Updated", &mut rq);
271
272 assert!(!rq.is_empty());
273 if let Some(label) = action_label.children[0].downcast_ref::<Label>() {
274 assert_eq!(label.rect(), &rect);
275 }
276 }
277
278 #[test]
279 fn test_event_is_emitted_on_tap() {
280 let rect = rect![0, 0, 200, 50];
281 let action_label =
282 ActionLabel::new(rect, "Test".to_string(), Align::Right(10)).event(Some(Event::Back));
283
284 let (hub, _receiver) = channel();
285 let mut bus = VecDeque::new();
286 let mut rq = RenderQueue::new();
287 let mut context = create_test_context();
288
289 let point = Point::new(100, 25);
290 let tap_event = Event::Gesture(crate::gesture::GestureEvent::Tap(point));
291
292 let mut boxed: Box<dyn View> = Box::new(action_label);
293 crate::view::handle_event(
294 boxed.as_mut(),
295 &tap_event,
296 &hub,
297 &mut bus,
298 &mut rq,
299 &mut context,
300 );
301
302 assert_eq!(bus.len(), 1);
303 assert!(matches!(bus.pop_front(), Some(Event::Back)));
304 }
305
306 #[test]
307 fn test_set_event_updates_label() {
308 let rect = rect![0, 0, 200, 50];
309 let mut action_label = ActionLabel::new(rect, "Test".to_string(), Align::Right(10));
310
311 action_label.set_event(Some(Event::Back));
312
313 let (hub, _receiver) = channel();
314 let mut bus = VecDeque::new();
315 let mut rq = RenderQueue::new();
316 let mut context = create_test_context();
317
318 let point = Point::new(100, 25);
319 let tap_event = Event::Gesture(crate::gesture::GestureEvent::Tap(point));
320
321 let mut boxed: Box<dyn View> = Box::new(action_label);
322 crate::view::handle_event(
323 boxed.as_mut(),
324 &tap_event,
325 &hub,
326 &mut bus,
327 &mut rq,
328 &mut context,
329 );
330
331 assert_eq!(bus.len(), 1);
332 assert!(matches!(bus.pop_front(), Some(Event::Back)));
333 }
334}