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 set_hold_event(&mut self, event: Option<Event>) {
91 self.hold_event = event;
92 }
93
94 pub fn text(&self) -> &str {
96 &self.text
97 }
98
99 #[cfg(test)]
101 pub fn get_scheme(&self) -> [Color; 3] {
102 self.scheme
103 }
104}
105
106impl View for Label {
107 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, _hub, bus, _rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
136 fn handle_event(
137 &mut self,
138 evt: &Event,
139 _hub: &Hub,
140 bus: &mut Bus,
141 _rq: &mut RenderQueue,
142 _context: &mut Context,
143 ) -> bool {
144 match *evt {
145 Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
146 if let Some(event) = self.event.clone() {
147 bus.push_back(event);
148 }
149
150 true
151 }
152 Event::Gesture(GestureEvent::HoldFingerShort(center, _))
153 if self.rect.includes(center) =>
154 {
155 if let Some(event) = self.hold_event.clone() {
156 bus.push_back(event);
157 }
158
159 true
160 }
161 _ => false,
162 }
163 }
164
165 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, fb, fonts, _rect), fields(rect = ?_rect)))]
177 fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, fonts: &mut Fonts) {
178 let dpi = CURRENT_DEVICE.dpi;
179
180 fb.draw_rectangle(&self.rect, self.scheme[0]);
181
182 let font = font_from_style(fonts, &NORMAL_STYLE, dpi);
183 let x_height = font.x_heights.0 as i32;
184 let padding = font.em() as i32;
185 let max_width = self.rect.width() as i32 - padding;
186
187 let plan = font.plan(&self.text, Some(max_width), None);
188
189 let dx = self.align.offset(plan.width, self.rect.width() as i32);
190 let dy = (self.rect.height() as i32 - x_height) / 2;
191 let pt = pt!(self.rect.min.x + dx, self.rect.max.y - dy);
192
193 font.render(fb, self.scheme[1], &plan, pt);
194 }
195
196 fn resize(
197 &mut self,
198 rect: Rectangle,
199 _hub: &Hub,
200 _rq: &mut RenderQueue,
201 _context: &mut Context,
202 ) {
203 if let Some(Event::ToggleNear(_, ref mut event_rect)) = self.event.as_mut() {
204 *event_rect = rect;
205 }
206 self.rect = rect;
207 }
208
209 fn rect(&self) -> &Rectangle {
210 &self.rect
211 }
212
213 fn rect_mut(&mut self) -> &mut Rectangle {
214 &mut self.rect
215 }
216
217 fn children(&self) -> &Vec<Box<dyn View>> {
218 &self.children
219 }
220
221 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
222 &mut self.children
223 }
224
225 fn id(&self) -> Id {
226 self.id
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::context::test_helpers::create_test_context;
234 use crate::geom::Point;
235 use crate::gesture::GestureEvent;
236 use std::collections::VecDeque;
237 use std::sync::mpsc::channel;
238
239 #[test]
240 fn test_tap_with_event_emits_and_consumes() {
241 let rect = rect![0, 0, 200, 50];
242 let mut label =
243 Label::new(rect, "Test".to_string(), Align::Center).event(Some(Event::Back));
244
245 let (hub, _receiver) = channel();
246 let mut bus = VecDeque::new();
247 let mut rq = RenderQueue::new();
248 let mut context = create_test_context();
249
250 let point = Point::new(100, 25);
251 let event = Event::Gesture(GestureEvent::Tap(point));
252 let handled = label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
253
254 assert!(handled);
255 assert_eq!(bus.len(), 1);
256 assert!(matches!(bus.pop_front(), Some(Event::Back)));
257 }
258
259 #[test]
260 fn test_tap_without_event_does_consume() {
261 let rect = rect![0, 0, 200, 50];
262 let mut label = Label::new(rect, "Test".to_string(), Align::Center);
263
264 let (hub, _receiver) = channel();
265 let mut bus = VecDeque::new();
266 let mut rq = RenderQueue::new();
267 let mut context = create_test_context();
268
269 let point = Point::new(100, 25);
270 let event = Event::Gesture(GestureEvent::Tap(point));
271 let handled = label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
272
273 assert!(handled);
274 assert_eq!(bus.len(), 0);
275 }
276
277 #[test]
278 fn test_tap_outside_rect_ignored() {
279 let rect = rect![0, 0, 200, 50];
280 let mut label =
281 Label::new(rect, "Test".to_string(), Align::Center).event(Some(Event::Back));
282
283 let (hub, _receiver) = channel();
284 let mut bus = VecDeque::new();
285 let mut rq = RenderQueue::new();
286 let mut context = create_test_context();
287
288 let point = Point::new(300, 100);
289 let event = Event::Gesture(GestureEvent::Tap(point));
290 let handled = label.handle_event(&event, &hub, &mut bus, &mut rq, &mut context);
291
292 assert!(!handled);
293 assert_eq!(bus.len(), 0);
294 }
295}