zng_wgt_scroll/
scrollbar.rs1use 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, default(super::Thumb!()), widget_impl(Scrollbar))]
55pub fn thumb(wgt: &mut WidgetBuilding, node: impl IntoUiNode) {
56 let _ = node;
57 wgt.expect_property_capture();
58}
59
60#[property(CONTEXT, default(Orientation::Vertical), widget_impl(Scrollbar))]
64pub fn orientation(wgt: &mut WidgetBuilding, orientation: impl IntoVar<Orientation>) {
65 let _ = orientation;
66 wgt.expect_property_capture();
67}
68
69context_var! {
70 pub(super) static ORIENTATION_VAR: Orientation = Orientation::Vertical;
71}
72
73pub struct SCROLLBAR;
75impl SCROLLBAR {
76 pub fn orientation(&self) -> Var<Orientation> {
78 ORIENTATION_VAR.read_only()
79 }
80}
81
82pub mod vis {
84 use super::*;
85
86 context_var! {
87 pub static BACKGROUND_VAR: Rgba = rgba(80, 80, 80, 50.pct());
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum Orientation {
95 Horizontal,
97 Vertical,
99}
100
101fn scroll_click_handler() -> Handler<MouseClickArgs> {
102 use std::cmp::Ordering;
103
104 let mut ongoing_direction = Ordering::Equal;
105 hn!(|args| {
106 use crate::*;
107
108 let orientation = ORIENTATION_VAR.get();
109 let bounds = WIDGET.bounds().inner_bounds();
110 let scale_factor = WINDOW.vars().scale_factor().get();
111 let position = args.position.to_px(scale_factor) - bounds.origin;
112
113 let (offset, mid_pt, mid_offset) = match orientation {
114 Orientation::Vertical => (
115 bounds.origin.y + bounds.size.height * SCROLL_VERTICAL_OFFSET_VAR.get(),
116 position.y,
117 position.y.0 as f32 / bounds.size.height.0 as f32,
118 ),
119 Orientation::Horizontal => (
120 bounds.origin.x + bounds.size.width * SCROLL_HORIZONTAL_OFFSET_VAR.get(),
121 position.x,
122 position.x.0 as f32 / bounds.size.width.0 as f32,
123 ),
124 };
125
126 let direction = mid_pt.cmp(&offset);
127
128 let clamp = match direction {
130 Ordering::Less => (mid_offset, 1.0),
131 Ordering::Greater => (0.0, mid_offset),
132 Ordering::Equal => (0.0, 0.0),
133 };
134 let request = cmd::ScrollRequest {
135 clamp,
136 ..Default::default()
137 };
138
139 if args.click_count.get() == 1 {
140 ongoing_direction = direction;
141 }
142 if ongoing_direction == direction {
143 match orientation {
144 Orientation::Vertical => match direction {
145 Ordering::Less => cmd::PAGE_UP_CMD.scoped(SCROLL.id()).notify_param(request),
146 Ordering::Greater => cmd::PAGE_DOWN_CMD.scoped(SCROLL.id()).notify_param(request),
147 Ordering::Equal => {}
148 },
149 Orientation::Horizontal => match direction {
150 Ordering::Less => cmd::PAGE_LEFT_CMD.scoped(SCROLL.id()).notify_param(request),
151 Ordering::Greater => cmd::PAGE_RIGHT_CMD.scoped(SCROLL.id()).notify_param(request),
152 Ordering::Equal => {}
153 },
154 }
155 }
156
157 args.propagation().stop();
158 })
159}
160
161fn access_node(child: impl IntoUiNode) -> UiNode {
162 let mut handle = VarHandle::dummy();
163 match_node(child, move |_, op| {
164 if let UiNodeOp::Info { info } = op
165 && let Some(mut info) = info.access()
166 {
167 use crate::*;
168
169 if handle.is_dummy() {
170 handle = ORIENTATION_VAR.subscribe(UpdateOp::Info, WIDGET.id());
171 }
172
173 match ORIENTATION_VAR.get() {
174 Orientation::Horizontal => info.set_scroll_horizontal(SCROLL_HORIZONTAL_OFFSET_VAR.current_context()),
175 Orientation::Vertical => info.set_scroll_vertical(SCROLL_VERTICAL_OFFSET_VAR.current_context()),
176 }
177
178 info.push_controls(SCROLL.id());
179 }
180 })
181}