zng_wgt_slider/
thumb.rs
1use zng_ext_input::mouse::MOUSE_MOVE_EVENT;
4use zng_wgt::prelude::*;
5use zng_wgt_input::{focus::FocusableMix, pointer_capture::capture_pointer};
6use zng_wgt_style::{Style, StyleMix, impl_style_fn, style_fn};
7
8use crate::{SLIDER_DIRECTION_VAR, SliderDirection, ThumbValue, WidgetInfoExt as _};
9
10#[widget($crate::thumb::Thumb {
12 ($value:expr) => {
13 value = $value;
14 }
15})]
16pub struct Thumb(FocusableMix<StyleMix<WidgetBase>>);
17impl Thumb {
18 fn widget_intrinsic(&mut self) {
19 self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
20
21 self.widget_builder()
22 .push_build_action(|wgt| match wgt.capture_var::<ThumbValue>(property_id!(Self::value)) {
23 Some(v) => {
24 wgt.push_intrinsic(NestGroup::LAYOUT, "event-layout", move |c| thumb_event_layout_node(c, v));
25 }
26 None => tracing::error!("missing required `slider::Thumb::value` property"),
27 });
28
29 widget_set! {
30 self;
31 style_base_fn = style_fn!(|_| DefaultStyle!());
32 capture_pointer = true;
33 }
34 }
35}
36impl_style_fn!(Thumb);
37
38#[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); }
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#[property(CONTEXT, capture, widget_impl(Thumb))]
69pub fn value(thumb: impl IntoVar<ThumbValue>) {}
70
71fn thumb_event_layout_node(child: impl UiNode, value: impl IntoVar<ThumbValue>) -> impl UiNode {
75 let value = value.into_var();
76 let mut layout_direction = LayoutDirection::LTR;
77 match_node(child, move |c, op| match op {
78 UiNodeOp::Init => {
79 WIDGET.sub_var_layout(&value).sub_event(&MOUSE_MOVE_EVENT);
80 }
81 UiNodeOp::Event { update } => {
82 c.event(update);
83 if let Some(args) = MOUSE_MOVE_EVENT.on_unhandled(update) {
84 if let Some(c) = &args.capture {
85 if c.target.widget_id() == WIDGET.id() {
86 let thumb_info = WIDGET.info();
87 let track_info = match thumb_info.slider_track() {
88 Some(i) => i,
89 None => {
90 tracing::error!("slider::Thumb is not inside a slider_track");
91 return;
92 }
93 };
94 args.propagation().stop();
95
96 let track_bounds = track_info.inner_bounds();
97 let track_orientation = SLIDER_DIRECTION_VAR.get();
98
99 let (track_min, track_max) = match track_orientation.layout(layout_direction) {
100 SliderDirection::LeftToRight => (track_bounds.min_x(), track_bounds.max_x()),
101 SliderDirection::RightToLeft => (track_bounds.max_x(), track_bounds.min_x()),
102 SliderDirection::BottomToTop => (track_bounds.max_y(), track_bounds.min_y()),
103 SliderDirection::TopToBottom => (track_bounds.min_y(), track_bounds.max_y()),
104 _ => unreachable!(),
105 };
106 let cursor = if track_orientation.is_horizontal() {
107 args.position.x.to_px(track_info.tree().scale_factor())
108 } else {
109 args.position.y.to_px(track_info.tree().scale_factor())
110 };
111 let new_offset = (cursor - track_min).0 as f32 / (track_max - track_min).abs().0 as f32;
112
113 let selector = crate::SELECTOR.get();
114 selector.set(value.get().offset(), new_offset.fct().clamp_range());
115 WIDGET.update();
116 }
117 }
118 }
119 }
120 UiNodeOp::Layout { wl, final_size } => {
121 *final_size = c.layout(wl);
122 layout_direction = LAYOUT.direction();
123
124 let c = LAYOUT.constraints();
126 let track_size = c.with_fill_vector(c.is_bounded()).fill_size();
127 let track_orientation = SLIDER_DIRECTION_VAR.get();
128 let offset = value.get().offset;
129
130 let offset = match track_orientation.layout(layout_direction) {
131 SliderDirection::LeftToRight => track_size.width * offset,
132 SliderDirection::RightToLeft => track_size.width - (track_size.width * offset),
133 SliderDirection::BottomToTop => track_size.height - (track_size.height * offset),
134 SliderDirection::TopToBottom => track_size.height * offset,
135 _ => unreachable!(),
136 };
137 let offset = if track_orientation.is_horizontal() {
138 PxVector::new(offset, Px(0))
139 } else {
140 PxVector::new(Px(0), offset)
141 };
142 wl.translate(offset);
143 }
144 _ => {}
145 })
146}