zng_wgt_slider/
thumb.rs

1//! Slider thumb widget.
2
3use zng_wgt::prelude::*;
4use zng_wgt_input::{focus::FocusableMix, pointer_capture::capture_pointer};
5use zng_wgt_style::{Style, StyleMix, impl_style_fn};
6
7use crate::{SLIDER_DIRECTION_VAR, SliderDirection, ThumbValue};
8
9/// Slider thumb widget.
10#[widget($crate::thumb::Thumb {
11    ($value:expr) => {
12        value = $value;
13    }
14})]
15pub struct Thumb(FocusableMix<StyleMix<WidgetBase>>);
16impl Thumb {
17    fn widget_intrinsic(&mut self) {
18        self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
19
20        self.widget_builder()
21            .push_build_action(|wgt| match wgt.capture_var::<ThumbValue>(property_id!(Self::value)) {
22                Some(v) => {
23                    wgt.push_intrinsic(NestGroup::LAYOUT, "event-layout", move |c| thumb_event_layout_node(c, v));
24                }
25                None => tracing::error!("missing required `slider::Thumb::value` property"),
26            });
27
28        widget_set! {
29            self;
30            // this to enable visual feedback on thumb (is_cap_hovered)
31            // the SliderTrack also captures the subtree
32            capture_pointer = true;
33        }
34    }
35}
36impl_style_fn!(Thumb, DefaultStyle);
37
38/// Default slider style.
39#[widget($crate::thumb::DefaultStyle)]
40pub struct DefaultStyle(Style);
41impl DefaultStyle {
42    fn widget_intrinsic(&mut self) {
43        widget_set! {
44            self;
45            zng_wgt::border = 3, LightDark::new(colors::BLACK, colors::WHITE).rgba_into();
46            zng_wgt_size_offset::force_size = 10 + 3 + 3;
47            zng_wgt::corner_radius = 16;
48            zng_wgt_fill::background_color = colors::ACCENT_COLOR_VAR.rgba();
49
50            when #{crate::SLIDER_DIRECTION_VAR}.is_horizontal() {
51                zng_wgt_size_offset::offset = (-3 - 10 / 2, -3 - 5 / 2); // track is 5 height
52            }
53            when #{crate::SLIDER_DIRECTION_VAR}.is_vertical() {
54                zng_wgt_size_offset::offset = (-3 - 5 / 2, -3 - 10 / 2);
55            }
56
57            #[easing(150.ms())]
58            zng_wgt_transform::scale = 100.pct();
59            when *#zng_wgt_input::is_cap_hovered {
60                #[easing(0.ms())]
61                zng_wgt_transform::scale = 120.pct();
62            }
63        }
64    }
65}
66
67/// Value represented by the thumb.
68#[property(CONTEXT, widget_impl(Thumb))]
69pub fn value(wgt: &mut WidgetBuilding, value: impl IntoVar<ThumbValue>) {
70    let _ = value;
71    wgt.expect_property_capture();
72}
73
74/// Main thumb implementation.
75///
76/// Handles mouse and touch drag, applies the thumb offset as translation on layout.
77fn thumb_event_layout_node(child: impl IntoUiNode, value: impl IntoVar<ThumbValue>) -> UiNode {
78    let value = value.into_var();
79    match_node(child, move |c, op| match op {
80        UiNodeOp::Init => {
81            WIDGET.sub_var_layout(&value);
82        }
83        UiNodeOp::Layout { wl, final_size } => {
84            *final_size = c.layout(wl);
85            let layout_direction = LAYOUT.direction();
86
87            // max if bounded, otherwise min.
88            let c = LAYOUT.constraints();
89            let track_size = c.with_fill_vector(c.is_bounded()).fill_size();
90            let track_orientation = SLIDER_DIRECTION_VAR.get();
91            let offset = value.get().offset;
92
93            let offset = match track_orientation.layout(layout_direction) {
94                SliderDirection::LeftToRight => track_size.width * offset,
95                SliderDirection::RightToLeft => track_size.width - (track_size.width * offset),
96                SliderDirection::BottomToTop => track_size.height - (track_size.height * offset),
97                SliderDirection::TopToBottom => track_size.height * offset,
98                _ => unreachable!(),
99            };
100            let offset = if track_orientation.is_horizontal() {
101                PxVector::new(offset, Px(0))
102            } else {
103                PxVector::new(Px(0), offset)
104            };
105            wl.translate(offset);
106        }
107        // Actual "drag" is implemented by the parent SliderTrack
108        _ => {}
109    })
110}