zng_wgt_scroll/
scrollbar.rs
1use zng_ext_input::mouse::{ClickMode, MouseClickArgs};
4use zng_ext_window::WINDOW_Ext as _;
5use zng_wgt::{align, prelude::*};
6use zng_wgt_access::{AccessRole, access_role};
7use zng_wgt_fill::background_color;
8use zng_wgt_input::{click_mode, mouse::on_mouse_click};
9
10#[widget($crate::Scrollbar)]
12pub struct Scrollbar(WidgetBase);
13impl Scrollbar {
14 fn widget_intrinsic(&mut self) {
15 widget_set! {
16 self;
17 background_color = vis::BACKGROUND_VAR;
18 click_mode = ClickMode::repeat();
19 on_mouse_click = scroll_click_handler();
20 access_role = AccessRole::ScrollBar;
21 }
22
23 self.widget_builder().push_build_action(|wgt| {
24 let thumb = wgt.capture_ui_node_or_else(property_id!(Self::thumb), || super::Thumb!());
26 let thumb = align(thumb, Align::FILL);
27 wgt.set_child(thumb);
28
29 wgt.push_intrinsic(NestGroup::LAYOUT, "orientation-align", move |child| {
30 align(
31 child,
32 ORIENTATION_VAR.map(|o| match o {
33 Orientation::Vertical => Align::FILL_RIGHT,
34 Orientation::Horizontal => Align::FILL_BOTTOM,
35 }),
36 )
37 });
38
39 let orientation = wgt.capture_var_or_else(property_id!(Self::orientation), || Orientation::Vertical);
40 wgt.push_intrinsic(NestGroup::CONTEXT, "scrollbar-context", move |child| {
41 let child = access_node(child);
42 with_context_var(child, ORIENTATION_VAR, orientation)
43 });
44 });
45 }
46}
47
48#[property(CHILD, capture, default(super::Thumb!()), widget_impl(Scrollbar))]
55pub fn thumb(node: impl UiNode) {}
56
57#[property(CONTEXT, capture, default(Orientation::Vertical), widget_impl(Scrollbar))]
61pub fn orientation(orientation: impl IntoVar<Orientation>) {}
62
63context_var! {
64 pub(super) static ORIENTATION_VAR: Orientation = Orientation::Vertical;
65}
66
67pub struct SCROLLBAR;
69impl SCROLLBAR {
70 pub fn orientation(&self) -> BoxedVar<Orientation> {
72 ORIENTATION_VAR.read_only().boxed()
73 }
74}
75
76pub mod vis {
78 use super::*;
79
80 context_var! {
81 pub static BACKGROUND_VAR: Rgba = rgba(80, 80, 80, 50.pct());
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum Orientation {
89 Horizontal,
91 Vertical,
93}
94
95fn scroll_click_handler() -> impl WidgetHandler<MouseClickArgs> {
96 use std::cmp::Ordering;
97
98 let mut ongoing_direction = Ordering::Equal;
99 hn!(|args: &MouseClickArgs| {
100 use crate::*;
101
102 let orientation = ORIENTATION_VAR.get();
103 let bounds = WIDGET.bounds().inner_bounds();
104 let scale_factor = WINDOW.vars().scale_factor().get();
105 let position = args.position.to_px(scale_factor) - bounds.origin;
106
107 let (offset, mid_pt, mid_offset) = match orientation {
108 Orientation::Vertical => (
109 bounds.origin.y + bounds.size.height * SCROLL_VERTICAL_OFFSET_VAR.get(),
110 position.y,
111 position.y.0 as f32 / bounds.size.height.0 as f32,
112 ),
113 Orientation::Horizontal => (
114 bounds.origin.x + bounds.size.width * SCROLL_HORIZONTAL_OFFSET_VAR.get(),
115 position.x,
116 position.x.0 as f32 / bounds.size.width.0 as f32,
117 ),
118 };
119
120 let direction = mid_pt.cmp(&offset);
121
122 let clamp = match direction {
124 Ordering::Less => (mid_offset, 1.0),
125 Ordering::Greater => (0.0, mid_offset),
126 Ordering::Equal => (0.0, 0.0),
127 };
128 let request = cmd::ScrollRequest {
129 clamp,
130 ..Default::default()
131 };
132
133 if args.click_count.get() == 1 {
134 ongoing_direction = direction;
135 }
136 if ongoing_direction == direction {
137 match orientation {
138 Orientation::Vertical => match direction {
139 Ordering::Less => cmd::PAGE_UP_CMD.scoped(SCROLL.id()).notify_param(request),
140 Ordering::Greater => cmd::PAGE_DOWN_CMD.scoped(SCROLL.id()).notify_param(request),
141 Ordering::Equal => {}
142 },
143 Orientation::Horizontal => match direction {
144 Ordering::Less => cmd::PAGE_LEFT_CMD.scoped(SCROLL.id()).notify_param(request),
145 Ordering::Greater => cmd::PAGE_RIGHT_CMD.scoped(SCROLL.id()).notify_param(request),
146 Ordering::Equal => {}
147 },
148 }
149 }
150
151 args.propagation().stop();
152 })
153}
154
155fn access_node(child: impl UiNode) -> impl UiNode {
156 let mut handle = VarHandle::dummy();
157 match_node(child, move |_, op| {
158 if let UiNodeOp::Info { info } = op {
159 if let Some(mut info) = info.access() {
160 use crate::*;
161
162 if handle.is_dummy() {
163 handle = ORIENTATION_VAR.subscribe(UpdateOp::Info, WIDGET.id());
164 }
165
166 match ORIENTATION_VAR.get() {
167 Orientation::Horizontal => info.set_scroll_horizontal(SCROLL_HORIZONTAL_OFFSET_VAR.actual_var()),
168 Orientation::Vertical => info.set_scroll_vertical(SCROLL_VERTICAL_OFFSET_VAR.actual_var()),
169 }
170
171 info.push_controls(SCROLL.id());
172 }
173 }
174 })
175}