1use std::sync::Arc;
7
8use derive_builder::Builder;
9use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
10use tessera_ui::{
11 Color, ComputedData, Constraint, DimensionValue, Dp, MeasureInput, MeasurementError, Px,
12 PxPosition, focus_state::Focus, tessera,
13};
14
15use crate::{material_color, pipelines::image_vector::command::VectorTintMode};
16
17use interaction::{
18 apply_range_slider_accessibility, apply_slider_accessibility, handle_range_slider_state,
19 handle_slider_state,
20};
21use layout::{
22 CenteredSliderLayout, RangeSliderLayout, SliderLayout, centered_slider_layout,
23 fallback_component_width, range_slider_layout, resolve_component_width, slider_layout,
24};
25use render::{
26 render_active_segment, render_centered_stops, render_centered_tracks, render_focus,
27 render_handle, render_inactive_segment, render_range_stops, render_range_tracks,
28 render_stop_indicator,
29};
30
31pub use interaction::RangeSliderState;
32
33mod interaction;
34mod layout;
35mod render;
36
37const ACCESSIBILITY_STEP: f32 = 0.05;
38const MIN_TOUCH_TARGET: Dp = Dp(40.0);
39const HANDLE_GAP: Dp = Dp(6.0);
40const STOP_INDICATOR_DIAMETER: Dp = Dp(4.0);
41
42pub(crate) struct SliderStateInner {
45 pub is_dragging: bool,
47 pub focus: Focus,
49 pub is_hovered: bool,
51}
52
53impl Default for SliderStateInner {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl SliderStateInner {
60 pub fn new() -> Self {
61 Self {
62 is_dragging: false,
63 focus: Focus::new(),
64 is_hovered: false,
65 }
66 }
67}
68
69#[derive(Clone)]
79pub struct SliderState {
80 inner: Arc<RwLock<SliderStateInner>>,
81}
82
83impl SliderState {
84 pub fn new() -> Self {
86 Self {
87 inner: Arc::new(RwLock::new(SliderStateInner::new())),
88 }
89 }
90
91 pub(crate) fn read(&self) -> RwLockReadGuard<'_, SliderStateInner> {
92 self.inner.read()
93 }
94
95 pub(crate) fn write(&self) -> RwLockWriteGuard<'_, SliderStateInner> {
96 self.inner.write()
97 }
98
99 pub fn is_dragging(&self) -> bool {
101 self.inner.read().is_dragging
102 }
103
104 pub fn set_dragging(&self, dragging: bool) {
106 self.inner.write().is_dragging = dragging;
107 }
108
109 pub fn request_focus(&self) {
111 self.inner.write().focus.request_focus();
112 }
113
114 pub fn clear_focus(&self) {
116 self.inner.write().focus.unfocus();
117 }
118
119 pub fn is_focused(&self) -> bool {
121 self.inner.read().focus.is_focused()
122 }
123
124 pub fn is_hovered(&self) -> bool {
126 self.inner.read().is_hovered
127 }
128}
129
130impl Default for SliderState {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
138pub enum SliderSize {
139 #[default]
141 ExtraSmall,
142 Small,
144 Medium,
146 Large,
148 ExtraLarge,
150}
151
152#[derive(Builder, Clone)]
154#[builder(pattern = "owned")]
155pub struct SliderArgs {
156 #[builder(default = "0.0")]
158 pub value: f32,
159 #[builder(default = "Arc::new(|_| {})")]
161 pub on_change: Arc<dyn Fn(f32) + Send + Sync>,
162 #[builder(default)]
164 pub size: SliderSize,
165 #[builder(default = "DimensionValue::Fixed(Dp(260.0).to_px())")]
167 pub width: DimensionValue,
168 #[builder(default = "crate::material_color::global_material_scheme().primary")]
170 pub active_track_color: Color,
171 #[builder(default = "crate::material_color::global_material_scheme().secondary_container")]
173 pub inactive_track_color: Color,
174 #[builder(default = "Dp(4.0)")]
176 pub thumb_diameter: Dp,
177 #[builder(default = "crate::material_color::global_material_scheme().primary")]
179 pub thumb_color: Color,
180 #[builder(default = "Dp(18.0)")]
182 pub state_layer_diameter: Dp,
183 #[builder(
185 default = "crate::material_color::global_material_scheme().primary.with_alpha(0.18)"
186 )]
187 pub state_layer_color: Color,
188 #[builder(default = "false")]
190 pub disabled: bool,
191 #[builder(default, setter(strip_option, into))]
193 pub accessibility_label: Option<String>,
194 #[builder(default, setter(strip_option, into))]
196 pub accessibility_description: Option<String>,
197 #[builder(default = "true")]
199 pub show_stop_indicator: bool,
200 #[builder(default, setter(strip_option, into))]
202 pub inset_icon: Option<crate::icon::IconContent>,
203}
204
205#[derive(Builder, Clone)]
207#[builder(pattern = "owned")]
208pub struct RangeSliderArgs {
209 #[builder(default = "(0.0, 1.0)")]
211 pub value: (f32, f32),
212
213 #[builder(default = "Arc::new(|_| {})")]
215 pub on_change: Arc<dyn Fn((f32, f32)) + Send + Sync>,
216
217 #[builder(default)]
219 pub size: SliderSize,
220
221 #[builder(default = "DimensionValue::Fixed(Dp(260.0).to_px())")]
223 pub width: DimensionValue,
224
225 #[builder(default = "crate::material_color::global_material_scheme().primary")]
227 pub active_track_color: Color,
228
229 #[builder(default = "crate::material_color::global_material_scheme().secondary_container")]
231 pub inactive_track_color: Color,
232
233 #[builder(default = "Dp(4.0)")]
235 pub thumb_diameter: Dp,
236
237 #[builder(default = "crate::material_color::global_material_scheme().primary")]
239 pub thumb_color: Color,
240
241 #[builder(default = "Dp(18.0)")]
243 pub state_layer_diameter: Dp,
244
245 #[builder(
247 default = "crate::material_color::global_material_scheme().primary.with_alpha(0.18)"
248 )]
249 pub state_layer_color: Color,
250
251 #[builder(default = "false")]
253 pub disabled: bool,
254 #[builder(default, setter(strip_option, into))]
256 pub accessibility_label: Option<String>,
257 #[builder(default, setter(strip_option, into))]
259 pub accessibility_description: Option<String>,
260
261 #[builder(default = "true")]
263 pub show_stop_indicator: bool,
264}
265
266fn measure_slider(
267 input: &MeasureInput,
268 layout: SliderLayout,
269 clamped_value: f32,
270 has_inset_icon: bool,
271) -> Result<ComputedData, MeasurementError> {
272 let self_width = layout.component_width;
273 let self_height = layout.component_height;
274
275 let active_id = input.children_ids[0];
276 let inactive_id = input.children_ids[1];
277
278 let mut current_index = 2;
280
281 let icon_id = if has_inset_icon {
282 let id = input.children_ids.get(current_index).copied();
283 current_index += 1;
284 id
285 } else {
286 None
287 };
288
289 let focus_id = input.children_ids[current_index];
290 current_index += 1;
291 let handle_id = input.children_ids[current_index];
292 current_index += 1;
293
294 let stop_id = if layout.show_stop_indicator {
295 input.children_ids.get(current_index).copied()
296 } else {
297 None
298 };
299
300 let active_width = layout.active_width(clamped_value);
301 let inactive_width = layout.inactive_width(clamped_value);
302
303 let active_constraint = Constraint::new(
304 DimensionValue::Fixed(active_width),
305 DimensionValue::Fixed(layout.track_height),
306 );
307 input.measure_child(active_id, &active_constraint)?;
308 input.place_child(active_id, PxPosition::new(Px(0), layout.track_y));
309
310 let inactive_constraint = Constraint::new(
311 DimensionValue::Fixed(inactive_width),
312 DimensionValue::Fixed(layout.track_height),
313 );
314 input.measure_child(inactive_id, &inactive_constraint)?;
315 input.place_child(
316 inactive_id,
317 PxPosition::new(
318 Px(active_width.0 + layout.handle_gap.0 * 2 + layout.handle_width.0),
319 layout.track_y,
320 ),
321 );
322
323 let focus_constraint = Constraint::new(
324 DimensionValue::Fixed(layout.focus_width),
325 DimensionValue::Fixed(layout.focus_height),
326 );
327 input.measure_child(focus_id, &focus_constraint)?;
328
329 let handle_constraint = Constraint::new(
330 DimensionValue::Fixed(layout.handle_width),
331 DimensionValue::Fixed(layout.handle_height),
332 );
333 input.measure_child(handle_id, &handle_constraint)?;
334
335 let handle_center = layout.handle_center(clamped_value);
336 let focus_offset = layout.center_child_offset(layout.focus_width);
337 input.place_child(
338 focus_id,
339 PxPosition::new(Px(handle_center.x.0 - focus_offset.0), layout.focus_y),
340 );
341
342 let handle_offset = layout.center_child_offset(layout.handle_width);
343 input.place_child(
344 handle_id,
345 PxPosition::new(Px(handle_center.x.0 - handle_offset.0), layout.handle_y),
346 );
347
348 if let Some(stop_id) = stop_id {
349 let stop_size = layout.stop_indicator_diameter;
350 let stop_constraint = Constraint::new(
351 DimensionValue::Fixed(stop_size),
352 DimensionValue::Fixed(stop_size),
353 );
354 input.measure_child(stop_id, &stop_constraint)?;
355 let stop_offset = layout.center_child_offset(layout.stop_indicator_diameter);
356 let inactive_start = active_width.0 + layout.handle_gap.0 * 2 + layout.handle_width.0;
357 let padding = Dp(8.0).to_px() - stop_size / Px(2);
358 let stop_center_x = Px(inactive_start + inactive_width.0 - padding.0);
359 input.place_child(
360 stop_id,
361 PxPosition::new(Px(stop_center_x.0 - stop_offset.0), layout.stop_indicator_y),
362 );
363 }
364
365 if let Some(icon_id) = icon_id
366 && let Some(icon_size) = layout.icon_size
367 {
368 let icon_constraint = Constraint::new(
369 DimensionValue::Wrap {
370 min: None,
371 max: Some(icon_size.into()),
372 },
373 DimensionValue::Wrap {
374 min: None,
375 max: Some(icon_size.into()),
376 },
377 );
378 let icon_measured = input.measure_child(icon_id, &icon_constraint)?;
379
380 let icon_padding = Dp(8.0).to_px();
382 let icon_y = layout.track_y + Px((layout.track_height.0 - icon_measured.height.0) / 2);
383 input.place_child(icon_id, PxPosition::new(icon_padding, icon_y));
384 }
385
386 Ok(ComputedData {
387 width: self_width,
388 height: self_height,
389 })
390}
391
392#[derive(Clone, Copy)]
393struct SliderColors {
394 active_track: Color,
395 inactive_track: Color,
396 handle: Color,
397 handle_focus: Color,
398}
399
400fn slider_colors(args: &SliderArgs, is_hovered: bool, is_dragging: bool) -> SliderColors {
401 if args.disabled {
402 let scheme = material_color::global_material_scheme();
403 return SliderColors {
404 active_track: scheme.on_surface.with_alpha(0.38),
405 inactive_track: scheme.on_surface.with_alpha(0.12),
406 handle: scheme.on_surface.with_alpha(0.38),
407 handle_focus: Color::new(0.0, 0.0, 0.0, 0.0),
408 };
409 }
410
411 let mut state_layer_alpha_scale = 0.0;
412 if is_dragging {
413 state_layer_alpha_scale = 1.0;
414 } else if is_hovered {
415 state_layer_alpha_scale = 0.7;
416 }
417 let base_state = args.state_layer_color;
418 let state_layer_alpha = (base_state.a * state_layer_alpha_scale).clamp(0.0, 1.0);
419 let handle_focus = Color::new(base_state.r, base_state.g, base_state.b, state_layer_alpha);
420
421 SliderColors {
422 active_track: args.active_track_color,
423 inactive_track: args.inactive_track_color,
424 handle: args.thumb_color,
425 handle_focus,
426 }
427}
428
429#[tessera]
466pub fn slider(args: impl Into<SliderArgs>, state: SliderState) {
467 let args: SliderArgs = args.into();
468 let initial_width = fallback_component_width(&args);
469 let layout = slider_layout(&args, initial_width);
470 let clamped_value = args.value.clamp(0.0, 1.0);
471 let state_snapshot = state.read();
472 let colors = slider_colors(&args, state_snapshot.is_hovered, state_snapshot.is_dragging);
473 drop(state_snapshot);
474
475 render_active_segment(layout, &colors);
476 render_inactive_segment(layout, &colors);
477
478 if let Some(icon_size) = layout.icon_size
479 && let Some(inset_icon) = args.inset_icon.as_ref()
480 {
481 let scheme = material_color::global_material_scheme();
482 let tint = if args.disabled {
483 scheme.on_surface.with_alpha(0.38)
484 } else {
485 scheme.on_primary
486 };
487
488 crate::icon::icon(
489 crate::icon::IconArgsBuilder::default()
490 .content(inset_icon.clone())
491 .tint(tint)
492 .tint_mode(VectorTintMode::Solid)
493 .size(icon_size)
494 .build()
495 .expect("Failed to build icon args"),
496 );
497 }
498
499 render_focus(layout, &colors);
500 render_handle(layout, &colors);
501 if layout.show_stop_indicator {
502 render_stop_indicator(layout, &colors);
503 }
504
505 let cloned_args = args.clone();
506 let state_clone = state.clone();
507 let clamped_value_for_accessibility = clamped_value;
508 input_handler(Box::new(move |mut input| {
509 let resolved_layout = slider_layout(&cloned_args, input.computed_data.width);
510 handle_slider_state(&mut input, &state_clone, &cloned_args, &resolved_layout);
511 apply_slider_accessibility(
512 &mut input,
513 &cloned_args,
514 clamped_value_for_accessibility,
515 &cloned_args.on_change,
516 );
517 }));
518
519 measure(Box::new(move |input| {
520 let component_width = resolve_component_width(&args, input.parent_constraint);
521 let resolved_layout = slider_layout(&args, component_width);
522 let has_inset_icon = args.inset_icon.is_some();
523 measure_slider(input, resolved_layout, clamped_value, has_inset_icon)
524 }));
525}
526
527fn measure_centered_slider(
528 input: &MeasureInput,
529 layout: CenteredSliderLayout,
530 value: f32,
531) -> Result<ComputedData, MeasurementError> {
532 let self_width = layout.base.component_width;
533 let self_height = layout.base.component_height;
534 let track_y = layout.base.track_y;
535
536 let left_inactive_id = input.children_ids[0];
537 let active_id = input.children_ids[1];
538 let right_inactive_id = input.children_ids[2];
539 let focus_id = input.children_ids[3];
540 let handle_id = input.children_ids[4];
541 let left_stop_id = input.children_ids[5];
542 let right_stop_id = input.children_ids[6];
543
544 let segments = layout.segments(value);
545
546 input.measure_child(
548 left_inactive_id,
549 &Constraint::new(
550 DimensionValue::Fixed(segments.left_inactive.1),
551 DimensionValue::Fixed(layout.base.track_height),
552 ),
553 )?;
554 input.place_child(
555 left_inactive_id,
556 PxPosition::new(segments.left_inactive.0, track_y),
557 );
558
559 input.measure_child(
561 active_id,
562 &Constraint::new(
563 DimensionValue::Fixed(segments.active.1),
564 DimensionValue::Fixed(layout.base.track_height),
565 ),
566 )?;
567 input.place_child(active_id, PxPosition::new(segments.active.0, track_y));
568
569 input.measure_child(
571 right_inactive_id,
572 &Constraint::new(
573 DimensionValue::Fixed(segments.right_inactive.1),
574 DimensionValue::Fixed(layout.base.track_height),
575 ),
576 )?;
577 input.place_child(
578 right_inactive_id,
579 PxPosition::new(segments.right_inactive.0, track_y),
580 );
581
582 let focus_offset = layout.base.center_child_offset(layout.base.focus_width);
584 input.measure_child(
585 focus_id,
586 &Constraint::new(
587 DimensionValue::Fixed(layout.base.focus_width),
588 DimensionValue::Fixed(layout.base.focus_height),
589 ),
590 )?;
591 input.place_child(
592 focus_id,
593 PxPosition::new(
594 Px(segments.handle_center.x.0 - focus_offset.0),
595 layout.base.focus_y,
596 ),
597 );
598
599 let handle_offset = layout.base.center_child_offset(layout.base.handle_width);
601 input.measure_child(
602 handle_id,
603 &Constraint::new(
604 DimensionValue::Fixed(layout.base.handle_width),
605 DimensionValue::Fixed(layout.base.handle_height),
606 ),
607 )?;
608 input.place_child(
609 handle_id,
610 PxPosition::new(
611 Px(segments.handle_center.x.0 - handle_offset.0),
612 layout.base.handle_y,
613 ),
614 );
615
616 if layout.base.show_stop_indicator {
617 let stop_size = layout.base.stop_indicator_diameter;
619 let stop_constraint = Constraint::new(
620 DimensionValue::Fixed(stop_size),
621 DimensionValue::Fixed(stop_size),
622 );
623 input.measure_child(left_stop_id, &stop_constraint)?;
624
625 let stop_offset = layout.base.center_child_offset(stop_size);
626 let stop_padding = layout.stop_indicator_offset();
627
628 let left_stop_x = Px(stop_padding.0);
629
630 input.place_child(
631 left_stop_id,
632 PxPosition::new(
633 Px(left_stop_x.0 - stop_offset.0),
634 layout.base.stop_indicator_y,
635 ),
636 );
637
638 input.measure_child(right_stop_id, &stop_constraint)?;
640 let right_stop_x = Px(self_width.0 - stop_padding.0);
641
642 input.place_child(
643 right_stop_id,
644 PxPosition::new(
645 Px(right_stop_x.0 - stop_offset.0),
646 layout.base.stop_indicator_y,
647 ),
648 );
649 }
650
651 Ok(ComputedData {
652 width: self_width,
653 height: self_height,
654 })
655}
656
657#[tessera]
711pub fn centered_slider(args: impl Into<SliderArgs>, state: SliderState) {
712 let args: SliderArgs = args.into();
713 let initial_width = fallback_component_width(&args);
714 let layout = centered_slider_layout(&args, initial_width);
715 let clamped_value = args.value.clamp(0.0, 1.0);
716 let state_snapshot = state.read();
717 let colors = slider_colors(&args, state_snapshot.is_hovered, state_snapshot.is_dragging);
718 drop(state_snapshot);
719
720 render_centered_tracks(layout, &colors);
721 render_focus(layout.base, &colors);
722 render_handle(layout.base, &colors);
723 if layout.base.show_stop_indicator {
724 render_centered_stops(layout, &colors);
725 }
726
727 let cloned_args = args.clone();
728 let state_clone = state.clone();
729 let clamped_value_for_accessibility = clamped_value;
730 input_handler(Box::new(move |mut input| {
731 let resolved_layout = centered_slider_layout(&cloned_args, input.computed_data.width);
732 handle_slider_state(
733 &mut input,
734 &state_clone,
735 &cloned_args,
736 &resolved_layout.base,
737 );
738 apply_slider_accessibility(
739 &mut input,
740 &cloned_args,
741 clamped_value_for_accessibility,
742 &cloned_args.on_change,
743 );
744 }));
745
746 measure(Box::new(move |input| {
747 let component_width = resolve_component_width(&args, input.parent_constraint);
748 let resolved_layout = centered_slider_layout(&args, component_width);
749 measure_centered_slider(input, resolved_layout, clamped_value)
750 }));
751}
752
753fn measure_range_slider(
754 input: &MeasureInput,
755 layout: RangeSliderLayout,
756 start: f32,
757 end: f32,
758) -> Result<ComputedData, MeasurementError> {
759 let self_width = layout.base.component_width;
760 let self_height = layout.base.component_height;
761 let track_y = layout.base.track_y;
762
763 let left_inactive_id = input.children_ids[0];
764 let active_id = input.children_ids[1];
765 let right_inactive_id = input.children_ids[2];
766 let focus_start_id = input.children_ids[3];
767 let focus_end_id = input.children_ids[4];
768 let handle_start_id = input.children_ids[5];
769 let handle_end_id = input.children_ids[6];
770 let stop_start_id = input.children_ids[7];
771 let stop_end_id = input.children_ids[8];
772
773 let segments = layout.segments(start, end);
774
775 input.measure_child(
776 left_inactive_id,
777 &Constraint::new(
778 DimensionValue::Fixed(segments.left_inactive.1),
779 DimensionValue::Fixed(layout.base.track_height),
780 ),
781 )?;
782 input.place_child(
783 left_inactive_id,
784 PxPosition::new(segments.left_inactive.0, track_y),
785 );
786
787 input.measure_child(
788 active_id,
789 &Constraint::new(
790 DimensionValue::Fixed(segments.active.1),
791 DimensionValue::Fixed(layout.base.track_height),
792 ),
793 )?;
794 input.place_child(active_id, PxPosition::new(segments.active.0, track_y));
795
796 input.measure_child(
797 right_inactive_id,
798 &Constraint::new(
799 DimensionValue::Fixed(segments.right_inactive.1),
800 DimensionValue::Fixed(layout.base.track_height),
801 ),
802 )?;
803 input.place_child(
804 right_inactive_id,
805 PxPosition::new(segments.right_inactive.0, track_y),
806 );
807
808 let focus_constraint = Constraint::new(
809 DimensionValue::Fixed(layout.base.focus_width),
810 DimensionValue::Fixed(layout.base.focus_height),
811 );
812 let handle_constraint = Constraint::new(
813 DimensionValue::Fixed(layout.base.handle_width),
814 DimensionValue::Fixed(layout.base.handle_height),
815 );
816 let focus_offset = layout.base.center_child_offset(layout.base.focus_width);
817 let handle_offset = layout.base.center_child_offset(layout.base.handle_width);
818
819 input.measure_child(focus_start_id, &focus_constraint)?;
820 input.place_child(
821 focus_start_id,
822 PxPosition::new(
823 Px(segments.start_handle_center.x.0 - focus_offset.0),
824 layout.base.focus_y,
825 ),
826 );
827
828 input.measure_child(handle_start_id, &handle_constraint)?;
829 input.place_child(
830 handle_start_id,
831 PxPosition::new(
832 Px(segments.start_handle_center.x.0 - handle_offset.0),
833 layout.base.handle_y,
834 ),
835 );
836
837 input.measure_child(focus_end_id, &focus_constraint)?;
838 input.place_child(
839 focus_end_id,
840 PxPosition::new(
841 Px(segments.end_handle_center.x.0 - focus_offset.0),
842 layout.base.focus_y,
843 ),
844 );
845
846 input.measure_child(handle_end_id, &handle_constraint)?;
847 input.place_child(
848 handle_end_id,
849 PxPosition::new(
850 Px(segments.end_handle_center.x.0 - handle_offset.0),
851 layout.base.handle_y,
852 ),
853 );
854
855 if layout.base.show_stop_indicator {
856 let stop_size = layout.base.stop_indicator_diameter;
857 let stop_constraint = Constraint::new(
858 DimensionValue::Fixed(stop_size),
859 DimensionValue::Fixed(stop_size),
860 );
861 input.measure_child(stop_start_id, &stop_constraint)?;
862
863 let stop_offset = layout.base.center_child_offset(stop_size);
864 let padding = Dp(8.0).to_px() - stop_size / Px(2);
868 let start_stop_x = Px(padding.0);
869
870 input.place_child(
871 stop_start_id,
872 PxPosition::new(
873 Px(start_stop_x.0 - stop_offset.0),
874 layout.base.stop_indicator_y,
875 ),
876 );
877
878 input.measure_child(stop_end_id, &stop_constraint)?;
879 let end_stop_x = Px(self_width.0 - padding.0);
880
881 input.place_child(
882 stop_end_id,
883 PxPosition::new(
884 Px(end_stop_x.0 - stop_offset.0),
885 layout.base.stop_indicator_y,
886 ),
887 );
888 }
889
890 Ok(ComputedData {
891 width: self_width,
892 height: self_height,
893 })
894}
895
896#[tessera]
933pub fn range_slider(args: impl Into<RangeSliderArgs>, state: RangeSliderState) {
934 let args: RangeSliderArgs = args.into();
935 let dummy_slider_args = SliderArgsBuilder::default()
938 .width(args.width)
939 .size(args.size)
940 .build()
941 .expect("Failed to build dummy args");
942 let initial_width = fallback_component_width(&dummy_slider_args);
943 let layout = range_slider_layout(&args, initial_width);
944
945 let start = args.value.0.clamp(0.0, 1.0);
946 let end = args.value.1.clamp(start, 1.0);
947
948 let state_snapshot = state.read();
949 let is_dragging_any = state_snapshot.is_dragging_start || state_snapshot.is_dragging_end;
955
956 let mut state_layer_alpha_scale = 0.0;
961 if is_dragging_any {
962 state_layer_alpha_scale = 1.0;
963 } else if state_snapshot.is_hovered {
964 state_layer_alpha_scale = 0.7;
965 }
966
967 let base_state = args.state_layer_color;
968 let state_layer_alpha = (base_state.a * state_layer_alpha_scale).clamp(0.0, 1.0);
969 let handle_focus_color =
970 Color::new(base_state.r, base_state.g, base_state.b, state_layer_alpha);
971
972 let colors = if args.disabled {
973 let scheme = material_color::global_material_scheme();
974 SliderColors {
975 active_track: scheme.on_surface.with_alpha(0.38),
976 inactive_track: scheme.on_surface.with_alpha(0.12),
977 handle: scheme.on_surface.with_alpha(0.38),
978 handle_focus: Color::new(0.0, 0.0, 0.0, 0.0),
979 }
980 } else {
981 SliderColors {
982 active_track: args.active_track_color,
983 inactive_track: args.inactive_track_color,
984 handle: args.thumb_color,
985 handle_focus: handle_focus_color,
986 }
987 };
988
989 drop(state_snapshot);
990
991 render_range_tracks(layout, &colors);
992
993 render_focus(layout.base, &colors);
995 render_focus(layout.base, &colors);
1000
1001 render_handle(layout.base, &colors);
1003
1004 render_handle(layout.base, &colors);
1006
1007 if layout.base.show_stop_indicator {
1008 render_range_stops(layout, &colors);
1009 }
1010
1011 let cloned_args = args.clone();
1012 let state_clone = state.clone();
1013 let start_val = start;
1014 let end_val = end;
1015
1016 input_handler(Box::new(move |mut input| {
1017 let resolved_layout = range_slider_layout(&cloned_args, input.computed_data.width);
1018 handle_range_slider_state(
1019 &mut input,
1020 &state_clone,
1021 &cloned_args,
1022 &resolved_layout.base,
1023 );
1024 apply_range_slider_accessibility(
1025 &mut input,
1026 &cloned_args,
1027 start_val,
1028 end_val,
1029 &cloned_args.on_change,
1030 );
1031 }));
1032
1033 measure(Box::new(move |input| {
1034 let dummy_args_for_resolve = SliderArgsBuilder::default()
1035 .width(args.width)
1036 .size(args.size)
1037 .build()
1038 .expect("Failed to build dummy args");
1039 let component_width =
1040 resolve_component_width(&dummy_args_for_resolve, input.parent_constraint);
1041 let resolved_layout = range_slider_layout(&args, component_width);
1042 measure_range_slider(input, resolved_layout, start, end)
1043 }));
1044}