1use super::{Align, Bus, Event, Hub, Id, RenderData, RenderQueue, View, ID_FEEDER};
2use crate::color::{Color, TEXT_NORMAL};
3use crate::context::Context;
4use crate::device::CURRENT_DEVICE;
5use crate::font::{font_from_style, Fonts, NORMAL_STYLE};
6use crate::framebuffer::{Framebuffer, UpdateMode};
7use crate::geom::Rectangle;
8use crate::gesture::GestureEvent;
9
10pub struct Label {
26 id: Id,
27 rect: Rectangle,
28 children: Vec<Box<dyn View>>,
29 text: String,
30 align: Align,
31 scheme: [Color; 3],
32 event: Option<Event>,
33 hold_event: Option<Event>,
34}
35
36impl Label {
37 pub fn new(rect: Rectangle, text: String, align: Align) -> Label {
38 Label {
39 id: ID_FEEDER.next(),
40 rect,
41 children: Vec::new(),
42 text,
43 align,
44 scheme: TEXT_NORMAL,
45 event: None,
46 hold_event: None,
47 }
48 }
49
50 pub fn event(mut self, event: Option<Event>) -> Label {
52 self.event = event;
53 self
54 }
55
56 pub fn hold_event(mut self, event: Option<Event>) -> Label {
58 self.hold_event = event;
59 self
60 }
61
62 pub fn scheme(mut self, scheme: [Color; 3]) -> Label {
64 self.scheme = scheme;
65 self
66 }
67
68 pub fn update(&mut self, text: &str, rq: &mut RenderQueue) {
70 if self.text != text {
71 self.text = text.to_string();
72 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
73 }
74 }
75
76 pub fn set_scheme(&mut self, scheme: [Color; 3], rq: &mut RenderQueue) {
78 if self.scheme != scheme {
79 self.scheme = scheme;
80 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
81 }
82 }
83
84 pub fn set_event(&mut self, event: Option<Event>) {
86 self.event = event;
87 }
88
89 pub fn text(&self) -> &str {
91 &self.text
92 }
93
94 #[cfg(test)]
96 pub fn get_scheme(&self) -> [Color; 3] {
97 self.scheme
98 }
99}
100
101impl View for Label {
102 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, bus, _rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
131 fn handle_event(
132 &mut self,
133 evt: &Event,
134 _hub: &Hub,
135 bus: &mut Bus,
136 _rq: &mut RenderQueue,
137 _context: &mut Context,
138 ) -> bool {
139 match *evt {
140 Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
141 if let Some(event) = self.event.clone() {
142 bus.push_back(event);
143 }
144
145 true
146 }
147 Event::Gesture(GestureEvent::HoldFingerShort(center, _))
148 if self.rect.includes(center) =>
149 {
150 if let Some(event) = self.hold_event.clone() {
151 bus.push_back(event);
152 }
153
154 true
155 }
156 _ => false,
157 }
158 }
159
160 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, fonts, _rect), fields(rect = ?_rect)))]
172 fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, fonts: &mut Fonts) {
173 let dpi = CURRENT_DEVICE.dpi;
174
175 fb.draw_rectangle(&self.rect, self.scheme[0]);
176
177 let font = font_from_style(fonts, &NORMAL_STYLE, dpi);
178 let x_height = font.x_heights.0 as i32;
179 let padding = font.em() as i32;
180 let max_width = self.rect.width() as i32 - padding;
181
182 let plan = font.plan(&self.text, Some(max_width), None);
183
184 let dx = self.align.offset(plan.width, self.rect.width() as i32);
185 let dy = (self.rect.height() as i32 - x_height) / 2;
186 let pt = pt!(self.rect.min.x + dx, self.rect.max.y - dy);
187
188 font.render(fb, self.scheme[1], &plan, pt);
189 }
190
191 fn resize(
192 &mut self,
193 rect: Rectangle,
194 _hub: &Hub,
195 _rq: &mut RenderQueue,
196 _context: &mut Context,
197 ) {
198 if let Some(Event::ToggleNear(_, ref mut event_rect)) = self.event.as_mut() {
199 *event_rect = rect;
200 }
201 self.rect = rect;
202 }
203
204 fn rect(&self) -> &Rectangle {
205 &self.rect
206 }
207
208 fn rect_mut(&mut self) -> &mut Rectangle {
209 &mut self.rect
210 }
211
212 fn children(&self) -> &Vec<Box<dyn View>> {
213 &self.children
214 }
215
216 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
217 &mut self.children
218 }
219
220 fn id(&self) -> Id {
221 self.id
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::context::test_helpers::create_test_context;
229 use crate::geom::Point;
230 use crate::gesture::GestureEvent;
231 use std::collections::VecDeque;
232 use std::sync::mpsc::channel;
233
234 #[test]
235 fn test_tap_with_event_emits_and_consumes() {
236 let rect = rect![0, 0, 200, 50];
237 let mut label =
238 Label::new(rect, "Test".to_string(), Align::Center).event(Some(Event::Back));
239
240 let (hub, _receiver) = channel();
241 let mut bus = VecDeque::new();
242 let mut rq = RenderQueue::new();
243 let mut context = create_test_context();
244
245 let point = Point::new(100, 25);
246 let event = Event::Gesture(GestureEvent::Tap(point));
247 let handled = label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
248
249 assert!(handled);
250 assert_eq!(bus.len(), 1);
251 assert!(matches!(bus.pop_front(), Some(Event::Back)));
252 }
253
254 #[test]
255 fn test_tap_without_event_does_consume() {
256 let rect = rect![0, 0, 200, 50];
257 let mut label = Label::new(rect, "Test".to_string(), Align::Center);
258
259 let (hub, _receiver) = channel();
260 let mut bus = VecDeque::new();
261 let mut rq = RenderQueue::new();
262 let mut context = create_test_context();
263
264 let point = Point::new(100, 25);
265 let event = Event::Gesture(GestureEvent::Tap(point));
266 let handled = label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
267
268 assert!(handled);
269 assert_eq!(bus.len(), 0);
270 }
271
272 #[test]
273 fn test_tap_outside_rect_ignored() {
274 let rect = rect![0, 0, 200, 50];
275 let mut label =
276 Label::new(rect, "Test".to_string(), Align::Center).event(Some(Event::Back));
277
278 let (hub, _receiver) = channel();
279 let mut bus = VecDeque::new();
280 let mut rq = RenderQueue::new();
281 let mut context = create_test_context();
282
283 let point = Point::new(300, 100);
284 let event = Event::Gesture(GestureEvent::Tap(point));
285 let handled = label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
286
287 assert!(!handled);
288 assert_eq!(bus.len(), 0);
289 }
290}