cadmus_core/view/
button.rs1use super::{Bus, Event, Hub, Id, RenderData, RenderQueue, View, ID_FEEDER};
2use super::{BORDER_RADIUS_LARGE, THICKNESS_MEDIUM};
3use crate::color::{TEXT_INVERTED_HARD, TEXT_NORMAL};
4use crate::context::Context;
5use crate::device::CURRENT_DEVICE;
6use crate::font::{font_from_style, Fonts, NORMAL_STYLE};
7use crate::framebuffer::{Framebuffer, UpdateMode};
8use crate::geom::{BorderSpec, CornerSpec, Rectangle};
9use crate::gesture::GestureEvent;
10use crate::input::{DeviceEvent, FingerStatus};
11use crate::unit::scale_by_dpi;
12
13pub struct Button {
14 id: Id,
15 rect: Rectangle,
16 children: Vec<Box<dyn View>>,
17 event: Event,
18 text: String,
19 active: bool,
20 pub disabled: bool,
21}
22
23impl Button {
24 pub fn new(rect: Rectangle, event: Event, text: String) -> Button {
25 Button {
26 id: ID_FEEDER.next(),
27 rect,
28 children: Vec::new(),
29 event,
30 text,
31 active: false,
32 disabled: false,
33 }
34 }
35
36 pub fn disabled(mut self, value: bool) -> Button {
37 self.disabled = value;
38 self
39 }
40}
41
42impl View for Button {
43 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
44 fn handle_event(
45 &mut self,
46 evt: &Event,
47 _hub: &Hub,
48 bus: &mut Bus,
49 rq: &mut RenderQueue,
50 _context: &mut Context,
51 ) -> bool {
52 match *evt {
53 Event::Device(DeviceEvent::Finger {
54 status, position, ..
55 }) if !self.disabled => match status {
56 FingerStatus::Down if self.rect.includes(position) => {
57 self.active = true;
58 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Fast));
59 true
60 }
61 FingerStatus::Up if self.active => {
62 self.active = false;
63 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
64 true
65 }
66 _ => false,
67 },
68 Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => {
69 if !self.disabled {
70 bus.push_back(self.event.clone());
71 }
72 true
73 }
74 _ => false,
75 }
76 }
77
78 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, fonts, _rect), fields(rect = ?_rect)))]
79 fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, fonts: &mut Fonts) {
80 let dpi = CURRENT_DEVICE.dpi;
81
82 let scheme = if self.active {
83 TEXT_INVERTED_HARD
84 } else {
85 TEXT_NORMAL
86 };
87 let foreground = if self.disabled { scheme[2] } else { scheme[1] };
88
89 let border_radius = scale_by_dpi(BORDER_RADIUS_LARGE, dpi) as i32;
90 let border_thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as u16;
91
92 fb.draw_rounded_rectangle_with_border(
93 &self.rect,
94 &CornerSpec::Uniform(border_radius),
95 &BorderSpec {
96 thickness: border_thickness,
97 color: foreground,
98 },
99 &scheme[0],
100 );
101
102 let font = font_from_style(fonts, &NORMAL_STYLE, dpi);
103 let x_height = font.x_heights.0 as i32;
104 let padding = font.em() as i32;
105 let max_width = self.rect.width() as i32 - padding;
106
107 let plan = font.plan(&self.text, Some(max_width), None);
108
109 let dx = (self.rect.width() as i32 - plan.width) / 2;
110 let dy = (self.rect.height() as i32 - x_height) / 2;
111 let pt = pt!(self.rect.min.x + dx, self.rect.max.y - dy);
112
113 font.render(fb, foreground, &plan, pt);
114 }
115
116 fn rect(&self) -> &Rectangle {
117 &self.rect
118 }
119
120 fn rect_mut(&mut self) -> &mut Rectangle {
121 &mut self.rect
122 }
123
124 fn children(&self) -> &Vec<Box<dyn View>> {
125 &self.children
126 }
127
128 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
129 &mut self.children
130 }
131
132 fn id(&self) -> Id {
133 self.id
134 }
135}