1use super::{
2 Bus, Event, Hub, Id, RenderData, RenderQueue, SliderId, View, ID_FEEDER, THICKNESS_SMALL,
3};
4use crate::color::{BLACK, PROGRESS_EMPTY, PROGRESS_FULL, PROGRESS_VALUE, WHITE};
5use crate::context::Context;
6use crate::device::CURRENT_DEVICE;
7use crate::font::{font_from_style, Fonts, SLIDER_VALUE};
8use crate::framebuffer::{Framebuffer, UpdateMode};
9use crate::geom::{halves, BorderSpec, CornerSpec, Rectangle};
10use crate::input::{DeviceEvent, FingerStatus};
11use crate::unit::scale_by_dpi;
12
13const PROGRESS_HEIGHT: f32 = 7.0;
14const BUTTON_DIAMETER: f32 = 46.0;
15
16pub struct Slider {
17 id: Id,
18 rect: Rectangle,
19 children: Vec<Box<dyn View>>,
20 slider_id: SliderId,
21 value: f32,
22 min_value: f32,
23 max_value: f32,
24 active: bool,
25 last_x: i32,
26}
27
28impl Slider {
29 pub fn new(
30 rect: Rectangle,
31 slider_id: SliderId,
32 value: f32,
33 min_value: f32,
34 max_value: f32,
35 ) -> Slider {
36 Slider {
37 id: ID_FEEDER.next(),
38 rect,
39 children: Vec::new(),
40 slider_id,
41 value,
42 min_value,
43 max_value,
44 active: false,
45 last_x: -1,
46 }
47 }
48
49 pub fn update_value(&mut self, x_hit: i32) {
50 let dpi = CURRENT_DEVICE.dpi;
51 let button_diameter = scale_by_dpi(BUTTON_DIAMETER, dpi) as i32;
52 let (small_radius, big_radius) = halves(button_diameter);
53 let x_offset = x_hit
54 .max(self.rect.min.x + small_radius)
55 .min(self.rect.max.x - big_radius);
56 let progress = ((x_offset - self.rect.min.x - small_radius) as f32
57 / (self.rect.width() as i32 - button_diameter) as f32)
58 .clamp(0.0, 1.0);
59 self.value = self.min_value + progress * (self.max_value - self.min_value);
60 }
61
62 pub fn update(&mut self, value: f32, rq: &mut RenderQueue) {
63 if (self.value - value).abs() >= f32::EPSILON {
64 self.value = value;
65 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
66 }
67 }
68}
69
70impl View for Slider {
71 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, bus, rq, _context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
72 fn handle_event(
73 &mut self,
74 evt: &Event,
75 _hub: &Hub,
76 bus: &mut Bus,
77 rq: &mut RenderQueue,
78 _context: &mut Context,
79 ) -> bool {
80 match *evt {
81 Event::Device(DeviceEvent::Finger {
82 status, position, ..
83 }) => match status {
84 FingerStatus::Down if self.rect.includes(position) => {
85 self.active = true;
86 self.update_value(position.x);
87 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
88 bus.push_back(Event::Slider(self.slider_id, self.value, status));
89 self.last_x = position.x;
90 true
91 }
92 FingerStatus::Motion if self.active && position.x != self.last_x => {
93 self.update_value(position.x);
94 rq.add(RenderData::no_wait(
95 self.id,
96 self.rect,
97 UpdateMode::FastMono,
98 ));
99 bus.push_back(Event::Slider(self.slider_id, self.value, status));
100 self.last_x = position.x;
101 true
102 }
103 FingerStatus::Up if self.active => {
104 self.active = false;
105 if position.x != self.last_x {
106 self.update_value(position.x);
107 self.last_x = position.x;
108 }
109 rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui));
110 bus.push_back(Event::Slider(self.slider_id, self.value, status));
111 true
112 }
113 _ => self.active,
114 },
115 _ => false,
116 }
117 }
118
119 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, fonts, _rect), fields(rect = ?_rect)))]
120 fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, fonts: &mut Fonts) {
121 let dpi = CURRENT_DEVICE.dpi;
122 let progress_height = scale_by_dpi(PROGRESS_HEIGHT, dpi) as i32;
123 let button_diameter = scale_by_dpi(BUTTON_DIAMETER, dpi) as i32;
124 let border_thickness = scale_by_dpi(THICKNESS_SMALL, dpi) as u16;
125
126 let progress = (self.value - self.min_value) / (self.max_value - self.min_value);
127 let (small_radius, big_radius) = halves(button_diameter);
128 let x_offset = self.rect.min.x
129 + small_radius
130 + ((self.rect.width() as f32 - button_diameter as f32) * progress) as i32;
131
132 fb.draw_rectangle(&self.rect, WHITE);
133
134 let (small_mini_radius, big_mini_radius) = halves(progress_height);
135 let (small_padding, big_padding) = halves(self.rect.height() as i32 - progress_height);
136 let rect = rect![
137 self.rect.min.x + small_radius - big_mini_radius,
138 self.rect.min.y + small_padding,
139 self.rect.max.x - big_radius + small_mini_radius,
140 self.rect.max.y - big_padding
141 ];
142
143 fb.draw_rounded_rectangle_with_border(
144 &rect,
145 &CornerSpec::Uniform(small_mini_radius),
146 &BorderSpec {
147 thickness: border_thickness,
148 color: BLACK,
149 },
150 &|x, _| {
151 if x < x_offset {
152 PROGRESS_FULL
153 } else {
154 PROGRESS_EMPTY
155 }
156 },
157 );
158
159 let (small_padding, big_padding) = halves(self.rect.height() as i32 - button_diameter);
160 let rect = rect![
161 x_offset - small_radius,
162 self.rect.min.y + small_padding,
163 x_offset + big_radius,
164 self.rect.max.y - big_padding
165 ];
166 let fill_color = if self.active { BLACK } else { WHITE };
167
168 fb.draw_rounded_rectangle_with_border(
169 &rect,
170 &CornerSpec::Uniform(small_radius),
171 &BorderSpec {
172 thickness: 2 * border_thickness,
173 color: BLACK,
174 },
175 &fill_color,
176 );
177
178 let font = font_from_style(fonts, &SLIDER_VALUE, dpi);
179 let plan = font.plan(&format!("{:.1}", self.value), None, None);
180 let x_height = font.x_heights.1 as i32;
181
182 let x_drift = if self.value > (self.min_value + self.max_value) / 2.0 {
183 -(small_radius + plan.width)
184 } else {
185 small_radius
186 };
187
188 let pt = pt!(
189 x_offset + x_drift,
190 self.rect.min.y + x_height.max(small_padding)
191 );
192 font.render(fb, PROGRESS_VALUE, &plan, pt);
193 }
194
195 fn rect(&self) -> &Rectangle {
196 &self.rect
197 }
198
199 fn rect_mut(&mut self) -> &mut Rectangle {
200 &mut self.rect
201 }
202
203 fn children(&self) -> &Vec<Box<dyn View>> {
204 &self.children
205 }
206
207 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
208 &mut self.children
209 }
210
211 fn id(&self) -> Id {
212 self.id
213 }
214}