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 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 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}