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::context::test_helpers::create_test_context;
150 use crate::geom::Point;
151 use std::collections::VecDeque;
152 use std::sync::mpsc::channel;
153
154 #[test]
155 fn test_new_creates_with_label_child() {
156 let rect = rect![0, 0, 200, 50];
157 let action_label = ActionLabel::new(rect, "Test".to_string(), Align::Right(10));
158
159 assert_eq!(action_label.children.len(), 1);
160 assert!(!action_label.active);
161 }
162
163 #[test]
164 fn test_finger_down_activates() {
165 let rect = rect![0, 0, 200, 50];
166 let mut action_label = ActionLabel::new(rect, "Test".to_string(), Align::Right(10));
167 let (hub, _receiver) = channel();
168 let mut bus = VecDeque::new();
169 let mut rq = RenderQueue::new();
170 let mut context = create_test_context();
171
172 assert!(!action_label.active);
173
174 let point = Point::new(100, 25);
175 let event = Event::Device(DeviceEvent::Finger {
176 status: FingerStatus::Down,
177 position: point,
178 id: 0,
179 time: 0.0,
180 });
181 let handled = action_label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
182
183 assert!(handled);
184 assert!(action_label.active);
185 assert!(!rq.is_empty());
186 }
187
188 #[test]
189 fn test_finger_up_deactivates() {
190 let rect = rect![0, 0, 200, 50];
191 let mut action_label = ActionLabel::new(rect, "Test".to_string(), Align::Right(10));
192
193 let (hub, _receiver) = channel();
194 let mut bus = VecDeque::new();
195 let mut rq = RenderQueue::new();
196
197 action_label.active = true;
199 if let Some(label) = action_label.children[0].downcast_mut::<Label>() {
200 label.set_scheme(TEXT_INVERTED_HARD, &mut rq);
201 }
202 rq.clear();
203 let mut context = create_test_context();
204
205 let point = Point::new(100, 25);
206 let event = Event::Device(DeviceEvent::Finger {
207 status: FingerStatus::Up,
208 position: point,
209 id: 0,
210 time: 0.0,
211 });
212 let handled = action_label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
213
214 assert!(handled);
215 assert!(!action_label.active);
216 assert!(!rq.is_empty());
217 }
218
219 #[test]
220 fn test_finger_down_outside_rect_ignored() {
221 let rect = rect![0, 0, 200, 50];
222 let mut action_label = ActionLabel::new(rect, "Test".to_string(), Align::Right(10));
223 let (hub, _receiver) = channel();
224 let mut bus = VecDeque::new();
225 let mut rq = RenderQueue::new();
226 let mut context = create_test_context();
227
228 let point = Point::new(300, 100);
229 let event = Event::Device(DeviceEvent::Finger {
230 status: FingerStatus::Down,
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 }
240
241 #[test]
242 fn test_update_changes_label_text() {
243 let rect = rect![0, 0, 200, 50];
244 let mut action_label = ActionLabel::new(rect, "Initial".to_string(), Align::Right(10));
245 let mut rq = RenderQueue::new();
246
247 action_label.update("Updated", &mut rq);
248
249 assert!(!rq.is_empty());
250 if let Some(label) = action_label.children[0].downcast_ref::<Label>() {
251 assert_eq!(label.rect(), &rect);
252 }
253 }
254
255 #[test]
256 fn test_event_is_emitted_on_tap() {
257 let rect = rect![0, 0, 200, 50];
258 let action_label =
259 ActionLabel::new(rect, "Test".to_string(), Align::Right(10)).event(Some(Event::Back));
260
261 let (hub, _receiver) = channel();
262 let mut bus = VecDeque::new();
263 let mut rq = RenderQueue::new();
264 let mut context = create_test_context();
265
266 let point = Point::new(100, 25);
267 let tap_event = Event::Gesture(crate::gesture::GestureEvent::Tap(point));
268
269 let mut boxed: Box<dyn View> = Box::new(action_label);
270 crate::view::handle_event(
271 boxed.as_mut(),
272 &tap_event,
273 &hub,
274 &mut bus,
275 &mut rq,
276 &mut context,
277 );
278
279 assert_eq!(bus.len(), 1);
280 assert!(matches!(bus.pop_front(), Some(Event::Back)));
281 }
282
283 #[test]
284 fn test_set_event_updates_label() {
285 let rect = rect![0, 0, 200, 50];
286 let mut action_label = ActionLabel::new(rect, "Test".to_string(), Align::Right(10));
287
288 action_label.set_event(Some(Event::Back));
289
290 let (hub, _receiver) = channel();
291 let mut bus = VecDeque::new();
292 let mut rq = RenderQueue::new();
293 let mut context = create_test_context();
294
295 let point = Point::new(100, 25);
296 let tap_event = Event::Gesture(crate::gesture::GestureEvent::Tap(point));
297
298 let mut boxed: Box<dyn View> = Box::new(action_label);
299 crate::view::handle_event(
300 boxed.as_mut(),
301 &tap_event,
302 &hub,
303 &mut bus,
304 &mut rq,
305 &mut context,
306 );
307
308 assert_eq!(bus.len(), 1);
309 assert!(matches!(bus.pop_front(), Some(Event::Back)));
310 }
311}