1use std::fmt;
15use std::rc::Rc;
16
17use frus_core::{BorderRadius, Color, Point, Rect, Scene};
18
19use crate::theme::Theme;
20
21#[derive(Clone, Copy, Debug, PartialEq)]
24pub struct SliderPalette {
25 pub active_track: Color,
27 pub inactive_track: Color,
29 pub secondary_track: Color,
31 pub thumb: Color,
33 pub thumb_border: Option<Color>,
35 pub active_tick_mark: Color,
37 pub inactive_tick_mark: Color,
39}
40
41#[derive(Clone, Copy, Debug)]
43pub struct SliderPaint<'a> {
44 pub theme: &'a Theme,
46 pub palette: SliderPalette,
48 pub enabled: bool,
50 pub discrete: bool,
52 pub opacity: f32,
54}
55
56impl SliderPaint<'_> {
57 pub fn is_rtl(&self) -> bool {
59 self.theme.direction.is_rtl()
60 }
61}
62
63#[derive(Clone, Copy, Debug, PartialEq)]
65pub struct SliderThumb {
66 pub elevation: f32,
68 pub on_top: bool,
71}
72
73pub trait SliderTrackShape {
76 fn paint(
81 &self,
82 scene: &mut Scene,
83 track: Rect,
84 thumbs: &[f32],
85 secondary: Option<f32>,
86 paint: &SliderPaint<'_>,
87 );
88}
89
90pub trait SliderTickMarkShape {
92 fn width(&self, track_height: f32) -> f32;
95
96 fn paint(
98 &self,
99 scene: &mut Scene,
100 centre: Point,
101 active: bool,
102 track_height: f32,
103 paint: &SliderPaint<'_>,
104 );
105}
106
107pub trait SliderThumbShape {
109 fn paint(&self, scene: &mut Scene, centre: Point, thumb: SliderThumb, paint: &SliderPaint<'_>);
111}
112
113pub trait SliderOverlayShape {
116 fn paint(
118 &self,
119 scene: &mut Scene,
120 centre: Point,
121 activation: f32,
122 color: Color,
123 paint: &SliderPaint<'_>,
124 );
125}
126
127pub trait SliderValueIndicatorShape {
129 fn paint(
131 &self,
132 scene: &mut Scene,
133 centre: Point,
134 text: String,
135 scale: f32,
136 paint: &SliderPaint<'_>,
137 );
138}
139
140pub struct SliderShape<T: ?Sized>(Rc<T>);
143
144impl<T: ?Sized> Clone for SliderShape<T> {
145 fn clone(&self) -> Self {
146 Self(self.0.clone())
147 }
148}
149
150impl<T: ?Sized> PartialEq for SliderShape<T> {
151 fn eq(&self, other: &Self) -> bool {
152 Rc::ptr_eq(&self.0, &other.0)
153 }
154}
155
156impl<T: ?Sized> fmt::Debug for SliderShape<T> {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.write_str("SliderShape(..)")
159 }
160}
161
162impl<T: ?Sized> std::ops::Deref for SliderShape<T> {
163 type Target = T;
164
165 fn deref(&self) -> &T {
166 &self.0
167 }
168}
169
170impl SliderShape<dyn SliderTrackShape> {
171 pub fn track(shape: impl SliderTrackShape + 'static) -> Self {
173 Self(Rc::new(shape))
174 }
175}
176
177impl SliderShape<dyn SliderTickMarkShape> {
178 pub fn tick_mark(shape: impl SliderTickMarkShape + 'static) -> Self {
180 Self(Rc::new(shape))
181 }
182}
183
184impl SliderShape<dyn SliderThumbShape> {
185 pub fn thumb(shape: impl SliderThumbShape + 'static) -> Self {
187 Self(Rc::new(shape))
188 }
189}
190
191impl SliderShape<dyn SliderOverlayShape> {
192 pub fn overlay(shape: impl SliderOverlayShape + 'static) -> Self {
194 Self(Rc::new(shape))
195 }
196}
197
198impl SliderShape<dyn SliderValueIndicatorShape> {
199 pub fn value_indicator(shape: impl SliderValueIndicatorShape + 'static) -> Self {
201 Self(Rc::new(shape))
202 }
203}
204
205pub(crate) fn pick<'a, T: ?Sized>(
207 widget: &'a Option<SliderShape<T>>,
208 theme: &'a Option<SliderShape<T>>,
209 fallback: &'a T,
210) -> &'a T {
211 widget
212 .as_ref()
213 .or(theme.as_ref())
214 .map_or(fallback, |shape| &**shape)
215}
216
217const ACTIVE_EXTRA: f32 = 2.0;
219
220#[derive(Clone, Copy, Debug, Default, PartialEq)]
225pub struct RoundedRectSliderTrackShape;
226
227impl SliderTrackShape for RoundedRectSliderTrackShape {
228 fn paint(
229 &self,
230 scene: &mut Scene,
231 track: Rect,
232 thumbs: &[f32],
233 secondary: Option<f32>,
234 paint: &SliderPaint<'_>,
235 ) {
236 let track_h = track.height;
237 let Some(&thumb) = thumbs.first() else {
238 return;
239 };
240 if track_h <= 0.0 {
241 return;
242 }
243 let o = paint.opacity;
244 let rtl = paint.is_rtl();
245 let palette = &paint.palette;
246 let half = track_h * 0.5;
247 let cy = track.y + half;
248 let active_half = (track_h + ACTIVE_EXTRA) * 0.5;
249 let (left, right) = (track.x, track.x + track.width);
250 let (a0, a1) = if rtl {
251 (thumb - half, right)
252 } else {
253 (left, thumb + half)
254 };
255 let (i0, i1) = if rtl {
256 (left, thumb + half)
257 } else {
258 (thumb - half, right)
259 };
260 if i1 - i0 > track_h {
261 scene.draw_rect(
262 Rect::new(i0, cy - half, i1 - i0, track_h),
263 palette.inactive_track.fade(o),
264 half,
265 0.0,
266 Color::TRANSPARENT,
267 );
268 }
269 if a1 - a0 > track_h {
270 scene.draw_rect(
271 Rect::new(a0, cy - active_half, a1 - a0, 2.0 * active_half),
272 palette.active_track.fade(o),
273 active_half,
274 0.0,
275 Color::TRANSPARENT,
276 );
277 }
278 if let Some(sx) = secondary {
280 let beyond = if rtl { sx < thumb } else { sx > thumb };
281 if beyond {
282 let (s0, s1) = if rtl { (sx, thumb) } else { (thumb, sx) };
283 let radius = if rtl {
284 BorderRadius {
285 top_left: half,
286 bottom_left: half,
287 ..BorderRadius::ZERO
288 }
289 } else {
290 BorderRadius {
291 top_right: half,
292 bottom_right: half,
293 ..BorderRadius::ZERO
294 }
295 };
296 scene.draw_rect(
297 Rect::new(s0, cy - half, s1 - s0, track_h),
298 palette.secondary_track.fade(o),
299 radius,
300 0.0,
301 Color::TRANSPARENT,
302 );
303 }
304 }
305 }
306}
307
308#[derive(Clone, Copy, Debug, Default, PartialEq)]
311pub struct RoundedRectRangeSliderTrackShape;
312
313impl SliderTrackShape for RoundedRectRangeSliderTrackShape {
314 fn paint(
315 &self,
316 scene: &mut Scene,
317 track: Rect,
318 thumbs: &[f32],
319 _secondary: Option<f32>,
320 paint: &SliderPaint<'_>,
321 ) {
322 let track_h = track.height;
323 let (Some(&x0), Some(&x1)) = (thumbs.first(), thumbs.get(1)) else {
324 return;
325 };
326 if track_h <= 0.0 {
327 return;
328 }
329 let o = paint.opacity;
330 let palette = &paint.palette;
331 let (lo, hi) = (x0.min(x1), x0.max(x1));
332 let half = track_h * 0.5;
333 let cy = track.y + half;
334 let active_half = (track_h + ACTIVE_EXTRA) * 0.5;
335 let (left, right) = (track.x, track.x + track.width);
336 for (a, b) in [(left, lo + half), (hi - half, right)] {
337 if b - a > track_h {
338 scene.draw_rect(
339 Rect::new(a, cy - half, b - a, track_h),
340 palette.inactive_track.fade(o),
341 half,
342 0.0,
343 Color::TRANSPARENT,
344 );
345 }
346 }
347 if hi - lo > 0.0 {
348 scene.draw_rect(
349 Rect::new(lo, cy - active_half, hi - lo, 2.0 * active_half),
350 palette.active_track.fade(o),
351 0.0,
352 0.0,
353 Color::TRANSPARENT,
354 );
355 }
356 }
357}
358
359#[derive(Clone, Copy, Debug, Default, PartialEq)]
362pub struct RoundSliderTickMarkShape {
363 pub radius: Option<f32>,
365}
366
367impl RoundSliderTickMarkShape {
368 fn radius_on(&self, track_height: f32) -> f32 {
369 self.radius.unwrap_or(track_height / 4.0)
370 }
371}
372
373impl SliderTickMarkShape for RoundSliderTickMarkShape {
374 fn width(&self, track_height: f32) -> f32 {
375 2.0 * self.radius_on(track_height)
376 }
377
378 fn paint(
379 &self,
380 scene: &mut Scene,
381 centre: Point,
382 active: bool,
383 track_height: f32,
384 paint: &SliderPaint<'_>,
385 ) {
386 let r = self.radius_on(track_height);
387 let color = if active {
388 paint.palette.active_tick_mark
389 } else {
390 paint.palette.inactive_tick_mark
391 };
392 scene.draw_rect(
393 Rect::new(centre.x - r, centre.y - r, 2.0 * r, 2.0 * r),
394 color.fade(paint.opacity),
395 r,
396 0.0,
397 Color::TRANSPARENT,
398 );
399 }
400}
401
402#[derive(Clone, Copy, Debug, PartialEq)]
406pub struct RoundSliderThumbShape {
407 pub radius: f32,
409}
410
411impl Default for RoundSliderThumbShape {
412 fn default() -> Self {
413 Self { radius: 10.0 }
414 }
415}
416
417impl SliderThumbShape for RoundSliderThumbShape {
418 fn paint(&self, scene: &mut Scene, centre: Point, thumb: SliderThumb, paint: &SliderPaint<'_>) {
419 let o = paint.opacity;
420 let r = self.radius;
421 let disc = Rect::new(centre.x - r, centre.y - r, 2.0 * r, 2.0 * r);
422 if thumb.elevation > 0.0 {
423 frus_core::paint_elevation(
425 scene,
426 disc,
427 BorderRadius::uniform(r),
428 thumb.elevation,
429 paint.theme.scheme.shadow.fade(o),
430 );
431 }
432 let (ring_width, ring) = if thumb.on_top {
433 (1.0, paint.theme.scheme.surface.fade(o))
434 } else if let Some(border) = paint.palette.thumb_border {
435 (2.0, border.fade(o))
436 } else {
437 (0.0, Color::TRANSPARENT)
438 };
439 scene.draw_rect(disc, paint.palette.thumb.fade(o), r, ring_width, ring);
440 }
441}
442
443#[derive(Clone, Copy, Debug, PartialEq)]
446pub struct RoundSliderOverlayShape {
447 pub radius: f32,
449}
450
451impl Default for RoundSliderOverlayShape {
452 fn default() -> Self {
453 Self { radius: 24.0 }
454 }
455}
456
457impl SliderOverlayShape for RoundSliderOverlayShape {
458 fn paint(
459 &self,
460 scene: &mut Scene,
461 centre: Point,
462 activation: f32,
463 color: Color,
464 paint: &SliderPaint<'_>,
465 ) {
466 let activation = activation.clamp(0.0, 1.0);
467 if activation <= 0.0 || color.a <= 0.0 {
468 return;
469 }
470 let r = self.radius * activation;
471 scene.draw_rect(
472 Rect::new(centre.x - r, centre.y - r, 2.0 * r, 2.0 * r),
473 color.fade(paint.opacity),
474 r,
475 0.0,
476 Color::TRANSPARENT,
477 );
478 }
479}
480
481#[derive(Clone, Copy, Debug, Default, PartialEq)]
484pub struct DropSliderValueIndicatorShape;
485
486impl SliderValueIndicatorShape for DropSliderValueIndicatorShape {
487 fn paint(
488 &self,
489 scene: &mut Scene,
490 centre: Point,
491 text: String,
492 scale: f32,
493 paint: &SliderPaint<'_>,
494 ) {
495 crate::slider::paint_drop(centre, text, paint.theme, scale, paint.opacity, scene);
496 }
497}
498
499#[derive(Clone, Copy, Debug, Default, PartialEq)]
502pub struct RectangularSliderValueIndicatorShape;
503
504impl SliderValueIndicatorShape for RectangularSliderValueIndicatorShape {
505 fn paint(
506 &self,
507 scene: &mut Scene,
508 centre: Point,
509 text: String,
510 scale: f32,
511 paint: &SliderPaint<'_>,
512 ) {
513 crate::slider::paint_rect_indicator(centre, text, paint.theme, scale, paint.opacity, scene);
514 }
515}
516
517#[derive(Clone, Copy, Debug, Default, PartialEq)]
520pub struct NoSliderShape;
521
522impl SliderTrackShape for NoSliderShape {
523 fn paint(&self, _: &mut Scene, _: Rect, _: &[f32], _: Option<f32>, _: &SliderPaint<'_>) {}
524}
525
526impl SliderTickMarkShape for NoSliderShape {
527 fn width(&self, _track_height: f32) -> f32 {
528 0.0
529 }
530
531 fn paint(&self, _: &mut Scene, _: Point, _: bool, _: f32, _: &SliderPaint<'_>) {}
532}
533
534impl SliderThumbShape for NoSliderShape {
535 fn paint(&self, _: &mut Scene, _: Point, _: SliderThumb, _: &SliderPaint<'_>) {}
536}
537
538impl SliderOverlayShape for NoSliderShape {
539 fn paint(&self, _: &mut Scene, _: Point, _: f32, _: Color, _: &SliderPaint<'_>) {}
540}
541
542impl SliderValueIndicatorShape for NoSliderShape {
543 fn paint(&self, _: &mut Scene, _: Point, _: String, _: f32, _: &SliderPaint<'_>) {}
544}
545
546pub(crate) static SLIDER_TRACK: RoundedRectSliderTrackShape = RoundedRectSliderTrackShape;
548pub(crate) static RANGE_TRACK: RoundedRectRangeSliderTrackShape = RoundedRectRangeSliderTrackShape;
549pub(crate) static TICK_MARK: RoundSliderTickMarkShape = RoundSliderTickMarkShape { radius: None };
550pub(crate) static THUMB: RoundSliderThumbShape = RoundSliderThumbShape { radius: 10.0 };
551pub(crate) static OVERLAY: RoundSliderOverlayShape = RoundSliderOverlayShape { radius: 24.0 };
552pub(crate) static DROP: DropSliderValueIndicatorShape = DropSliderValueIndicatorShape;
553pub(crate) static RECTANGULAR: RectangularSliderValueIndicatorShape =
554 RectangularSliderValueIndicatorShape;
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559 use crate::interaction::Status;
560 use crate::widget::Widget;
561 use crate::{RangeSlider, Slider};
562 use frus_core::Primitive;
563 use std::cell::RefCell;
564
565 const BOX: Rect = Rect::new(0.0, 0.0, 248.0, 48.0);
567
568 #[derive(Clone, Default)]
570 struct Log(Rc<RefCell<Vec<String>>>);
571
572 impl Log {
573 fn calls(&self) -> Vec<String> {
574 self.0.borrow().clone()
575 }
576
577 fn note(&self, line: String) {
578 self.0.borrow_mut().push(line);
579 }
580 }
581
582 impl SliderTrackShape for Log {
583 fn paint(
584 &self,
585 _: &mut Scene,
586 track: Rect,
587 thumbs: &[f32],
588 secondary: Option<f32>,
589 _: &SliderPaint<'_>,
590 ) {
591 self.note(format!("track {track:?} {thumbs:?} {secondary:?}"));
592 }
593 }
594
595 impl SliderTickMarkShape for Log {
596 fn width(&self, _: f32) -> f32 {
597 4.0
598 }
599
600 fn paint(&self, _: &mut Scene, centre: Point, active: bool, _: f32, _: &SliderPaint<'_>) {
601 self.note(format!("tick {} {active}", centre.x));
602 }
603 }
604
605 impl SliderThumbShape for Log {
606 fn paint(&self, _: &mut Scene, centre: Point, thumb: SliderThumb, _: &SliderPaint<'_>) {
607 self.note(format!(
608 "thumb {} {} {}",
609 centre.x, thumb.elevation, thumb.on_top
610 ));
611 }
612 }
613
614 impl SliderOverlayShape for Log {
615 fn paint(
616 &self,
617 _: &mut Scene,
618 centre: Point,
619 activation: f32,
620 _: Color,
621 _: &SliderPaint<'_>,
622 ) {
623 self.note(format!("overlay {} {activation}", centre.x));
624 }
625 }
626
627 impl SliderValueIndicatorShape for Log {
628 fn paint(
629 &self,
630 _: &mut Scene,
631 centre: Point,
632 text: String,
633 scale: f32,
634 _: &SliderPaint<'_>,
635 ) {
636 self.note(format!("indicator {} {text} {scale}", centre.x));
637 }
638 }
639
640 fn paint(widget: &dyn Widget<()>, status: Status, theme: &Theme) -> Vec<Primitive> {
641 let mut scene = Scene::new();
642 widget.paint(BOX, status, theme, &mut scene);
643 if widget.paints_over() {
644 widget.paint_over(BOX, status, theme, &mut scene);
645 }
646 scene.primitives().to_vec()
647 }
648
649 fn held() -> Status {
650 Status {
651 interaction: crate::interaction::Interaction::Pressed,
652 press_progress: 1.0,
653 ..Default::default()
654 }
655 }
656
657 #[test]
661 fn a_slider_paints_with_the_parts_it_names() {
662 let log = Log::default();
663 let slider = Slider::<()>::new(0.5)
664 .divisions(2)
665 .secondary_track_value(0.75)
666 .value_label(|v| format!("{v}"))
667 .on_change(|_| ())
668 .track_shape(log.clone())
669 .tick_mark_shape(log.clone())
670 .thumb_shape(log.clone())
671 .overlay_shape(log.clone())
672 .value_indicator_shape(log.clone());
673 let painted = paint(&slider, held(), &Theme::default());
674 assert!(painted.is_empty(), "nothing of the defaults: {painted:?}");
675 assert_eq!(
676 log.calls(),
677 vec![
678 "track Rect { x: 24.0, y: 22.0, width: 200.0, height: 4.0 } [124.0] Some(173.0)",
679 "overlay 124 1",
680 "tick 26 true",
681 "tick 124 true",
682 "tick 222 false",
683 "thumb 124 6 false",
684 "indicator 124 0.5 1",
685 ]
686 );
687 }
688
689 #[test]
692 fn the_theme_names_the_parts_for_each_slider() {
693 let for_slider = Log::default();
694 let for_range = Log::default();
695 let mine = Log::default();
696 let mut theme = Theme::default();
697 let t = &mut theme.widgets.slider;
698 t.thumb_shape = Some(SliderShape::thumb(for_slider.clone()));
699 t.range_thumb_shape = Some(SliderShape::thumb(for_range.clone()));
700 t.track_shape = Some(SliderShape::track(for_slider.clone()));
701 t.range_track_shape = Some(SliderShape::track(for_range.clone()));
702 t.value_indicator_shape = Some(SliderShape::value_indicator(for_slider.clone()));
703 t.range_value_indicator_shape = Some(SliderShape::value_indicator(for_range.clone()));
704
705 paint(&Slider::<()>::new(0.25), Status::default(), &theme);
706 assert_eq!(for_slider.calls().len(), 2, "{:?}", for_slider.calls());
707 assert!(for_range.calls().is_empty());
708
709 let range = RangeSlider::<()>::new(0.25, 0.75)
710 .value_label(|v| format!("{v}"))
711 .show_value_indicator(crate::ShowValueIndicator::AlwaysVisible);
712 paint(&range, Status::default(), &theme);
713 assert_eq!(
714 for_range.calls(),
715 vec![
716 "track Rect { x: 24.0, y: 22.0, width: 200.0, height: 4.0 } [74.0, 174.0] None",
717 "thumb 74 1 false",
718 "thumb 174 1 false",
719 "indicator 74 0.25 1",
720 "indicator 174 0.75 1",
721 ]
722 );
723 assert_eq!(for_slider.calls().len(), 2, "not the slider's indicator");
724
725 paint(
726 &Slider::<()>::new(0.25).thumb_shape(mine.clone()),
727 Status::default(),
728 &theme,
729 );
730 assert_eq!(mine.calls(), vec!["thumb 74 1 false"], "the widget's first");
731 assert_eq!(
732 for_slider.calls().len(),
733 3,
734 "only the theme's track this time"
735 );
736 }
737
738 #[test]
742 fn a_range_slider_paints_with_the_parts_it_names() {
743 let log = Log::default();
744 let range = RangeSlider::<()>::new(0.5, 0.6)
745 .divisions(10)
746 .value_label(|v| format!("{v}"))
747 .on_change(|_, _| ())
748 .track_shape(log.clone())
749 .tick_mark_shape(log.clone())
750 .thumb_shape(log.clone())
751 .overlay_shape(log.clone())
752 .value_indicator_shape(log.clone());
753 let painted = paint(&range, held(), &Theme::default());
754 assert!(painted.is_empty(), "nothing of the defaults: {painted:?}");
755 let calls = log.calls();
756 assert_eq!(
757 calls[0],
758 "track Rect { x: 24.0, y: 22.0, width: 200.0, height: 4.0 } [124.0, 143.6] None"
759 );
760 let ticks: Vec<_> = calls.iter().filter(|c| c.starts_with("tick")).collect();
761 assert_eq!(ticks.len(), 11);
762 assert_eq!(
763 ticks.iter().filter(|c| c.ends_with("true")).count(),
764 2,
765 "{ticks:?}"
766 );
767 let rest: Vec<_> = calls
768 .iter()
769 .filter(|c| !c.starts_with("tick") && !c.starts_with("track"))
770 .collect();
771 assert_eq!(
772 rest,
773 vec![
774 "overlay 143.6 1",
775 "thumb 124 1 false",
776 "thumb 143.6 6 true",
777 "indicator 124 0.5 1",
778 "indicator 143.6 0.6 1",
779 ]
780 );
781
782 let stop = &Widget::<()>::children(&range)[0].children()[0].children()[0];
784 let focused = Status {
785 focus_progress: 1.0,
786 ..Default::default()
787 };
788 let mut scene = Scene::new();
789 stop.paint(
790 Rect::new(100.0, 0.0, 48.0, 48.0),
791 focused,
792 &Theme::default(),
793 &mut scene,
794 );
795 assert!(scene.primitives().is_empty());
796 assert_eq!(
797 log.calls()[calls.len()..],
798 ["overlay 124 1", "indicator 124 0.5 1"]
799 );
800 }
801
802 #[test]
804 fn no_slider_shape_paints_nothing() {
805 let bare = Slider::<()>::new(0.5)
806 .divisions(4)
807 .value_label(|v| format!("{v}"))
808 .track_shape(NoSliderShape)
809 .tick_mark_shape(NoSliderShape)
810 .thumb_shape(NoSliderShape)
811 .overlay_shape(NoSliderShape)
812 .value_indicator_shape(NoSliderShape);
813 assert!(paint(&bare, held(), &Theme::default()).is_empty());
814 }
815
816 #[test]
819 fn the_reference_parts_take_their_sizes() {
820 let theme = Theme::default();
821 let crisp = |p: &[Primitive]| -> Vec<Rect> {
822 p.iter()
823 .filter_map(|p| match p {
824 Primitive::Rect { rect, blur, .. } if *blur == 0.0 => Some(*rect),
825 _ => None,
826 })
827 .collect()
828 };
829 let big = Slider::<()>::new(0.5)
830 .on_change(|_| ())
831 .thumb_shape(RoundSliderThumbShape { radius: 14.0 })
832 .overlay_shape(RoundSliderOverlayShape { radius: 30.0 });
833 let rects = crisp(&paint(&big, held(), &theme));
834 assert!(
835 rects.contains(&Rect::new(110.0, 10.0, 28.0, 28.0)),
836 "{rects:?}"
837 );
838 assert!(
839 rects.contains(&Rect::new(94.0, -6.0, 60.0, 60.0)),
840 "{rects:?}"
841 );
842
843 let dotted = Slider::<()>::new(0.5)
844 .divisions(2)
845 .tick_mark_shape(RoundSliderTickMarkShape { radius: Some(3.0) });
846 let rects = crisp(&paint(&dotted, Status::default(), &theme));
847 assert_eq!(
848 rects
849 .iter()
850 .filter(|r| r.width == 6.0 && r.height == 6.0)
851 .count(),
852 3,
853 "{rects:?}"
854 );
855
856 let boxed = Slider::<()>::new(0.5)
858 .divisions(2)
859 .value_label(|v| format!("{v}"))
860 .on_change(|_| ())
861 .value_indicator_shape(RectangularSliderValueIndicatorShape);
862 let indicator = theme
863 .scheme
864 .surface
865 .fade(0.90)
866 .lerp(theme.scheme.on_surface, 0.60);
867 let found = paint(&boxed, held(), &theme);
868 assert!(found
869 .iter()
870 .any(|p| matches!(p, Primitive::Rect { color, .. } if *color == indicator)));
871 }
872
873 #[test]
876 fn a_held_part_compares_by_identity() {
877 let one = SliderShape::thumb(RoundSliderThumbShape::default());
878 assert_eq!(one, one.clone());
879 assert_ne!(one, SliderShape::thumb(RoundSliderThumbShape::default()));
880 let mut theme = Theme::default();
881 theme.widgets.slider.thumb_shape = Some(one);
882 assert_eq!(theme.widgets.slider, theme.widgets.slider.clone());
883 assert_eq!(
884 format!("{:?}", SliderShape::overlay(NoSliderShape)),
885 "SliderShape(..)"
886 );
887 }
888
889 #[test]
891 fn a_transparent_halo_paints_nothing() {
892 let theme = Theme::default();
893 let paint = SliderPaint {
894 theme: &theme,
895 palette: SliderPalette {
896 active_track: Color::BLACK,
897 inactive_track: Color::BLACK,
898 secondary_track: Color::BLACK,
899 thumb: Color::BLACK,
900 thumb_border: None,
901 active_tick_mark: Color::BLACK,
902 inactive_tick_mark: Color::BLACK,
903 },
904 enabled: true,
905 discrete: false,
906 opacity: 1.0,
907 };
908 let mut scene = Scene::new();
909 let halo = RoundSliderOverlayShape::default();
910 halo.paint(
911 &mut scene,
912 Point::new(50.0, 24.0),
913 1.0,
914 Color::TRANSPARENT,
915 &paint,
916 );
917 halo.paint(
918 &mut scene,
919 Point::new(50.0, 24.0),
920 0.0,
921 Color::BLACK,
922 &paint,
923 );
924 assert!(scene.primitives().is_empty());
925 halo.paint(
926 &mut scene,
927 Point::new(50.0, 24.0),
928 0.5,
929 Color::BLACK,
930 &paint,
931 );
932 assert_eq!(scene.primitives().len(), 1);
933 }
934
935 #[test]
938 fn crowded_ticks_are_left_out() {
939 let ticks = |n: usize| {
940 let log = Log::default();
941 let slider = Slider::<()>::new(0.5)
942 .divisions(n)
943 .tick_mark_shape(log.clone());
944 paint(&slider, Status::default(), &Theme::default());
945 log.calls().len()
946 };
947 assert_eq!(ticks(16), 17);
949 assert_eq!(ticks(17), 0);
950 }
951}