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#[derive(Clone)]
13pub struct SliderConfig {
14 pub modifier: Modifier,
16 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 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 enabled: is_enabled,
410 value: Some(format!("{value}")),
411 ..Default::default()
412 })
413}
414
415pub fn RangeSlider(
416 start: f32,
417 end: f32,
418 range: (f32, f32),
419 step: Option<f32>,
420 on_change: impl Fn(f32, f32) + 'static,
421 config: SliderConfig,
422) -> View {
423 assert!(range.0 <= range.1, "Slider range start must be <= end");
424 if let Some(s) = step {
425 assert!(s > 0.0, "Slider step must be positive");
426 }
427 let id = *remember(|| SLIDER_COUNTER.fetch_add(1, Ordering::Relaxed));
428 let track_rect = remember_state_with_key(format!("mrs_rect_{}", id), Rect::default);
429 let drag_active = remember_mutable_with_key(format!("mrs_da_{}", id), || false);
430 let active_thumb = remember_mutable_with_key(format!("mrs_at_{}", id), || false);
431
432 let min = range.0;
433 let max = range.1;
434 let oc = Rc::new(on_change);
435 let range_size = (max - min).max(1e-6);
436 let t0 = ((start - min) / range_size).clamp(0.0, 1.0);
437 let t1 = ((end - min) / range_size).clamp(0.0, 1.0);
438 let is_enabled = config.enabled;
439
440 let act_trk = if !is_enabled {
441 config.disabled_active_track_color
442 } else {
443 config.active_track_color
444 };
445 let inact_trk = if !is_enabled {
446 config.disabled_inactive_track_color
447 } else {
448 config.inactive_track_color
449 };
450 let act_tick = if !is_enabled {
451 config.disabled_active_tick_color
452 } else {
453 config.active_tick_color
454 };
455 let inact_tick = if !is_enabled {
456 config.disabled_inactive_tick_color
457 } else {
458 config.inactive_tick_color
459 };
460 let thumb_col = if !is_enabled {
461 config.disabled_thumb_color
462 } else {
463 config.thumb_color
464 };
465
466 let tick_frac: Vec<f32> = if let Some(s) = step {
467 let n = ((max - min) / s.max(1e-6)).round() as usize;
468 (0..=n).map(|i| i as f32 / n as f32).collect()
469 } else {
470 Vec::new()
471 };
472
473 let track_rect_p = track_rect.clone();
474 let drag_active_p = drag_active.clone();
475 let active_thumb_p = active_thumb.clone();
476
477 let sl_source: Rc<MutableInteractionSource> = config
478 .interaction_source
479 .clone()
480 .map(Rc::new)
481 .unwrap_or_else(|| remember(MutableInteractionSource::new));
482 let mut host = Modifier::new()
483 .min_width(200.0)
484 .height(44.0)
485 .interaction_source(&sl_source);
486 if !is_enabled {
487 host = host.enabled(false);
488 }
489 host = host.focusable(true);
490 Box(host
491 .painter(move |scene: &mut Scene, rect: Rect, alpha: f32| {
492 let mul_c = |c: Color| {
493 Color(
494 c.0,
495 c.1,
496 c.2,
497 ((c.3 as f32) * alpha).clamp(0.0, 255.0) as u8,
498 )
499 };
500 let track_h = dp_to_px(SliderDefaults::TRACK_HEIGHT);
501 let thumb_w = dp_to_px(SliderDefaults::THUMB_WIDTH);
502 let thumb_h = dp_to_px(SliderDefaults::THUMB_HEIGHT);
503 let dot_r = dp_to_px(2.0);
504 let corner = track_h * 0.5;
505 let gap = thumb_w * 0.5 + dp_to_px(ProgressIndicatorDefaults::SLIDER_THUMB_TRACK_GAP);
506 let pad = thumb_w * 0.5;
507 let track_x = rect.x + pad;
508 let track_w = (rect.w - thumb_w).max(0.0);
509 let cy = rect.y + rect.h * 0.5;
510
511 let thumb_pos = |tf: f32, fracs: &[f32]| {
512 if step.is_some() && !fracs.is_empty() {
513 let is_first = (tf - fracs[0]).abs() < 1e-6;
514 let is_last = (tf - fracs[fracs.len() - 1]).abs() < 1e-6;
515 if is_first || is_last {
516 track_x + tf * track_w
517 } else {
518 track_x + (track_w - track_h) * tf + corner
519 }
520 } else {
521 track_x + tf * track_w
522 }
523 };
524 let k0 = thumb_pos(t0, &tick_frac);
525 let k1 = thumb_pos(t1, &tick_frac);
526 let active_l = k0.min(k1);
527 let active_r = k0.max(k1);
528
529 *track_rect_p.borrow_mut() = Rect {
530 x: track_x,
531 y: rect.y,
532 w: track_w,
533 h: rect.h,
534 };
535
536 let linactive_w = (active_l - gap - track_x).max(0.0);
537 if linactive_w > 0.0 {
538 scene.nodes.push(SceneNode::Rect {
539 rect: Rect {
540 x: track_x,
541 y: cy - track_h * 0.5,
542 w: linactive_w,
543 h: track_h,
544 },
545 brush: Brush::Solid(mul_c(inact_trk)),
546 radius: [corner; 4],
547 });
548 let sx0 = track_x + corner;
549 scene.nodes.push(SceneNode::Ellipse {
550 rect: Rect {
551 x: sx0 - dot_r,
552 y: cy - dot_r,
553 w: dot_r * 2.0,
554 h: dot_r * 2.0,
555 },
556 brush: Brush::Solid(mul_c(inact_tick)),
557 });
558 }
559 let rinactive_x = (active_r + gap).min(track_x + track_w);
560 let rinactive_w = (track_x + track_w - rinactive_x).max(0.0);
561 if rinactive_w > 0.0 {
562 scene.nodes.push(SceneNode::Rect {
563 rect: Rect {
564 x: rinactive_x,
565 y: cy - track_h * 0.5,
566 w: rinactive_w,
567 h: track_h,
568 },
569 brush: Brush::Solid(mul_c(inact_trk)),
570 radius: [corner; 4],
571 });
572 let sx = track_x + track_w - corner;
573 scene.nodes.push(SceneNode::Ellipse {
574 rect: Rect {
575 x: sx - dot_r,
576 y: cy - dot_r,
577 w: dot_r * 2.0,
578 h: dot_r * 2.0,
579 },
580 brush: Brush::Solid(mul_c(inact_tick)),
581 });
582 }
583 let active_w = (active_r - gap - (active_l + gap)).max(0.0);
584 if active_w > 0.0 {
585 scene.nodes.push(SceneNode::Rect {
586 rect: Rect {
587 x: active_l + gap,
588 y: cy - track_h * 0.5,
589 w: active_w,
590 h: track_h,
591 },
592 brush: Brush::Solid(mul_c(act_trk)),
593 radius: [corner; 4],
594 });
595 }
596 let tick_start = track_x + corner;
597 let tick_end = track_x + track_w - corner;
598 for (i, &tf) in tick_frac.iter().enumerate() {
599 let tx = tick_start + tf * (tick_end - tick_start);
600 if i == 0 || i == tick_frac.len() - 1 {
602 continue;
603 }
604 let in_lgap = tx >= active_l - gap && tx <= active_l + gap;
605 let in_rgap = tx >= active_r - gap && tx <= active_r + gap;
606 if in_lgap || in_rgap {
607 continue;
608 }
609 let on_active = tx >= active_l + gap && tx <= active_r - gap;
610 scene.nodes.push(SceneNode::Ellipse {
611 rect: Rect {
612 x: tx - dot_r,
613 y: cy - dot_r,
614 w: dot_r * 2.0,
615 h: dot_r * 2.0,
616 },
617 brush: Brush::Solid(mul_c(if on_active { act_tick } else { inact_tick })),
618 });
619 }
620 let da = *drag_active_p.get();
621 let at = *active_thumb_p.get();
622 let thumbs = [k0, k1];
623 for (idx, &kx) in thumbs.iter().enumerate() {
624 let is_active = da && (if idx == 0 { !at } else { at });
625 let tw = if is_active { thumb_w * 0.5 } else { thumb_w };
626 scene.nodes.push(SceneNode::Rect {
627 rect: Rect {
628 x: kx - tw * 0.5,
629 y: cy - thumb_h * 0.5,
630 w: tw,
631 h: thumb_h,
632 },
633 brush: Brush::Solid(mul_c(thumb_col)),
634 radius: [tw * 0.5; 4],
635 });
636 }
637 })
638 .on_pointer_down({
639 let oc = oc.clone();
640 let track_rect = track_rect.clone();
641 let drag_active = drag_active.clone();
642 let active_thumb = active_thumb.clone();
643 let en = is_enabled;
644 move |pe: PointerEvent| {
645 if !en {
646 return;
647 }
648 drag_active.set(true);
649 let r = *track_rect.borrow();
650 let v = value_from_x(pe.position_in_window().x, r, min, max, step);
651 let use_end = (v - end).abs() < (v - start).abs();
652 active_thumb.set(use_end);
653 let (a, b) = if use_end {
654 (start, v.max(start))
655 } else {
656 (v.min(end), end)
657 };
658 (oc)(a, b);
659 pe.consume();
660 }
661 })
662 .on_pointer_move({
663 let oc = oc.clone();
664 let track_rect = track_rect.clone();
665 let drag_active = drag_active.clone();
666 let active_thumb = active_thumb.clone();
667 let en = is_enabled;
668 move |pe: PointerEvent| {
669 if !en {
670 return;
671 }
672 if !*drag_active.get() {
673 return;
674 }
675 let r = *track_rect.borrow();
676 let v = value_from_x(pe.position_in_window().x, r, min, max, step);
677 let use_end = *active_thumb.get();
678 let (a, b) = if use_end {
679 (start, v.max(start))
680 } else {
681 (v.min(end), end)
682 };
683 (oc)(a, b);
684 pe.consume();
685 }
686 })
687 .on_pointer_up({
688 let drag_active = drag_active.clone();
689 let active_thumb = active_thumb.clone();
690 let on_finished = config.on_value_change_finished.clone();
691 move |_pe: PointerEvent| {
692 let was = *drag_active.get();
693 drag_active.set(false);
694 active_thumb.set(false);
695 if was {
696 if let Some(ref cb) = on_finished {
697 (cb)();
698 }
699 }
700 }
701 })
702 .on_pointer_cancel({
703 let drag_active = drag_active.clone();
704 let active_thumb = active_thumb.clone();
705 let on_finished = config.on_value_change_finished.clone();
706 move |_pe: PointerEvent| {
707 if *drag_active.get() {
708 drag_active.set(false);
709 active_thumb.set(false);
710 if let Some(ref cb) = on_finished {
711 (cb)();
712 }
713 }
714 }
715 })
716 .on_scroll({
717 let oc = oc.clone();
718 let active_thumb = active_thumb.clone();
719 let en = is_enabled;
720 move |d: Vec2| -> Vec2 {
721 if !en {
722 return d;
723 }
724 let dir = if d.y < -0.5 {
725 1
726 } else if d.y > 0.5 {
727 -1
728 } else {
729 0
730 };
731 if dir == 0 {
732 return d;
733 }
734 let step_val = step.unwrap_or(1.0).max(1e-6);
735 let use_end = *active_thumb.get();
736 let (mut a, mut b) = (start, end);
737 if use_end {
738 b = snap_step(end + (dir as f32) * step_val, min, max, step).max(a);
739 } else {
740 a = snap_step(start + (dir as f32) * step_val, min, max, step).min(b);
741 }
742 if (a - start).abs() > 1e-6 || (b - end).abs() > 1e-6 {
743 (oc)(a, b);
744 Vec2 { x: d.x, y: 0.0 }
745 } else {
746 d
747 }
748 }
749 })
750 .on_key_event({
751 let oc = oc.clone();
752 let active_thumb = active_thumb.clone();
753 let en = is_enabled;
754 move |ke: KeyEvent| -> bool {
755 if !en || ke.event_type != KeyEventType::Down {
756 return false;
757 }
758 let step_val = step.unwrap_or(((max - min) / 100.0).max(1e-6)).max(1e-6);
759 let page = step_val * 10.0;
760 let use_end = *active_thumb.get();
761 let (a, b) = if use_end {
762 let new_b = match ke.key {
763 Key::ArrowLeft | Key::ArrowDown => {
764 snap_step(end - step_val, min, max, step)
765 }
766 Key::ArrowRight | Key::ArrowUp => {
767 snap_step(end + step_val, min, max, step)
768 }
769 Key::PageDown => snap_step(end - page, min, max, step),
770 Key::PageUp => snap_step(end + page, min, max, step),
771 Key::Home => start,
772 Key::End => max,
773 _ => return false,
774 };
775 (start, new_b.max(start))
776 } else {
777 let new_a = match ke.key {
778 Key::ArrowLeft | Key::ArrowDown => {
779 snap_step(start - step_val, min, max, step)
780 }
781 Key::ArrowRight | Key::ArrowUp => {
782 snap_step(start + step_val, min, max, step)
783 }
784 Key::PageDown => snap_step(start - page, min, max, step),
785 Key::PageUp => snap_step(start + page, min, max, step),
786 Key::Home => min,
787 Key::End => end,
788 _ => return false,
789 };
790 (new_a.min(end), end)
791 };
792 if (a - start).abs() > 1e-6 || (b - end).abs() > 1e-6 {
793 (oc)(a, b);
794 }
795 true
796 }
797 })
798 .then(config.modifier))
799 .semantics(Semantics {
800 role: Role::Slider,
801 enabled: is_enabled,
802 value: Some(format!("{start}..{end}")),
803 ..Default::default()
804 })
805}