zng_wgt_slider/
lib.rs

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