Skip to main content

repose_material/material3/
slider.rs

1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use repose_core::*;
7use repose_ui::Box;
8
9use super::*;
10
11/// Configuration for [`Slider`] and [`RangeSlider`].
12#[derive(Clone)]
13pub struct SliderConfig {
14    // Debug impl is manual because on_value_change_finished contains a closure
15    pub modifier: Modifier,
16    /// When false, renders disabled colors and does not respond to input.
17    pub enabled: bool,
18    pub active_track_color: Color,
19    pub inactive_track_color: Color,
20    pub thumb_color: Color,
21    pub active_tick_color: Color,
22    pub inactive_tick_color: Color,
23    pub disabled_thumb_color: Color,
24    pub disabled_active_track_color: Color,
25    pub disabled_inactive_track_color: Color,
26    pub disabled_active_tick_color: Color,
27    pub disabled_inactive_tick_color: Color,
28    pub state_colors: StateColors,
29    pub on_value_change_finished: Option<Rc<dyn Fn()>>,
30    pub interaction_source: Option<MutableInteractionSource>,
31}
32
33impl std::fmt::Debug for SliderConfig {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.debug_struct("SliderConfig")
36            .field("modifier", &self.modifier)
37            .field("enabled", &self.enabled)
38            .field("active_track_color", &self.active_track_color)
39            .field("inactive_track_color", &self.inactive_track_color)
40            .field("thumb_color", &self.thumb_color)
41            .field("active_tick_color", &self.active_tick_color)
42            .field("inactive_tick_color", &self.inactive_tick_color)
43            .field("disabled_thumb_color", &self.disabled_thumb_color)
44            .field(
45                "disabled_active_track_color",
46                &self.disabled_active_track_color,
47            )
48            .field(
49                "disabled_inactive_track_color",
50                &self.disabled_inactive_track_color,
51            )
52            .field(
53                "disabled_active_tick_color",
54                &self.disabled_active_tick_color,
55            )
56            .field(
57                "disabled_inactive_tick_color",
58                &self.disabled_inactive_tick_color,
59            )
60            .field("state_colors", &self.state_colors)
61            .field(
62                "on_value_change_finished",
63                &self.on_value_change_finished.as_ref().map(|_| ".."),
64            )
65            .field(
66                "interaction_source",
67                &self.interaction_source.as_ref().map(|_| ".."),
68            )
69            .finish()
70    }
71}
72
73impl Default for SliderConfig {
74    fn default() -> Self {
75        Self {
76            modifier: Modifier::new(),
77            enabled: true,
78            active_track_color: SliderDefaults::active_track_color(),
79            inactive_track_color: SliderDefaults::inactive_track_color(),
80            thumb_color: SliderDefaults::thumb_color(),
81            active_tick_color: SliderDefaults::active_tick_color(),
82            inactive_tick_color: SliderDefaults::inactive_tick_color(),
83            disabled_thumb_color: SliderDefaults::disabled_thumb_color(),
84            disabled_active_track_color: SliderDefaults::disabled_active_track_color(),
85            disabled_inactive_track_color: SliderDefaults::disabled_inactive_track_color(),
86            disabled_active_tick_color: SliderDefaults::disabled_active_tick_color(),
87            disabled_inactive_tick_color: SliderDefaults::disabled_inactive_tick_color(),
88            state_colors: SliderDefaults::state_colors_default(),
89            on_value_change_finished: None,
90            interaction_source: None,
91        }
92    }
93}
94
95static SLIDER_COUNTER: AtomicU64 = AtomicU64::new(0);
96
97fn snap_step(v: f32, min: f32, max: f32, step: Option<f32>) -> f32 {
98    let v = v.clamp(min, max);
99    if let Some(s) = step.filter(|s| *s > 0.0) {
100        let t = ((v - min) / s).round();
101        (min + t * s).clamp(min, max)
102    } else {
103        v
104    }
105}
106
107fn value_from_x(x: f32, rect: Rect, min: f32, max: f32, step: Option<f32>) -> f32 {
108    let w = rect.w.max(1.0);
109    let t = ((x - rect.x) / w).clamp(0.0, 1.0);
110    let v = min + t * (max - min);
111    snap_step(v, min, max, step)
112}
113
114pub fn Slider(
115    value: f32,
116    range: (f32, f32),
117    step: Option<f32>,
118    on_change: impl Fn(f32) + 'static,
119    config: SliderConfig,
120) -> View {
121    assert!(range.0 <= range.1, "Slider range start must be <= end");
122    if let Some(s) = step {
123        assert!(s > 0.0, "Slider step must be positive");
124    }
125    let id = *remember(|| SLIDER_COUNTER.fetch_add(1, Ordering::Relaxed));
126    let track_rect = remember_state_with_key(format!("ms_rect_{}", id), Rect::default);
127    let drag_active = remember_mutable_with_key(format!("ms_da_{}", id), || false);
128
129    let track_rect_p = track_rect.clone();
130    let drag_active_p = drag_active.clone();
131
132    let min = range.0;
133    let max = range.1;
134    let oc = Rc::new(on_change);
135    let range_size = (max - min).max(1e-6);
136    let t = ((value - min) / range_size).clamp(0.0, 1.0);
137
138    let is_enabled = config.enabled;
139
140    let act_trk = if !is_enabled {
141        config.disabled_active_track_color
142    } else {
143        config.active_track_color
144    };
145    let inact_trk = if !is_enabled {
146        config.disabled_inactive_track_color
147    } else {
148        config.inactive_track_color
149    };
150    let act_tick = if !is_enabled {
151        config.disabled_active_tick_color
152    } else {
153        config.active_tick_color
154    };
155    let inact_tick = if !is_enabled {
156        config.disabled_inactive_tick_color
157    } else {
158        config.inactive_tick_color
159    };
160    let thumb_col = if !is_enabled {
161        config.disabled_thumb_color
162    } else {
163        config.thumb_color
164    };
165
166    let tick_frac: Vec<f32> = if let Some(s) = step {
167        let n = ((max - min) / s.max(1e-6)).round() as usize;
168        (0..=n).map(|i| i as f32 / n as f32).collect()
169    } else {
170        Vec::new()
171    };
172
173    let sl_source: Rc<MutableInteractionSource> = config
174        .interaction_source
175        .clone()
176        .map(Rc::new)
177        .unwrap_or_else(|| remember(MutableInteractionSource::new));
178    let mut host = Modifier::new()
179        .min_width(200.0)
180        .height(44.0)
181        .interaction_source(&sl_source);
182    if !is_enabled {
183        host = host.enabled(false);
184    }
185    host = host.focusable(true);
186    Box(host
187        .painter(move |scene: &mut Scene, rect: Rect, alpha: f32| {
188            let mul_c = |c: Color| {
189                Color(
190                    c.0,
191                    c.1,
192                    c.2,
193                    ((c.3 as f32) * alpha).clamp(0.0, 255.0) as u8,
194                )
195            };
196            let track_h = dp_to_px(SliderDefaults::TRACK_HEIGHT);
197            let thumb_w = dp_to_px(SliderDefaults::THUMB_WIDTH);
198            let thumb_h = dp_to_px(SliderDefaults::THUMB_HEIGHT);
199            let dot_r = dp_to_px(2.0);
200            let corner = track_h * 0.5;
201            let gap = thumb_w * 0.5 + dp_to_px(ProgressIndicatorDefaults::SLIDER_THUMB_TRACK_GAP);
202            let pad = thumb_w * 0.5;
203            let track_x = rect.x + pad;
204            let track_w = (rect.w - thumb_w).max(0.0);
205            let cy = rect.y + rect.h * 0.5;
206
207            let kx = if step.is_some() && !tick_frac.is_empty() {
208                let is_first = (t - tick_frac[0]).abs() < 1e-6;
209                let is_last = (t - tick_frac[tick_frac.len() - 1]).abs() < 1e-6;
210                if is_first || is_last {
211                    track_x + t * track_w
212                } else {
213                    track_x + (track_w - track_h) * t + corner
214                }
215            } else {
216                track_x + t * track_w
217            };
218
219            *track_rect_p.borrow_mut() = Rect {
220                x: track_x,
221                y: rect.y,
222                w: track_w,
223                h: rect.h,
224            };
225
226            let inactive_x = track_x.max(kx + gap);
227            let inactive_w = (track_x + track_w - inactive_x).max(0.0);
228            if inactive_w > 0.0 {
229                scene.nodes.push(SceneNode::Rect {
230                    rect: Rect {
231                        x: inactive_x,
232                        y: cy - track_h * 0.5,
233                        w: inactive_w,
234                        h: track_h,
235                    },
236                    brush: Brush::Solid(mul_c(inact_trk)),
237                    radius: [corner; 4],
238                });
239                let sx = track_x + track_w - corner;
240                scene.nodes.push(SceneNode::Ellipse {
241                    rect: Rect {
242                        x: sx - dot_r,
243                        y: cy - dot_r,
244                        w: dot_r * 2.0,
245                        h: dot_r * 2.0,
246                    },
247                    brush: Brush::Solid(mul_c(inact_tick)),
248                });
249            }
250            let fill_w = (kx - gap - track_x).max(0.0);
251            if fill_w > 0.0 {
252                scene.nodes.push(SceneNode::Rect {
253                    rect: Rect {
254                        x: track_x,
255                        y: cy - track_h * 0.5,
256                        w: fill_w,
257                        h: track_h,
258                    },
259                    brush: Brush::Solid(mul_c(act_trk)),
260                    radius: [corner; 4],
261                });
262            }
263            let tick_start = track_x + corner;
264            let tick_end = track_x + track_w - corner;
265            for (i, &tf) in tick_frac.iter().enumerate() {
266                let tx = tick_start + tf * (tick_end - tick_start);
267                // skip ticks that fall on the stop indicator (last)
268                if i == tick_frac.len() - 1 {
269                    continue;
270                }
271                if tx >= kx - gap && tx <= kx + gap {
272                    continue;
273                }
274                let on_active = tx <= kx - gap;
275                scene.nodes.push(SceneNode::Ellipse {
276                    rect: Rect {
277                        x: tx - dot_r,
278                        y: cy - dot_r,
279                        w: dot_r * 2.0,
280                        h: dot_r * 2.0,
281                    },
282                    brush: Brush::Solid(mul_c(if on_active { act_tick } else { inact_tick })),
283                });
284            }
285            let da = *drag_active_p.get();
286            let tw = if da { thumb_w * 0.5 } else { thumb_w };
287            scene.nodes.push(SceneNode::Rect {
288                rect: Rect {
289                    x: kx - tw * 0.5,
290                    y: cy - thumb_h * 0.5,
291                    w: tw,
292                    h: thumb_h,
293                },
294                brush: Brush::Solid(mul_c(thumb_col)),
295                radius: [tw * 0.5; 4],
296            });
297        })
298        .on_pointer_down({
299            let oc = oc.clone();
300            let track_rect = track_rect.clone();
301            let drag_active = drag_active.clone();
302            let en = is_enabled;
303            move |pe: PointerEvent| {
304                if !en {
305                    return;
306                }
307                drag_active.set(true);
308                let r = *track_rect.borrow();
309                (oc)(value_from_x(pe.position_in_window().x, r, min, max, step));
310                pe.consume();
311            }
312        })
313        .on_pointer_move({
314            let oc = oc.clone();
315            let track_rect = track_rect.clone();
316            let drag_active = drag_active.clone();
317            let en = is_enabled;
318            move |pe: PointerEvent| {
319                if !en {
320                    return;
321                }
322                if !*drag_active.get() {
323                    return;
324                }
325                let r = *track_rect.borrow();
326                (oc)(value_from_x(pe.position_in_window().x, r, min, max, step));
327                pe.consume();
328            }
329        })
330        .on_pointer_up({
331            let drag_active = drag_active.clone();
332            let on_finished = config.on_value_change_finished.clone();
333            move |_pe: PointerEvent| {
334                let was = *drag_active.get();
335                drag_active.set(false);
336                if was {
337                    if let Some(ref cb) = on_finished {
338                        (cb)();
339                    }
340                }
341            }
342        })
343        .on_pointer_cancel({
344            let drag_active = drag_active.clone();
345            let on_finished = config.on_value_change_finished.clone();
346            move |_pe: PointerEvent| {
347                if *drag_active.get() {
348                    drag_active.set(false);
349                    if let Some(ref cb) = on_finished {
350                        (cb)();
351                    }
352                }
353            }
354        })
355        .on_scroll({
356            let oc = oc.clone();
357            let en = is_enabled;
358            move |d: Vec2| -> Vec2 {
359                if !en {
360                    return d;
361                }
362                let dir = if d.y < -0.5 {
363                    1
364                } else if d.y > 0.5 {
365                    -1
366                } else {
367                    0
368                };
369                if dir == 0 {
370                    return d;
371                }
372                let step_val = step.unwrap_or(1.0).max(1e-6);
373                let new_val = snap_step(value + (dir as f32) * step_val, min, max, step);
374                if (new_val - value).abs() > 1e-6 {
375                    (oc)(new_val);
376                    Vec2 { x: d.x, y: 0.0 }
377                } else {
378                    d
379                }
380            }
381        })
382        .on_key_event({
383            let oc = oc.clone();
384            let en = is_enabled;
385            move |ke: KeyEvent| -> bool {
386                if !en || ke.event_type != KeyEventType::Down {
387                    return false;
388                }
389                let step_val = step.unwrap_or(((max - min) / 100.0).max(1e-6)).max(1e-6);
390                let page = step_val * 10.0;
391                let new_val = match ke.key {
392                    Key::ArrowLeft | Key::ArrowDown => snap_step(value - step_val, min, max, step),
393                    Key::ArrowRight | Key::ArrowUp => snap_step(value + step_val, min, max, step),
394                    Key::PageDown => snap_step(value - page, min, max, step),
395                    Key::PageUp => snap_step(value + page, min, max, step),
396                    Key::Home => min,
397                    Key::End => max,
398                    _ => return false,
399                };
400                if (new_val - value).abs() > 1e-6 {
401                    (oc)(new_val);
402                }
403                true
404            }
405        })
406        .then(config.modifier))
407    .semantics(Semantics {
408        role: Role::Slider,
409        label: None,
410        focused: false,
411        enabled: is_enabled,
412        selectable_group: false,
413    })
414}
415
416pub fn RangeSlider(
417    start: f32,
418    end: f32,
419    range: (f32, f32),
420    step: Option<f32>,
421    on_change: impl Fn(f32, f32) + 'static,
422    config: SliderConfig,
423) -> View {
424    assert!(range.0 <= range.1, "Slider range start must be <= end");
425    if let Some(s) = step {
426        assert!(s > 0.0, "Slider step must be positive");
427    }
428    let id = *remember(|| SLIDER_COUNTER.fetch_add(1, Ordering::Relaxed));
429    let track_rect = remember_state_with_key(format!("mrs_rect_{}", id), Rect::default);
430    let drag_active = remember_mutable_with_key(format!("mrs_da_{}", id), || false);
431    let active_thumb = remember_mutable_with_key(format!("mrs_at_{}", id), || false);
432
433    let min = range.0;
434    let max = range.1;
435    let oc = Rc::new(on_change);
436    let range_size = (max - min).max(1e-6);
437    let t0 = ((start - min) / range_size).clamp(0.0, 1.0);
438    let t1 = ((end - min) / range_size).clamp(0.0, 1.0);
439    let is_enabled = config.enabled;
440
441    let act_trk = if !is_enabled {
442        config.disabled_active_track_color
443    } else {
444        config.active_track_color
445    };
446    let inact_trk = if !is_enabled {
447        config.disabled_inactive_track_color
448    } else {
449        config.inactive_track_color
450    };
451    let act_tick = if !is_enabled {
452        config.disabled_active_tick_color
453    } else {
454        config.active_tick_color
455    };
456    let inact_tick = if !is_enabled {
457        config.disabled_inactive_tick_color
458    } else {
459        config.inactive_tick_color
460    };
461    let thumb_col = if !is_enabled {
462        config.disabled_thumb_color
463    } else {
464        config.thumb_color
465    };
466
467    let tick_frac: Vec<f32> = if let Some(s) = step {
468        let n = ((max - min) / s.max(1e-6)).round() as usize;
469        (0..=n).map(|i| i as f32 / n as f32).collect()
470    } else {
471        Vec::new()
472    };
473
474    let track_rect_p = track_rect.clone();
475    let drag_active_p = drag_active.clone();
476    let active_thumb_p = active_thumb.clone();
477
478    let sl_source: Rc<MutableInteractionSource> = config
479        .interaction_source
480        .clone()
481        .map(Rc::new)
482        .unwrap_or_else(|| remember(MutableInteractionSource::new));
483    let mut host = Modifier::new()
484        .min_width(200.0)
485        .height(44.0)
486        .interaction_source(&sl_source);
487    if !is_enabled {
488        host = host.enabled(false);
489    }
490    host = host.focusable(true);
491    Box(host
492        .painter(move |scene: &mut Scene, rect: Rect, alpha: f32| {
493            let mul_c = |c: Color| {
494                Color(
495                    c.0,
496                    c.1,
497                    c.2,
498                    ((c.3 as f32) * alpha).clamp(0.0, 255.0) as u8,
499                )
500            };
501            let track_h = dp_to_px(SliderDefaults::TRACK_HEIGHT);
502            let thumb_w = dp_to_px(SliderDefaults::THUMB_WIDTH);
503            let thumb_h = dp_to_px(SliderDefaults::THUMB_HEIGHT);
504            let dot_r = dp_to_px(2.0);
505            let corner = track_h * 0.5;
506            let gap = thumb_w * 0.5 + dp_to_px(ProgressIndicatorDefaults::SLIDER_THUMB_TRACK_GAP);
507            let pad = thumb_w * 0.5;
508            let track_x = rect.x + pad;
509            let track_w = (rect.w - thumb_w).max(0.0);
510            let cy = rect.y + rect.h * 0.5;
511
512            let thumb_pos = |tf: f32, fracs: &[f32]| {
513                if step.is_some() && !fracs.is_empty() {
514                    let is_first = (tf - fracs[0]).abs() < 1e-6;
515                    let is_last = (tf - fracs[fracs.len() - 1]).abs() < 1e-6;
516                    if is_first || is_last {
517                        track_x + tf * track_w
518                    } else {
519                        track_x + (track_w - track_h) * tf + corner
520                    }
521                } else {
522                    track_x + tf * track_w
523                }
524            };
525            let k0 = thumb_pos(t0, &tick_frac);
526            let k1 = thumb_pos(t1, &tick_frac);
527            let active_l = k0.min(k1);
528            let active_r = k0.max(k1);
529
530            *track_rect_p.borrow_mut() = Rect {
531                x: track_x,
532                y: rect.y,
533                w: track_w,
534                h: rect.h,
535            };
536
537            let linactive_w = (active_l - gap - track_x).max(0.0);
538            if linactive_w > 0.0 {
539                scene.nodes.push(SceneNode::Rect {
540                    rect: Rect {
541                        x: track_x,
542                        y: cy - track_h * 0.5,
543                        w: linactive_w,
544                        h: track_h,
545                    },
546                    brush: Brush::Solid(mul_c(inact_trk)),
547                    radius: [corner; 4],
548                });
549                let sx0 = track_x + corner;
550                scene.nodes.push(SceneNode::Ellipse {
551                    rect: Rect {
552                        x: sx0 - dot_r,
553                        y: cy - dot_r,
554                        w: dot_r * 2.0,
555                        h: dot_r * 2.0,
556                    },
557                    brush: Brush::Solid(mul_c(inact_tick)),
558                });
559            }
560            let rinactive_x = (active_r + gap).min(track_x + track_w);
561            let rinactive_w = (track_x + track_w - rinactive_x).max(0.0);
562            if rinactive_w > 0.0 {
563                scene.nodes.push(SceneNode::Rect {
564                    rect: Rect {
565                        x: rinactive_x,
566                        y: cy - track_h * 0.5,
567                        w: rinactive_w,
568                        h: track_h,
569                    },
570                    brush: Brush::Solid(mul_c(inact_trk)),
571                    radius: [corner; 4],
572                });
573                let sx = track_x + track_w - corner;
574                scene.nodes.push(SceneNode::Ellipse {
575                    rect: Rect {
576                        x: sx - dot_r,
577                        y: cy - dot_r,
578                        w: dot_r * 2.0,
579                        h: dot_r * 2.0,
580                    },
581                    brush: Brush::Solid(mul_c(inact_tick)),
582                });
583            }
584            let active_w = (active_r - gap - (active_l + gap)).max(0.0);
585            if active_w > 0.0 {
586                scene.nodes.push(SceneNode::Rect {
587                    rect: Rect {
588                        x: active_l + gap,
589                        y: cy - track_h * 0.5,
590                        w: active_w,
591                        h: track_h,
592                    },
593                    brush: Brush::Solid(mul_c(act_trk)),
594                    radius: [corner; 4],
595                });
596            }
597            let tick_start = track_x + corner;
598            let tick_end = track_x + track_w - corner;
599            for (i, &tf) in tick_frac.iter().enumerate() {
600                let tx = tick_start + tf * (tick_end - tick_start);
601                // skip ticks that fall on the stop indicators (first and last)
602                if i == 0 || i == tick_frac.len() - 1 {
603                    continue;
604                }
605                let in_lgap = tx >= active_l - gap && tx <= active_l + gap;
606                let in_rgap = tx >= active_r - gap && tx <= active_r + gap;
607                if in_lgap || in_rgap {
608                    continue;
609                }
610                let on_active = tx >= active_l + gap && tx <= active_r - gap;
611                scene.nodes.push(SceneNode::Ellipse {
612                    rect: Rect {
613                        x: tx - dot_r,
614                        y: cy - dot_r,
615                        w: dot_r * 2.0,
616                        h: dot_r * 2.0,
617                    },
618                    brush: Brush::Solid(mul_c(if on_active { act_tick } else { inact_tick })),
619                });
620            }
621            let da = *drag_active_p.get();
622            let at = *active_thumb_p.get();
623            let thumbs = [k0, k1];
624            for (idx, &kx) in thumbs.iter().enumerate() {
625                let is_active = da && (if idx == 0 { !at } else { at });
626                let tw = if is_active { thumb_w * 0.5 } else { thumb_w };
627                scene.nodes.push(SceneNode::Rect {
628                    rect: Rect {
629                        x: kx - tw * 0.5,
630                        y: cy - thumb_h * 0.5,
631                        w: tw,
632                        h: thumb_h,
633                    },
634                    brush: Brush::Solid(mul_c(thumb_col)),
635                    radius: [tw * 0.5; 4],
636                });
637            }
638        })
639        .on_pointer_down({
640            let oc = oc.clone();
641            let track_rect = track_rect.clone();
642            let drag_active = drag_active.clone();
643            let active_thumb = active_thumb.clone();
644            let en = is_enabled;
645            move |pe: PointerEvent| {
646                if !en {
647                    return;
648                }
649                drag_active.set(true);
650                let r = *track_rect.borrow();
651                let v = value_from_x(pe.position_in_window().x, r, min, max, step);
652                let use_end = (v - end).abs() < (v - start).abs();
653                active_thumb.set(use_end);
654                let (a, b) = if use_end {
655                    (start, v.max(start))
656                } else {
657                    (v.min(end), end)
658                };
659                (oc)(a, b);
660                pe.consume();
661            }
662        })
663        .on_pointer_move({
664            let oc = oc.clone();
665            let track_rect = track_rect.clone();
666            let drag_active = drag_active.clone();
667            let active_thumb = active_thumb.clone();
668            let en = is_enabled;
669            move |pe: PointerEvent| {
670                if !en {
671                    return;
672                }
673                if !*drag_active.get() {
674                    return;
675                }
676                let r = *track_rect.borrow();
677                let v = value_from_x(pe.position_in_window().x, r, min, max, step);
678                let use_end = *active_thumb.get();
679                let (a, b) = if use_end {
680                    (start, v.max(start))
681                } else {
682                    (v.min(end), end)
683                };
684                (oc)(a, b);
685                pe.consume();
686            }
687        })
688        .on_pointer_up({
689            let drag_active = drag_active.clone();
690            let active_thumb = active_thumb.clone();
691            let on_finished = config.on_value_change_finished.clone();
692            move |_pe: PointerEvent| {
693                let was = *drag_active.get();
694                drag_active.set(false);
695                active_thumb.set(false);
696                if was {
697                    if let Some(ref cb) = on_finished {
698                        (cb)();
699                    }
700                }
701            }
702        })
703        .on_pointer_cancel({
704            let drag_active = drag_active.clone();
705            let active_thumb = active_thumb.clone();
706            let on_finished = config.on_value_change_finished.clone();
707            move |_pe: PointerEvent| {
708                if *drag_active.get() {
709                    drag_active.set(false);
710                    active_thumb.set(false);
711                    if let Some(ref cb) = on_finished {
712                        (cb)();
713                    }
714                }
715            }
716        })
717        .on_scroll({
718            let oc = oc.clone();
719            let active_thumb = active_thumb.clone();
720            let en = is_enabled;
721            move |d: Vec2| -> Vec2 {
722                if !en {
723                    return d;
724                }
725                let dir = if d.y < -0.5 {
726                    1
727                } else if d.y > 0.5 {
728                    -1
729                } else {
730                    0
731                };
732                if dir == 0 {
733                    return d;
734                }
735                let step_val = step.unwrap_or(1.0).max(1e-6);
736                let use_end = *active_thumb.get();
737                let (mut a, mut b) = (start, end);
738                if use_end {
739                    b = snap_step(end + (dir as f32) * step_val, min, max, step).max(a);
740                } else {
741                    a = snap_step(start + (dir as f32) * step_val, min, max, step).min(b);
742                }
743                if (a - start).abs() > 1e-6 || (b - end).abs() > 1e-6 {
744                    (oc)(a, b);
745                    Vec2 { x: d.x, y: 0.0 }
746                } else {
747                    d
748                }
749            }
750        })
751        .on_key_event({
752            let oc = oc.clone();
753            let active_thumb = active_thumb.clone();
754            let en = is_enabled;
755            move |ke: KeyEvent| -> bool {
756                if !en || ke.event_type != KeyEventType::Down {
757                    return false;
758                }
759                let step_val = step.unwrap_or(((max - min) / 100.0).max(1e-6)).max(1e-6);
760                let page = step_val * 10.0;
761                let use_end = *active_thumb.get();
762                let (a, b) = if use_end {
763                    let new_b = match ke.key {
764                        Key::ArrowLeft | Key::ArrowDown => {
765                            snap_step(end - step_val, min, max, step)
766                        }
767                        Key::ArrowRight | Key::ArrowUp => {
768                            snap_step(end + step_val, min, max, step)
769                        }
770                        Key::PageDown => snap_step(end - page, min, max, step),
771                        Key::PageUp => snap_step(end + page, min, max, step),
772                        Key::Home => start,
773                        Key::End => max,
774                        _ => return false,
775                    };
776                    (start, new_b.max(start))
777                } else {
778                    let new_a = match ke.key {
779                        Key::ArrowLeft | Key::ArrowDown => {
780                            snap_step(start - step_val, min, max, step)
781                        }
782                        Key::ArrowRight | Key::ArrowUp => {
783                            snap_step(start + step_val, min, max, step)
784                        }
785                        Key::PageDown => snap_step(start - page, min, max, step),
786                        Key::PageUp => snap_step(start + page, min, max, step),
787                        Key::Home => min,
788                        Key::End => end,
789                        _ => return false,
790                    };
791                    (new_a.min(end), end)
792                };
793                if (a - start).abs() > 1e-6 || (b - end).abs() > 1e-6 {
794                    (oc)(a, b);
795                }
796                true
797            }
798        })
799        .then(config.modifier))
800    .semantics(Semantics {
801        role: Role::Slider,
802        label: None,
803        focused: false,
804        enabled: is_enabled,
805        selectable_group: false,
806    })
807}