zng_wgt_slider/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! Widget for selecting a value or range by dragging a selector thumb.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12zng_wgt::enable_widget_macros!();
13
14pub mod thumb;
15
16use std::{any::Any, fmt, ops::Range, sync::Arc};
17
18use colors::ACCENT_COLOR_VAR;
19use parking_lot::Mutex;
20use zng_var::{AnyVar, AnyVarValue, BoxedAnyVar};
21use zng_wgt::prelude::*;
22use zng_wgt_input::{focus::FocusableMix, pointer_capture::capture_pointer};
23use zng_wgt_style::{Style, StyleMix, impl_style_fn, style_fn};
24
25/// Value selector from a range of values.
26#[widget($crate::Slider)]
27pub struct Slider(FocusableMix<StyleMix<WidgetBase>>);
28impl Slider {
29    fn widget_intrinsic(&mut self) {
30        self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
31
32        widget_set! {
33            self;
34            style_base_fn = style_fn!(|_| DefaultStyle!());
35            capture_pointer = true;
36        }
37    }
38}
39impl_style_fn!(Slider);
40
41/// Default slider style.
42#[widget($crate::DefaultStyle)]
43pub struct DefaultStyle(Style);
44impl DefaultStyle {
45    fn widget_intrinsic(&mut self) {
46        widget_set! {
47            self;
48            zng_wgt_container::child = SliderTrack! {
49                zng_wgt::corner_radius = 5;
50                zng_wgt_fill::background_color = ACCENT_COLOR_VAR.rgba();
51                zng_wgt::margin = 8; // thumb overflow
52
53                when #{SLIDER_DIRECTION_VAR}.is_horizontal() {
54                    zng_wgt_size_offset::height = 5;
55                }
56                when #{SLIDER_DIRECTION_VAR}.is_vertical() {
57                    zng_wgt_size_offset::width = 5;
58                }
59            };
60            zng_wgt_container::child_align = Align::FILL_X;
61        }
62    }
63}
64
65trait SelectorImpl: Send {
66    fn selection(&self) -> BoxedAnyVar;
67    fn set(&mut self, nearest: Factor, to: Factor);
68    fn thumbs(&self) -> Vec<ThumbValue>;
69    fn to_offset(&self, t: &dyn AnyVarValue) -> Option<Factor>;
70    #[allow(clippy::wrong_self_convention)]
71    fn from_offset(&self, offset: Factor) -> Box<dyn Any>;
72}
73
74trait OffsetConvert<T>: Send {
75    fn to(&self, t: &T) -> Factor;
76    fn from(&self, f: Factor) -> T;
77}
78impl<T, Tf: Fn(&T) -> Factor + Send, Ff: Fn(Factor) -> T + Send> OffsetConvert<T> for (Tf, Ff) {
79    fn to(&self, t: &T) -> Factor {
80        (self.0)(t)
81    }
82
83    fn from(&self, f: Factor) -> T {
84        (self.1)(f)
85    }
86}
87
88/// Represents a type that can auto implement a [`Selector`].
89///
90/// # Implementing
91///
92/// This trait is implemented for all primitive type and Zng layout types, if a type does not you
93/// can declare custom conversions using [`Selector::value`].
94pub trait SelectorValue: VarValue {
95    /// Make the selector.
96    fn to_selector(value: BoxedVar<Self>, min: Self, max: Self) -> Selector;
97}
98
99/// Defines the values and ranges selected by a slider.
100///
101/// Selectors are set on the [`selector`](fn@selector) property.
102#[derive(Clone)]
103pub struct Selector(Arc<Mutex<dyn SelectorImpl>>);
104impl fmt::Debug for Selector {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(f, "Selector(_)")
107    }
108}
109impl PartialEq for Selector {
110    fn eq(&self, other: &Self) -> bool {
111        Arc::ptr_eq(&self.0, &other.0)
112    }
113}
114impl Selector {
115    /// New with single value thumb of type `T` that can be set any value in the `min..=max` range.
116    pub fn value<T: SelectorValue>(selection: impl IntoVar<T>, min: T, max: T) -> Self {
117        T::to_selector(selection.into_var().boxed(), min, max)
118    }
119
120    /// New with a single value thumb of type `T`.
121    ///
122    /// The value must convert to a normalized factor `[0.fct()..=1.fct()]` where `0.fct()` is the minimum possible value and `1.fct()` is the maximum
123    /// possible value. If a value outside of this range is returned it is clamped to the range and the `selection` variable is updated back.
124    pub fn value_with<T>(
125        selection: impl IntoVar<T>,
126        to_offset: impl Fn(&T) -> Factor + Send + 'static,
127        from_offset: impl Fn(Factor) -> T + Send + 'static,
128    ) -> Self
129    where
130        T: VarValue,
131    {
132        struct SingleImpl<T> {
133            selection: BoxedVar<T>,
134            selection_fct: Factor,
135            to_from: Box<dyn OffsetConvert<T>>,
136        }
137        impl<T: VarValue> SelectorImpl for SingleImpl<T> {
138            fn selection(&self) -> BoxedAnyVar {
139                self.selection.clone_any()
140            }
141
142            fn set(&mut self, _: Factor, to: Factor) {
143                self.selection_fct = to;
144                let _ = self.selection.set(self.to_from.from(to));
145            }
146
147            fn thumbs(&self) -> Vec<ThumbValue> {
148                vec![ThumbValue {
149                    offset: self.selection_fct,
150                    n_of: (0, 0),
151                }]
152            }
153
154            fn to_offset(&self, t: &dyn AnyVarValue) -> Option<Factor> {
155                let f = self.to_from.to(t.as_any().downcast_ref::<T>()?);
156                Some(f)
157            }
158
159            fn from_offset(&self, offset: Factor) -> Box<dyn Any> {
160                Box::new(self.to_from.from(offset))
161            }
162        }
163        let selection = selection.into_var();
164        Self(Arc::new(Mutex::new(SingleImpl {
165            selection_fct: selection.with(&to_offset),
166            selection: selection.boxed(),
167            to_from: Box::new((to_offset, from_offset)),
168        })))
169    }
170
171    /// New with two value thumbs of type `T` that can be set any value in the `min..=max` range.
172    pub fn range<T: SelectorValue>(range: impl IntoVar<std::ops::Range<T>>, min: T, max: T) -> Self {
173        // create a selector just to get the conversion closures
174        let convert = T::to_selector(zng_var::LocalVar(min.clone()).boxed(), min, max);
175        Self::range_with(range.into_var(), clmv!(convert, |t| convert.to_offset(t).unwrap()), move |f| {
176            convert.from_offset(f).unwrap()
177        })
178    }
179
180    /// New with two values thumbs that define a range of type `T`.
181    ///
182    /// The conversion closure have the same constraints as [`value_with`].
183    ///
184    /// [`value_with`]: Self::value_with
185    pub fn range_with<T>(
186        range: impl IntoVar<std::ops::Range<T>>,
187        to_offset: impl Fn(&T) -> Factor + Send + 'static,
188        from_offset: impl Fn(Factor) -> T + Send + 'static,
189    ) -> Self
190    where
191        T: VarValue,
192    {
193        struct RangeImpl<T> {
194            selection: BoxedVar<Range<T>>,
195            selection_fct: [Factor; 2],
196            to_from: Box<dyn OffsetConvert<T>>,
197        }
198        impl<T: VarValue> SelectorImpl for RangeImpl<T> {
199            fn selection(&self) -> BoxedAnyVar {
200                self.selection.clone_any()
201            }
202
203            fn set(&mut self, nearest: Factor, to: Factor) {
204                if (self.selection_fct[0] - nearest).abs() < (self.selection_fct[1] - nearest).abs() {
205                    self.selection_fct[0] = to;
206                } else {
207                    self.selection_fct[1] = to;
208                }
209                if self.selection_fct[0] > self.selection_fct[1] {
210                    self.selection_fct.swap(0, 1);
211                }
212                let start = self.to_from.from(self.selection_fct[0]);
213                let end = self.to_from.from(self.selection_fct[1]);
214                let _ = self.selection.set(start..end);
215            }
216
217            fn thumbs(&self) -> Vec<ThumbValue> {
218                vec![
219                    ThumbValue {
220                        offset: self.selection_fct[0],
221                        n_of: (0, 2),
222                    },
223                    ThumbValue {
224                        offset: self.selection_fct[1],
225                        n_of: (1, 2),
226                    },
227                ]
228            }
229
230            fn to_offset(&self, t: &dyn AnyVarValue) -> Option<Factor> {
231                let f = self.to_from.to(t.as_any().downcast_ref::<T>()?);
232                Some(f)
233            }
234
235            fn from_offset(&self, offset: Factor) -> Box<dyn Any> {
236                Box::new(self.to_from.from(offset))
237            }
238        }
239        let selection = range.into_var();
240
241        Self(Arc::new(Mutex::new(RangeImpl {
242            selection_fct: selection.with(|r| [to_offset(&r.start), to_offset(&r.end)]),
243            selection: selection.boxed(),
244            to_from: Box::new((to_offset, from_offset)),
245        })))
246    }
247
248    /// New with many value thumbs of type `T` that can be set any value in the `min..=max` range.
249    pub fn many<T: SelectorValue>(many: impl IntoVar<Vec<T>>, min: T, max: T) -> Self {
250        // create a selector just to get the conversion closures
251        let convert = T::to_selector(zng_var::LocalVar(min.clone()).boxed(), min, max);
252        Self::many_with(many.into_var(), clmv!(convert, |t| convert.to_offset(t).unwrap()), move |f| {
253            convert.from_offset(f).unwrap()
254        })
255    }
256
257    /// New with many value thumbs of type `T`.
258    ///
259    /// The conversion closure have the same constraints as [`value_with`].
260    ///
261    /// [`value_with`]: Self::value_with
262    pub fn many_with<T>(
263        many: impl IntoVar<Vec<T>>,
264        to_offset: impl Fn(&T) -> Factor + Send + 'static,
265        from_offset: impl Fn(Factor) -> T + Send + 'static,
266    ) -> Self
267    where
268        T: VarValue,
269    {
270        struct ManyImpl<T> {
271            selection: BoxedVar<Vec<T>>,
272            selection_fct: Vec<Factor>,
273            to_from: Box<dyn OffsetConvert<T>>,
274        }
275        impl<T: VarValue> SelectorImpl for ManyImpl<T> {
276            fn selection(&self) -> BoxedAnyVar {
277                self.selection.clone_any()
278            }
279
280            fn set(&mut self, nearest: Factor, to: Factor) {
281                if let Some((i, _)) = self
282                    .selection_fct
283                    .iter()
284                    .enumerate()
285                    .map(|(i, &f)| (i, (f - nearest).abs()))
286                    .reduce(|a, b| if a.1 < b.1 { a } else { b })
287                {
288                    self.selection_fct[i] = to;
289                    self.selection_fct.sort_by(|a, b| a.0.total_cmp(&b.0));
290                    let s: Vec<_> = self.selection_fct.iter().map(|&f| self.to_from.from(f)).collect();
291                    let _ = self.selection.set(s);
292                }
293            }
294
295            fn thumbs(&self) -> Vec<ThumbValue> {
296                let len = self.selection_fct.len().min(u16::MAX as usize) as u16;
297                self.selection_fct
298                    .iter()
299                    .enumerate()
300                    .map(|(i, &f)| ThumbValue {
301                        offset: f,
302                        n_of: (i.min(u16::MAX as usize) as u16, len),
303                    })
304                    .collect()
305            }
306
307            fn to_offset(&self, t: &dyn AnyVarValue) -> Option<Factor> {
308                let f = self.to_from.to(t.as_any().downcast_ref::<T>()?);
309                Some(f)
310            }
311
312            fn from_offset(&self, offset: Factor) -> Box<dyn Any> {
313                Box::new(self.to_from.from(offset))
314            }
315        }
316        let selection = many.into_var();
317        Self(Arc::new(Mutex::new(ManyImpl {
318            selection_fct: selection.with(|m| m.iter().map(&to_offset).collect()),
319            selection: selection.boxed(),
320            to_from: Box::new((to_offset, from_offset)),
321        })))
322    }
323
324    /// New with no value thumb.
325    pub fn nil() -> Self {
326        Self::many_with(vec![], |_: &bool| 0.fct(), |_| false)
327    }
328
329    /// Convert the value to a normalized factor.
330    ///
331    /// If `T` is not the same type returns `None`.
332    pub fn to_offset<T: VarValue>(&self, t: &T) -> Option<Factor> {
333        self.0.lock().to_offset(t)
334    }
335
336    /// Convert the normalized factor to a value `T`.
337    ///
338    /// If `T` is not the same type returns `None`.
339    pub fn from_offset<T: VarValue>(&self, offset: impl IntoValue<Factor>) -> Option<T> {
340        let b = self.0.lock().from_offset(offset.into()).downcast().ok()?;
341        Some(*b)
342    }
343
344    /// Gets the value thumbs.
345    pub fn thumbs(&self) -> Vec<ThumbValue> {
346        self.0.lock().thumbs()
347    }
348
349    /// Move the `nearest_thumb` to a new offset.
350    ///
351    /// Note that ranges don't invert, this operation may swap the thumb roles.
352    pub fn set(&self, nearest_thumb: impl IntoValue<Factor>, to: impl IntoValue<Factor>) {
353        self.0.lock().set(nearest_thumb.into(), to.into())
354    }
355
356    /// The selection var.
357    ///
358    /// Downcast to `T` or `Range<T>` to get and set the value.
359    pub fn selection(&self) -> BoxedAnyVar {
360        self.0.lock().selection()
361    }
362}
363
364/// Represents a selector thumb in a slider.
365#[derive(Clone, Debug, PartialEq, Copy)]
366pub struct ThumbValue {
367    offset: Factor,
368    n_of: (u16, u16),
369}
370impl ThumbValue {
371    /// Thumb offset.
372    pub fn offset(&self) -> Factor {
373        self.offset
374    }
375
376    /// Thumb position among others.
377    ///
378    /// In a single value this is `(0, 1)`, in a range this is `(0, 2)` for the start thumb and `(1, 2)` for the end thumb.
379    pub fn n_of(&self) -> (u16, u16) {
380        self.n_of
381    }
382
383    /// Is first thumb (smallest offset).
384    pub fn is_first(&self) -> bool {
385        self.n_of.0 == 0
386    }
387
388    /// Is last thumb (largest offset).
389    pub fn is_last(&self) -> bool {
390        self.n_of.0 == self.n_of.1
391    }
392}
393
394context_local! {
395    /// Contextual [`Selector`].
396    pub static SELECTOR: Selector = Selector::nil();
397}
398context_var! {
399    /// Contextual thumb function.
400    pub static THUMB_FN_VAR: WidgetFn<ThumbArgs> = wgt_fn!(|a: ThumbArgs| thumb::Thumb!(a.thumb()));
401}
402
403/// Sets the slider selector that defines the values, ranges that are selected.
404#[property(CONTEXT, default(Selector::nil()), widget_impl(Slider))]
405pub fn selector(child: impl UiNode, selector: impl IntoValue<Selector>) -> impl UiNode {
406    with_context_local(child, &SELECTOR, selector)
407}
408
409/// Widget function that converts [`ThumbArgs`] to widgets.
410#[property(CONTEXT, default(THUMB_FN_VAR))]
411pub fn thumb_fn(child: impl UiNode, thumb: impl IntoVar<WidgetFn<ThumbArgs>>) -> impl UiNode {
412    with_context_var(child, THUMB_FN_VAR, thumb)
413}
414
415/// Arguments for a slider thumb widget generator.
416pub struct ThumbArgs {
417    thumb: ArcVar<ThumbValue>,
418}
419impl ThumbArgs {
420    /// Variable with the thumb value that must be represented by the widget.
421    pub fn thumb(&self) -> ReadOnlyArcVar<ThumbValue> {
422        self.thumb.read_only()
423    }
424}
425
426/// Slider extension methods for widget info.
427pub trait WidgetInfoExt {
428    /// Widget inner bounds define the slider range length.
429    fn is_slider_track(&self) -> bool;
430
431    /// Find the nearest ancestor that is a slider track.
432    fn slider_track(&self) -> Option<WidgetInfo>;
433}
434impl WidgetInfoExt for WidgetInfo {
435    fn is_slider_track(&self) -> bool {
436        self.meta().flagged(*IS_SLIDER_ID)
437    }
438
439    fn slider_track(&self) -> Option<WidgetInfo> {
440        self.self_and_ancestors().find(|w| w.is_slider_track())
441    }
442}
443
444static_id! {
445    static ref IS_SLIDER_ID: StateId<()>;
446}
447
448/// Slider orientation and direction.
449#[derive(Clone, Copy, PartialEq, Eq)]
450pub enum SliderDirection {
451    /// Horizontal. Minimum at start, maximum at end.
452    ///
453    /// Start is left in LTR contexts and right in RTL contexts.
454    StartToEnd,
455    /// Horizontal. Minimum at end, maximum at start.
456    ///
457    /// Start is left in LTR contexts and right in RTL contexts.
458    EndToStart,
459    /// Horizontal. Minimum at left, maximum at right.
460    LeftToRight,
461    /// Horizontal. Minimum at right, maximum at left.
462    RightToLeft,
463    /// Vertical. Minimum at bottom, maximum at top.
464    BottomToTop,
465    /// Vertical. Minimum at top, maximum at bottom.
466    TopToBottom,
467}
468impl SliderDirection {
469    /// Slider track is vertical.
470    pub fn is_vertical(&self) -> bool {
471        matches!(self, Self::BottomToTop | Self::TopToBottom)
472    }
473
474    /// Slider track is horizontal.
475    pub fn is_horizontal(&self) -> bool {
476        !self.is_vertical()
477    }
478
479    /// Convert start/end to left/right in the given `direction` context.
480    pub fn layout(&self, direction: LayoutDirection) -> Self {
481        match *self {
482            SliderDirection::StartToEnd => {
483                if direction.is_ltr() {
484                    Self::LeftToRight
485                } else {
486                    Self::RightToLeft
487                }
488            }
489            SliderDirection::EndToStart => {
490                if direction.is_ltr() {
491                    Self::RightToLeft
492                } else {
493                    Self::LeftToRight
494                }
495            }
496            s => s,
497        }
498    }
499}
500impl fmt::Debug for SliderDirection {
501    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
502        if f.alternate() {
503            write!(f, "SliderDirection::")?;
504        }
505        match self {
506            Self::StartToEnd => write!(f, "StartToEnd"),
507            Self::EndToStart => write!(f, "EndToStart"),
508            Self::LeftToRight => write!(f, "LeftToRight"),
509            Self::RightToLeft => write!(f, "RightToLeft"),
510            Self::BottomToTop => write!(f, "BottomToTop"),
511            Self::TopToBottom => write!(f, "TopToBottom"),
512        }
513    }
514}
515
516context_var! {
517    /// Orientation and direction of the parent slider.
518    pub static SLIDER_DIRECTION_VAR: SliderDirection = SliderDirection::StartToEnd;
519}
520
521/// Defines the orientation and direction of the slider track.
522///
523/// This property sets the [`SLIDER_DIRECTION_VAR`].
524#[property(CONTEXT, default(SLIDER_DIRECTION_VAR), widget_impl(Slider))]
525fn direction(child: impl UiNode, direction: impl IntoVar<SliderDirection>) -> impl UiNode {
526    with_context_var(child, SLIDER_DIRECTION_VAR, direction)
527}
528
529/// Slider track container widget.
530///
531/// The slider track widget is an special container that generates thumb widgets for the slider. The widget
532/// inner bounds define the track area/range.
533#[widget($crate::SliderTrack)]
534pub struct SliderTrack(WidgetBase);
535impl SliderTrack {
536    fn widget_intrinsic(&mut self) {
537        self.widget_builder().push_build_action(|wgt| {
538            wgt.set_child(slider_track_node());
539        })
540    }
541}
542
543fn slider_track_node() -> impl UiNode {
544    let mut thumbs = ui_vec![];
545    let mut thumb_vars = vec![];
546    match_node_leaf(move |op| match op {
547        UiNodeOp::Init => {
548            WIDGET.sub_var(&THUMB_FN_VAR);
549
550            thumb_vars = SELECTOR.get().thumbs().into_iter().map(zng_var::var).collect();
551            thumbs.reserve(thumb_vars.len());
552
553            let thumb_fn = THUMB_FN_VAR.get();
554            for v in &thumb_vars {
555                thumbs.push(thumb_fn(ThumbArgs { thumb: v.clone() }))
556            }
557
558            thumbs.init_all();
559        }
560        UiNodeOp::Deinit => {
561            thumbs.deinit_all();
562            thumbs = ui_vec![];
563            thumb_vars = vec![];
564        }
565        UiNodeOp::Info { info } => {
566            info.flag_meta(*IS_SLIDER_ID);
567            thumbs.info_all(info);
568        }
569        UiNodeOp::Measure { desired_size, .. } => {
570            *desired_size = LAYOUT.constraints().fill_size();
571        }
572        UiNodeOp::Layout { final_size, wl } => {
573            *final_size = LAYOUT.constraints().fill_size();
574            let _ = thumbs.layout_each(wl, |_, n, wl| n.layout(wl), |_, _| PxSize::zero());
575        }
576        UiNodeOp::Update { updates } => {
577            if let Some(thumb_fn) = THUMB_FN_VAR.get_new() {
578                thumbs.deinit_all();
579                thumb_vars.clear();
580                thumbs.clear();
581
582                for value in SELECTOR.get().thumbs() {
583                    let var = zng_var::var(value);
584                    let thumb = thumb_fn(ThumbArgs { thumb: var.clone() });
585                    thumb_vars.push(var);
586                    thumbs.push(thumb);
587                }
588
589                thumbs.init_all();
590
591                WIDGET.update_info().layout().render();
592            } else {
593                thumbs.update_all(updates, &mut ());
594
595                // sync views and vars with updated SELECTOR thumbs
596
597                let mut thumb_values = SELECTOR.get().thumbs();
598                match thumb_values.len().cmp(&thumb_vars.len()) {
599                    std::cmp::Ordering::Less => {
600                        // now has less thumbs
601                        for mut drop in thumbs.drain(thumb_values.len()..) {
602                            drop.deinit();
603                        }
604                        thumb_vars.truncate(thumbs.len());
605                    }
606                    std::cmp::Ordering::Greater => {
607                        // now has more thumbs
608                        let thumb_fn = THUMB_FN_VAR.get();
609                        for value in thumb_values.drain(thumbs.len()..) {
610                            let var = zng_var::var(value);
611                            let mut thumb = thumb_fn(ThumbArgs { thumb: var.clone() });
612                            thumb.init();
613                            thumb_vars.push(var);
614                            thumbs.push(thumb);
615                        }
616                    }
617                    std::cmp::Ordering::Equal => {}
618                }
619
620                // reuse thumbs
621                for (var, value) in thumb_vars.iter().zip(thumb_values) {
622                    var.set(value);
623                }
624            }
625        }
626        op => thumbs.op(op),
627    })
628}
629
630macro_rules! impl_32 {
631    (($to_f32:expr, $from_f32:expr) => $($T:ident),+ $(,)?) => {
632        $(
633            impl SelectorValue for $T {
634                #[allow(clippy::unnecessary_cast)]
635                fn to_selector(value: BoxedVar<Self>, min: Self, max: Self) -> Selector {
636                    let to_f32 = $to_f32;
637                    let from_f32 = $from_f32;
638
639                    let min = to_f32(min);
640                    let max = to_f32(max);
641                    if min >= max {
642                        Selector::nil()
643                    } else {
644                        let d = max - min;
645                        Selector::value_with(value, move |i| {
646                            let i = to_f32(i.clone());
647                            ((i - min)  / d).fct()
648                        }, move |f| {
649                            from_f32((f.0 * d + min).round())
650                        })
651                    }
652                }
653            }
654        )+
655    };
656}
657impl_32!((|i| i as f32, |f| f as Self) => u32, i32, u16, i16, u8, i8, f32);
658impl_32!((|p: Self| p.0 as f32, |f| Self(f as _)) => Px, Factor, FactorPercent);
659impl_32!((|p: Dip| p.to_f32(), |f| Dip::new_f32(f)) => Dip);
660
661macro_rules! impl_64 {
662    ($($T:ident),+ $(,)?) => {
663        $(
664            impl SelectorValue for $T {
665                fn to_selector(value: BoxedVar<Self>, min: Self, max: Self) -> Selector {
666                    let min = min as f64;
667                    let max = max as f64;
668                    if min >= max {
669                        Selector::nil()
670                    } else {
671                        let d = max - min;
672                        Selector::value_with(value, move |&i| {
673                            let i = i as f64;
674                            Factor(((i - min)  / d) as f32)
675                        }, move |f| {
676                            ((f.0 as f64) * d + min).round() as Self
677                        })
678                    }
679                }
680            }
681        )+
682    };
683}
684impl_64!(u64, i64, u128, i128, f64);
685
686impl SelectorValue for Length {
687    fn to_selector(value: BoxedVar<Self>, min: Self, max: Self) -> Selector {
688        let (min_f32, max_f32) = LAYOUT.with_context(LayoutMetrics::new(1.fct(), PxSize::splat(Px(1000)), Px(16)), || {
689            (min.layout_f32(LayoutAxis::X), max.layout_f32(LayoutAxis::X))
690        });
691        if min_f32 >= max_f32 {
692            Selector::nil()
693        } else {
694            let d_f32 = max_f32 - min_f32;
695            let d = max - min.clone();
696            Selector::value_with(
697                value,
698                move |l| {
699                    let l = LAYOUT.with_context(LayoutMetrics::new(1.fct(), PxSize::splat(Px(1000)), Px(16)), || {
700                        l.layout_f32(LayoutAxis::X)
701                    });
702                    Factor((l - min_f32) / d_f32)
703                },
704                move |f| d.clone() * f + min.clone(),
705            )
706        }
707    }
708}
709
710#[cfg(test)]
711mod tests {
712    use super::*;
713
714    fn selector_value_t<T: SelectorValue>(min: T, max: T) {
715        let s = Selector::value(min.clone(), min.clone(), max.clone());
716        assert_eq!(s.to_offset(&min), Some(0.fct()));
717        assert_eq!(s.to_offset(&max), Some(1.fct()));
718        assert_eq!(s.from_offset(0.fct()), Some(min));
719        assert_eq!(s.from_offset(1.fct()), Some(max));
720    }
721
722    #[test]
723    fn selector_value_u8() {
724        selector_value_t(u8::MIN, u8::MAX);
725        selector_value_t(20u8, 120u8);
726    }
727
728    #[test]
729    fn selector_value_i32() {
730        selector_value_t(i32::MIN, i32::MAX);
731        selector_value_t(20i32, 120i32);
732    }
733
734    #[test]
735    fn selector_value_i64() {
736        selector_value_t(i64::MIN, i64::MAX);
737    }
738
739    #[test]
740    fn selector_value_f64() {
741        selector_value_t(-200f64, 200f64);
742        selector_value_t(20f64, 120f64);
743    }
744
745    #[test]
746    fn selector_pct() {
747        selector_value_t(0.pct(), 100.pct());
748    }
749
750    #[test]
751    fn selector_value_px() {
752        selector_value_t(Px(20), Px(200));
753    }
754
755    #[test]
756    fn selector_value_dip() {
757        selector_value_t(Dip::new(20), Dip::new(200));
758    }
759
760    #[test]
761    fn selector_value_length() {
762        selector_value_t(20.px(), 200.px());
763    }
764
765    #[test]
766    fn selector_value_set() {
767        let s = Selector::value(10u8, 0, 100);
768        s.set(10.pct(), 20.pct());
769        assert_eq!(s.thumbs()[0].offset, 0.2.fct());
770    }
771
772    #[test]
773    fn selector_range_set() {
774        let s = Selector::range(10u8..20u8, 0, 100);
775        // less then first
776        s.set(0.pct(), 5.pct());
777        assert_eq!(s.thumbs()[0].offset, 0.05.fct());
778        assert_eq!(s.thumbs()[1].offset, 0.2.fct());
779
780        // more then last
781        s.set(25.pct(), 30.pct());
782        assert_eq!(s.thumbs()[0].offset, 0.05.fct());
783        assert_eq!(s.thumbs()[1].offset, 0.3.fct());
784
785        // nearest first
786        s.set(6.pct(), 7.pct());
787        assert_eq!(s.thumbs()[0].offset, 0.07.fct());
788        assert_eq!(s.thumbs()[1].offset, 0.3.fct());
789
790        // invert
791        s.set(7.pct(), 40.pct());
792        assert_eq!(s.thumbs()[0].offset, 0.3.fct());
793        assert_eq!(s.thumbs()[1].offset, 0.4.fct());
794    }
795
796    #[test]
797    fn selector_range_set_eq() {
798        let s = Selector::range(10u8..10, 0, 100);
799        assert_eq!(s.thumbs()[0].offset, 0.1.fct());
800        assert_eq!(s.thumbs()[1].offset, 0.1.fct());
801
802        // only the last must move
803        s.set(10.pct(), 20.pct());
804        assert_eq!(s.thumbs()[0].offset, 0.1.fct());
805        assert_eq!(s.thumbs()[1].offset, 0.2.fct());
806
807        let s = Selector::range(10u8..10, 0, 100);
808        s.set(5.pct(), 5.pct());
809        assert_eq!(s.thumbs()[0].offset, 0.05.fct());
810        assert_eq!(s.thumbs()[1].offset, 0.1.fct());
811    }
812}