1use std::cell::Cell;
4use std::rc::Rc;
5use std::sync::Arc;
6
7use crate::draw_ctx::DrawCtx;
8use crate::event::{Event, EventResult, Key, MouseButton};
9use crate::geometry::{Rect, Size};
10use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
11use crate::text::Font;
12use crate::widget::{paint_subtree, Widget};
13use crate::widgets::label::{Label, LabelAlign};
14
15const TRACK_H: f64 = 4.0;
16const THUMB_R: f64 = 7.0;
17const WIDGET_H: f64 = 22.0;
21const VALUE_W: f64 = 44.0;
25const VALUE_GAP: f64 = 6.0;
27
28pub struct Slider {
30 bounds: Rect,
31 children: Vec<Box<dyn Widget>>, base: WidgetBase,
33 value: f64,
34 min: f64,
35 max: f64,
36 step: f64,
37 show_value: bool,
38 decimals: Option<usize>,
41 font_size: f64,
42 dragging: bool,
43 focused: bool,
44 hovered: bool,
45 on_change: Option<Box<dyn FnMut(f64)>>,
46 value_cell: Option<Rc<Cell<f64>>>,
52 value_label: Label,
59 last_value_text: String,
62}
63
64impl Slider {
65 pub fn new(value: f64, min: f64, max: f64, font: Arc<Font>) -> Self {
66 let v = value.clamp(min, max);
67 let font_size = 12.0;
68 let value_label = Label::new("", Arc::clone(&font))
69 .with_font_size(font_size)
70 .with_align(LabelAlign::Right);
71 Self {
72 bounds: Rect::default(),
73 children: Vec::new(),
74 base: WidgetBase::new(),
75 value: v,
76 min,
77 max,
78 step: (max - min) / 100.0,
79 show_value: true,
80 decimals: None,
81 font_size,
82 dragging: false,
83 focused: false,
84 hovered: false,
85 on_change: None,
86 value_cell: None,
87 value_label,
88 last_value_text: String::new(),
89 }
90 }
91
92 pub fn with_step(mut self, step: f64) -> Self {
93 self.step = step;
94 self
95 }
96
97 pub fn with_value_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
104 self.value = cell.get().clamp(self.min, self.max);
105 self.value_cell = Some(cell);
106 self
107 }
108 pub fn with_show_value(mut self, show: bool) -> Self {
109 self.show_value = show;
110 self
111 }
112
113 pub fn with_decimals(mut self, decimals: usize) -> Self {
116 self.decimals = Some(decimals);
117 self
118 }
119
120 pub fn with_margin(mut self, m: Insets) -> Self {
121 self.base.margin = m;
122 self
123 }
124 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
125 self.base.h_anchor = h;
126 self
127 }
128 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
129 self.base.v_anchor = v;
130 self
131 }
132 pub fn with_min_size(mut self, s: Size) -> Self {
133 self.base.min_size = s;
134 self
135 }
136 pub fn with_max_size(mut self, s: Size) -> Self {
137 self.base.max_size = s;
138 self
139 }
140
141 pub fn on_change(mut self, cb: impl FnMut(f64) + 'static) -> Self {
142 self.on_change = Some(Box::new(cb));
143 self
144 }
145
146 pub fn value(&self) -> f64 {
147 self.value
148 }
149
150 pub fn set_value(&mut self, v: f64) {
151 self.value = v.clamp(self.min, self.max);
152 if let Some(cell) = &self.value_cell {
153 cell.set(self.value);
154 }
155 }
156
157 fn fire(&mut self) {
158 let v = self.value;
159 if let Some(cell) = &self.value_cell {
160 cell.set(v);
161 }
162 if let Some(cb) = self.on_change.as_mut() {
163 cb(v);
164 }
165 }
166
167 fn track_right(&self) -> f64 {
171 let reserved = if self.show_value {
172 VALUE_W + VALUE_GAP
173 } else {
174 0.0
175 };
176 (self.bounds.width - reserved - THUMB_R).max(THUMB_R + 1.0)
177 }
178
179 fn thumb_x(&self) -> f64 {
181 let track_left = THUMB_R;
182 let track_right = self.track_right();
183 let t = if self.max > self.min {
184 (self.value - self.min) / (self.max - self.min)
185 } else {
186 0.0
187 };
188 track_left + t * (track_right - track_left)
189 }
190
191 fn value_from_x(&self, x: f64) -> f64 {
192 let track_left = THUMB_R;
193 let track_right = self.track_right();
194 let t = ((x - track_left) / (track_right - track_left)).clamp(0.0, 1.0);
195 let raw = self.min + t * (self.max - self.min);
196 let snapped = (raw / self.step).round() * self.step;
198 snapped.clamp(self.min, self.max)
199 }
200
201 fn format_value(&self) -> String {
204 if let Some(d) = self.decimals {
205 return format!("{:.*}", d, self.value);
206 }
207 if self.step >= 1.0 {
208 format!("{:.0}", self.value)
209 } else if self.step >= 0.1 {
210 format!("{:.1}", self.value)
211 } else if self.step >= 0.01 {
212 format!("{:.2}", self.value)
213 } else {
214 format!("{:.3}", self.value)
215 }
216 }
217}
218
219impl Widget for Slider {
220 fn type_name(&self) -> &'static str {
221 "Slider"
222 }
223 fn bounds(&self) -> Rect {
224 self.bounds
225 }
226 fn set_bounds(&mut self, b: Rect) {
227 self.bounds = b;
228 }
229 fn children(&self) -> &[Box<dyn Widget>] {
230 &self.children
231 }
232 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
233 &mut self.children
234 }
235
236 fn is_focusable(&self) -> bool {
237 true
238 }
239
240 fn margin(&self) -> Insets {
241 self.base.margin
242 }
243 fn h_anchor(&self) -> HAnchor {
244 self.base.h_anchor
245 }
246 fn v_anchor(&self) -> VAnchor {
247 self.base.v_anchor
248 }
249 fn min_size(&self) -> Size {
250 self.base.min_size
251 }
252 fn max_size(&self) -> Size {
253 self.base.max_size
254 }
255
256 fn layout(&mut self, available: Size) -> Size {
257 if !self.dragging {
262 if let Some(cell) = &self.value_cell {
263 self.value = cell.get().clamp(self.min, self.max);
264 }
265 }
266
267 if self.show_value {
272 let new_text = self.format_value();
273 if new_text != self.last_value_text {
274 self.value_label.set_text(new_text.clone());
275 self.last_value_text = new_text;
276 }
277 let lh = self.font_size * 1.5;
280 let _ = self.value_label.layout(Size::new(VALUE_W, lh));
281 self.value_label
282 .set_bounds(Rect::new(0.0, 0.0, VALUE_W, lh));
283 }
284
285 Size::new(available.width, WIDGET_H)
286 }
287
288 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
289 let v = ctx.visuals();
290 let h = self.bounds.height;
291 let cy = h * 0.5;
292
293 let track_right = self.track_right();
294 let track_w = (track_right - THUMB_R).max(0.0);
295
296 ctx.set_fill_color(v.track_bg);
298 ctx.begin_path();
299 ctx.rounded_rect(THUMB_R, cy - TRACK_H * 0.5, track_w, TRACK_H, TRACK_H * 0.5);
300 ctx.fill();
301
302 let tx = self.thumb_x();
304 if tx > THUMB_R {
305 ctx.set_fill_color(v.accent);
306 ctx.begin_path();
307 ctx.rounded_rect(
308 THUMB_R,
309 cy - TRACK_H * 0.5,
310 tx - THUMB_R,
311 TRACK_H,
312 TRACK_H * 0.5,
313 );
314 ctx.fill();
315 }
316
317 if self.focused {
319 ctx.set_stroke_color(v.accent_focus);
320 ctx.set_line_width(2.0);
321 ctx.begin_path();
322 ctx.circle(tx, cy, THUMB_R + 3.0);
323 ctx.stroke();
324 }
325
326 let thumb_color = if self.dragging || self.focused {
328 v.accent_pressed
329 } else if self.hovered {
330 v.accent_hovered
331 } else {
332 v.accent
333 };
334 ctx.set_fill_color(thumb_color);
335 ctx.begin_path();
336 ctx.circle(tx, cy, THUMB_R);
337 ctx.fill();
338
339 ctx.set_fill_color(v.widget_bg);
340 ctx.begin_path();
341 ctx.circle(tx, cy, THUMB_R - 2.5);
342 ctx.fill();
343
344 if self.show_value {
349 self.value_label.set_color(v.text_color);
350 let lb = self.value_label.bounds();
351 let strip_left = track_right + VALUE_GAP;
352 let ly = cy - lb.height * 0.5;
353 self.value_label
354 .set_bounds(Rect::new(strip_left, ly, lb.width, lb.height));
355 ctx.save();
356 ctx.translate(strip_left, ly);
357 paint_subtree(&mut self.value_label, ctx);
358 ctx.restore();
359 }
360 }
361
362 fn on_event(&mut self, event: &Event) -> EventResult {
363 match event {
364 Event::MouseMove { pos } => {
365 let was = self.hovered;
366 self.hovered = self.hit_test(*pos);
367 if self.dragging {
368 self.value = self.value_from_x(pos.x);
369 self.fire();
370 crate::animation::request_draw();
371 return EventResult::Consumed;
372 }
373 if was != self.hovered {
374 crate::animation::request_draw();
375 return EventResult::Consumed;
376 }
377 EventResult::Ignored
378 }
379 Event::MouseDown {
380 button: MouseButton::Left,
381 pos,
382 ..
383 } => {
384 self.dragging = true;
385 self.value = self.value_from_x(pos.x);
386 self.fire();
387 crate::animation::request_draw();
388 EventResult::Consumed
389 }
390 Event::MouseUp {
391 button: MouseButton::Left,
392 ..
393 } => {
394 let was = self.dragging;
395 self.dragging = false;
396 if was {
397 crate::animation::request_draw();
398 }
399 EventResult::Consumed
400 }
401 Event::KeyDown { key, .. } => {
402 let changed = match key {
403 Key::ArrowLeft => {
404 self.value = (self.value - self.step).clamp(self.min, self.max);
405 true
406 }
407 Key::ArrowRight => {
408 self.value = (self.value + self.step).clamp(self.min, self.max);
409 true
410 }
411 Key::ArrowDown => {
412 self.value = (self.value - self.step * 10.0).clamp(self.min, self.max);
413 true
414 }
415 Key::ArrowUp => {
416 self.value = (self.value + self.step * 10.0).clamp(self.min, self.max);
417 true
418 }
419 _ => false,
420 };
421 if changed {
422 self.fire();
423 crate::animation::request_draw();
424 EventResult::Consumed
425 } else {
426 EventResult::Ignored
427 }
428 }
429 Event::FocusGained => {
430 let was = self.focused;
431 self.focused = true;
432 if !was {
433 crate::animation::request_draw();
434 EventResult::Consumed
435 } else {
436 EventResult::Ignored
437 }
438 }
439 Event::FocusLost => {
440 let was_focused = self.focused;
441 let was_dragging = self.dragging;
442 self.focused = false;
443 self.dragging = false;
444 if was_focused || was_dragging {
445 crate::animation::request_draw();
446 EventResult::Consumed
447 } else {
448 EventResult::Ignored
449 }
450 }
451 _ => EventResult::Ignored,
452 }
453 }
454}