zng_wgt_scroll/scroll_properties.rs
1use super::{cmd::ScrollToMode, types::*, *};
2use zng_ext_input::{
3 mouse::{MOUSE_INPUT_EVENT, MOUSE_MOVE_EVENT},
4 pointer_capture::POINTER_CAPTURE,
5};
6use zng_wgt::visibility;
7use zng_wgt_fill::node::flood;
8
9context_var! {
10 /// Widget function for creating the vertical scrollbar of an scroll widget.
11 pub static VERTICAL_SCROLLBAR_FN_VAR: WidgetFn<ScrollBarArgs> = default_scrollbar();
12
13 /// Widget function for creating the vertical scrollbar of an scroll widget.
14 pub static HORIZONTAL_SCROLLBAR_FN_VAR: WidgetFn<ScrollBarArgs> = default_scrollbar();
15
16 /// Widget function for the little square that joins the two scrollbars when both are visible.
17 pub static SCROLLBAR_JOINER_FN_VAR: WidgetFn<()> = wgt_fn!(|_| flood(scrollbar::vis::BACKGROUND_VAR));
18
19 /// Vertical offset added when the [`SCROLL_DOWN_CMD`] runs and removed when the [`SCROLL_UP_CMD`] runs.
20 ///
21 /// Relative lengths are relative to the viewport height, default value is `1.3.em()`.
22 ///
23 /// [`SCROLL_DOWN_CMD`]: crate::cmd::SCROLL_DOWN_CMD
24 /// [`SCROLL_UP_CMD`]: crate::cmd::SCROLL_UP_CMD
25 pub static VERTICAL_LINE_UNIT_VAR: Length = 1.3.em();
26
27 /// Horizontal offset added when the [`SCROLL_RIGHT_CMD`] runs and removed when the [`SCROLL_LEFT_CMD`] runs.
28 ///
29 /// Relative lengths are relative to the viewport width, default value is `1.3.em()`.
30 ///
31 /// [`SCROLL_LEFT_CMD`]: crate::cmd::SCROLL_LEFT_CMD
32 /// [`SCROLL_RIGHT_CMD`]: crate::cmd::SCROLL_RIGHT_CMD
33 pub static HORIZONTAL_LINE_UNIT_VAR: Length = 1.3.em();
34
35 /// Vertical offset added when the [`PAGE_DOWN_CMD`] runs and removed when the [`PAGE_UP_CMD`] runs.
36 ///
37 /// Relative lengths are relative to the viewport height, default value is `100.pct()`.
38 ///
39 /// [`PAGE_DOWN_CMD`]: crate::cmd::PAGE_DOWN_CMD
40 /// [`PAGE_UP_CMD`]: crate::cmd::PAGE_UP_CMD
41 pub static VERTICAL_PAGE_UNIT_VAR: Length = 100.pct();
42
43 /// Horizontal offset multiplied by the [`MouseScrollDelta::LineDelta`] ***x***.
44 ///
45 /// [`MouseScrollDelta::LineDelta`]: zng_ext_input::mouse::MouseScrollDelta::LineDelta
46 pub static HORIZONTAL_WHEEL_UNIT_VAR: Length = 60;
47
48 /// Vertical offset multiplied by the [`MouseScrollDelta::LineDelta`] ***y***.
49 ///
50 /// [`MouseScrollDelta::LineDelta`]: zng_ext_input::mouse::MouseScrollDelta::LineDelta
51 pub static VERTICAL_WHEEL_UNIT_VAR: Length = 60;
52
53 /// Scale delta added or removed from the zoom scale by [`MouseScrollDelta::LineDelta`] used in zoom operations.
54 ///
55 /// [`MouseScrollDelta::LineDelta`]: zng_ext_input::mouse::MouseScrollDelta::LineDelta
56 pub static ZOOM_WHEEL_UNIT_VAR: Factor = 10.pct();
57
58 /// Horizontal offset added when the [`PAGE_RIGHT_CMD`] runs and removed when the [`PAGE_LEFT_CMD`] runs.
59 ///
60 /// Relative lengths are relative to the viewport width, default value is `100.pct()`.
61 ///
62 /// [`PAGE_LEFT_CMD`]: crate::cmd::PAGE_LEFT_CMD
63 /// [`PAGE_RIGHT_CMD`]: crate::cmd::PAGE_RIGHT_CMD
64 pub static HORIZONTAL_PAGE_UNIT_VAR: Length = 100.pct();
65
66 /// Scroll unit multiplier used when alternate scrolling.
67 pub static ALT_FACTOR_VAR: Factor = 3.fct();
68
69 /// Smooth scrolling config for an scroll widget.
70 pub static SMOOTH_SCROLLING_VAR: SmoothScrolling = SmoothScrolling::default();
71
72 /// If a scroll widget defines its viewport size as the [`LayoutMetrics::viewport`] for the scroll content.
73 ///
74 /// This is `true` by default.
75 ///
76 /// [`LayoutMetrics::viewport`]: zng_wgt::prelude::LayoutMetrics::viewport
77 pub static DEFINE_VIEWPORT_UNIT_VAR: bool = true;
78
79 /// Scroll to mode used when scrolling to make the focused child visible.
80 ///
81 /// Default is minimal 0dip on all sides.
82 pub static SCROLL_TO_FOCUSED_MODE_VAR: Option<ScrollToMode> = ScrollToMode::Minimal {
83 margin: SideOffsets::new_all(0.dip()),
84 };
85
86 /// Defines how the content is scaled to fit.
87 pub static ZOOM_TO_FIT_MODE_VAR: ZoomToFitMode = ZoomToFitMode::default();
88
89 /// Extra space added to the viewport auto-hide rectangle.
90 ///
91 /// The scroll sets the viewport plus these offsets as the [`FrameBuilder::auto_hide_rect`], this value is used
92 /// for optimizations from the render culling to lazy widgets.
93 ///
94 /// By default is `500.dip().min(100.pct())`, one full viewport extra capped at 500.
95 ///
96 /// [`FrameBuilder::auto_hide_rect`]: zng_wgt::prelude::FrameBuilder::auto_hide_rect
97 pub static AUTO_HIDE_EXTRA_VAR: SideOffsets = 500.dip().min(100.pct());
98
99 /// Color of the overscroll indicator.
100 pub static OVERSCROLL_COLOR_VAR: Rgba = colors::GRAY.with_alpha(50.pct());
101
102 /// Minimum scale allowed when [`ScrollMode::ZOOM`] is enabled.
103 pub static MIN_ZOOM_VAR: Factor = 10.pct();
104
105 /// Maximum scale allowed when [`ScrollMode::ZOOM`] is enabled.
106 pub static MAX_ZOOM_VAR: Factor = 500.pct();
107
108 /// Center point of zoom scaling done using the mouse scroll wheel.
109 ///
110 /// Relative values are resolved in the viewport space. The scroll offsets so that the point in the
111 /// viewport and content stays as close as possible after the scale change.
112 ///
113 /// The default value ([`Length::Default`]) is the cursor position.
114 ///
115 /// [`Length::Default`]: zng_wgt::prelude::Length::Default
116 pub static ZOOM_WHEEL_ORIGIN_VAR: Point = Point::default();
117
118 /// Center point of zoom scaling done using the touch *pinch* gesture.
119 ///
120 /// Relative values are resolved in the viewport space. The scroll offsets so that the point in the
121 /// viewport and content stays as close as possible after the scale change.
122 ///
123 /// The default value ([`Length::Default`]) is center point between the two touch contact points.
124 ///
125 /// [`Length::Default`]: zng_wgt::prelude::Length::Default
126 pub static ZOOM_TOUCH_ORIGIN_VAR: Point = Point::default();
127
128 /// If auto-scrolling on middle click is enabled.
129 ///
130 /// Is `true` by default.
131 pub static AUTO_SCROLL_VAR: bool = true;
132
133 /// Auto scroll icon/indicator node. The node is child of the auto scroll indicator widget, the full
134 /// [`SCROLL`] context can be used in the indicator.
135 ///
136 /// Is [`node::default_auto_scroll_indicator`] by default.
137 ///
138 /// [`node::default_auto_scroll_indicator`]: crate::node::default_auto_scroll_indicator
139 pub static AUTO_SCROLL_INDICATOR_VAR: WidgetFn<AutoScrollArgs> = wgt_fn!(|_| { crate::node::default_auto_scroll_indicator() });
140}
141
142fn default_scrollbar() -> WidgetFn<ScrollBarArgs> {
143 wgt_fn!(|args: ScrollBarArgs| {
144 Scrollbar! {
145 thumb = Thumb! {
146 viewport_ratio = args.viewport_ratio();
147 offset = args.offset();
148 };
149 orientation = args.orientation;
150 visibility = args.content_overflows().map_into();
151 }
152 })
153}
154
155/// Vertical scrollbar function for all scroll widget descendants.
156///
157/// This property sets the [`VERTICAL_SCROLLBAR_FN_VAR`].
158#[property(CONTEXT, default(VERTICAL_SCROLLBAR_FN_VAR), widget_impl(super::ScrollbarFnMix<P>))]
159pub fn v_scrollbar_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<ScrollBarArgs>>) -> UiNode {
160 with_context_var(child, VERTICAL_SCROLLBAR_FN_VAR, wgt_fn)
161}
162
163/// Horizontal scrollbar function for all scroll widget descendants.
164///
165/// This property sets the [`HORIZONTAL_SCROLLBAR_FN_VAR`].
166#[property(CONTEXT, default(HORIZONTAL_SCROLLBAR_FN_VAR), widget_impl(super::ScrollbarFnMix<P>))]
167pub fn h_scrollbar_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<ScrollBarArgs>>) -> UiNode {
168 with_context_var(child, HORIZONTAL_SCROLLBAR_FN_VAR, wgt_fn)
169}
170
171/// Scrollbar function for both orientations applicable to all scroll widget descendants.
172///
173/// This property sets both [`v_scrollbar_fn`] and [`h_scrollbar_fn`] to the same `wgt_fn`.
174///
175/// [`v_scrollbar_fn`]: fn@v_scrollbar_fn
176/// [`h_scrollbar_fn`]: fn@h_scrollbar_fn
177#[property(CONTEXT, default(WidgetFn::nil()), widget_impl(super::ScrollbarFnMix<P>))]
178pub fn scrollbar_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<ScrollBarArgs>>) -> UiNode {
179 let wgt_fn = wgt_fn.into_var();
180 let child = v_scrollbar_fn(child, wgt_fn.clone());
181 h_scrollbar_fn(child, wgt_fn)
182}
183
184/// Widget function for the little square in the corner that joins the two scrollbars when both are visible.
185///
186/// This property sets the [`SCROLLBAR_JOINER_FN_VAR`].
187#[property(CONTEXT, default(SCROLLBAR_JOINER_FN_VAR), widget_impl(super::ScrollbarFnMix<P>))]
188pub fn scrollbar_joiner_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<()>>) -> UiNode {
189 with_context_var(child, SCROLLBAR_JOINER_FN_VAR, wgt_fn)
190}
191
192/// Vertical offset added when the [`SCROLL_DOWN_CMD`] runs and removed when the [`SCROLL_UP_CMD`] runs.
193///
194/// Relative lengths are relative to the viewport height.
195///
196/// [`SCROLL_UP_CMD`]: crate::cmd::SCROLL_UP_CMD
197/// [`SCROLL_DOWN_CMD`]: crate::cmd::SCROLL_DOWN_CMD
198///
199/// This property sets the [`VERTICAL_LINE_UNIT_VAR`].
200#[property(CONTEXT, default(VERTICAL_LINE_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
201pub fn v_line_unit(child: impl IntoUiNode, unit: impl IntoVar<Length>) -> UiNode {
202 with_context_var(child, VERTICAL_LINE_UNIT_VAR, unit)
203}
204
205/// Horizontal offset added when the [`SCROLL_RIGHT_CMD`] runs and removed when the [`SCROLL_LEFT_CMD`] runs.
206///
207/// Relative lengths are relative to the viewport width.
208///
209/// [`SCROLL_LEFT_CMD`]: crate::cmd::SCROLL_LEFT_CMD
210/// [`SCROLL_RIGHT_CMD`]: crate::cmd::SCROLL_RIGHT_CMD
211///
212/// This property sets the [`HORIZONTAL_LINE_UNIT_VAR`].
213#[property(CONTEXT, default(HORIZONTAL_LINE_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
214pub fn h_line_unit(child: impl IntoUiNode, unit: impl IntoVar<Length>) -> UiNode {
215 with_context_var(child, HORIZONTAL_LINE_UNIT_VAR, unit)
216}
217
218/// Horizontal and vertical offsets used when scrolling.
219///
220/// This property sets the [`h_line_unit`] and [`v_line_unit`].
221///
222/// [`h_line_unit`]: fn@h_line_unit
223/// [`v_line_unit`]: fn@v_line_unit
224#[property(CONTEXT, default(HORIZONTAL_LINE_UNIT_VAR, VERTICAL_LINE_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
225pub fn line_units(child: impl IntoUiNode, horizontal: impl IntoVar<Length>, vertical: impl IntoVar<Length>) -> UiNode {
226 let child = h_line_unit(child, horizontal);
227 v_line_unit(child, vertical)
228}
229
230/// Scroll unit multiplier used when alternate scrolling.
231///
232/// This property sets the [`ALT_FACTOR_VAR`].
233#[property(CONTEXT, default(ALT_FACTOR_VAR), widget_impl(super::ScrollUnitsMix<P>))]
234pub fn alt_factor(child: impl IntoUiNode, factor: impl IntoVar<Factor>) -> UiNode {
235 with_context_var(child, ALT_FACTOR_VAR, factor)
236}
237
238/// Vertical offset added when the [`PAGE_DOWN_CMD`] runs and removed when the [`PAGE_UP_CMD`] runs.
239///
240/// Relative lengths are relative to the viewport height.
241///
242/// [`PAGE_UP_CMD`]: crate::cmd::PAGE_UP_CMD
243/// [`PAGE_DOWN_CMD`]: crate::cmd::PAGE_DOWN_CMD
244///
245/// This property sets the [`VERTICAL_PAGE_UNIT_VAR`].
246#[property(CONTEXT, default(VERTICAL_PAGE_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
247pub fn v_page_unit(child: impl IntoUiNode, unit: impl IntoVar<Length>) -> UiNode {
248 with_context_var(child, VERTICAL_PAGE_UNIT_VAR, unit)
249}
250
251/// Horizontal offset added when the [`PAGE_RIGHT_CMD`] runs and removed when the [`PAGE_LEFT_CMD`] runs.
252///
253/// Relative lengths are relative to the viewport width.
254///
255/// [`PAGE_LEFT_CMD`]: crate::cmd::PAGE_LEFT_CMD
256/// [`PAGE_RIGHT_CMD`]: crate::cmd::PAGE_RIGHT_CMD
257///
258/// This property sets the [`HORIZONTAL_PAGE_UNIT_VAR`].
259#[property(CONTEXT, default(HORIZONTAL_PAGE_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
260pub fn h_page_unit(child: impl IntoUiNode, unit: impl IntoVar<Length>) -> UiNode {
261 with_context_var(child, HORIZONTAL_PAGE_UNIT_VAR, unit)
262}
263
264/// Horizontal and vertical offsets used when page-scrolling.
265///
266/// This property sets the [`h_page_unit`] and [`v_page_unit`].
267///
268/// [`h_page_unit`]: fn@h_page_unit
269/// [`v_page_unit`]: fn@v_page_unit
270#[property(CONTEXT, default(HORIZONTAL_PAGE_UNIT_VAR, VERTICAL_PAGE_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
271pub fn page_units(child: impl IntoUiNode, horizontal: impl IntoVar<Length>, vertical: impl IntoVar<Length>) -> UiNode {
272 let child = h_page_unit(child, horizontal);
273 v_page_unit(child, vertical)
274}
275
276/// Horizontal offset added when the mouse wheel is scrolling by lines.
277///
278/// The `unit` value is multiplied by the [`MouseScrollDelta::LineDelta`] ***x*** value to determinate the scroll delta.
279///
280/// [`MouseScrollDelta::LineDelta`]: zng_ext_input::mouse::MouseScrollDelta::LineDelta
281///
282/// This property sets the [`HORIZONTAL_WHEEL_UNIT_VAR`].
283#[property(CONTEXT, default(HORIZONTAL_WHEEL_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
284pub fn h_wheel_unit(child: impl IntoUiNode, unit: impl IntoVar<Length>) -> UiNode {
285 with_context_var(child, HORIZONTAL_WHEEL_UNIT_VAR, unit)
286}
287
288/// Vertical offset added when the mouse wheel is scrolling by lines.
289///
290/// The `unit` value is multiplied by the [`MouseScrollDelta::LineDelta`] ***y*** value to determinate the scroll delta.
291///
292/// [`MouseScrollDelta::LineDelta`]: zng_ext_input::mouse::MouseScrollDelta::LineDelta
293///
294/// This property sets the [`VERTICAL_WHEEL_UNIT_VAR`]`.
295#[property(CONTEXT, default(VERTICAL_WHEEL_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
296pub fn v_wheel_unit(child: impl IntoUiNode, unit: impl IntoVar<Length>) -> UiNode {
297 with_context_var(child, VERTICAL_WHEEL_UNIT_VAR, unit)
298}
299
300/// Horizontal and vertical offsets used when mouse wheel scrolling.
301///
302/// This property sets the [`h_wheel_unit`] and [`v_wheel_unit`].
303///
304/// [`h_wheel_unit`]: fn@h_wheel_unit
305/// [`v_wheel_unit`]: fn@v_wheel_unit
306#[property(CONTEXT, default(HORIZONTAL_WHEEL_UNIT_VAR, VERTICAL_WHEEL_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
307pub fn wheel_units(child: impl IntoUiNode, horizontal: impl IntoVar<Length>, vertical: impl IntoVar<Length>) -> UiNode {
308 let child = h_wheel_unit(child, horizontal);
309 v_wheel_unit(child, vertical)
310}
311
312/// Scale delta added when the mouse wheel is zooming by lines.
313///
314/// The `unit` value is multiplied by the [`MouseScrollDelta::LineDelta`] value to determinate the scale delta.
315///
316/// [`MouseScrollDelta::LineDelta`]: zng_ext_input::mouse::MouseScrollDelta::LineDelta
317///
318/// This property sets the [`ZOOM_WHEEL_UNIT_VAR`].
319#[property(CONTEXT, default(ZOOM_WHEEL_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
320pub fn zoom_wheel_unit(child: impl IntoUiNode, unit: impl IntoVar<Factor>) -> UiNode {
321 with_context_var(child, ZOOM_WHEEL_UNIT_VAR, unit)
322}
323
324/// If the scroll defines its viewport size as the [`LayoutMetrics::viewport`] for the scroll content.
325///
326/// This property sets the [`DEFINE_VIEWPORT_UNIT_VAR`].
327///
328/// [`LayoutMetrics::viewport`]: zng_wgt::prelude::LayoutMetrics::viewport
329#[property(CONTEXT, default(DEFINE_VIEWPORT_UNIT_VAR), widget_impl(super::ScrollUnitsMix<P>))]
330pub fn define_viewport_unit(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
331 with_context_var(child, DEFINE_VIEWPORT_UNIT_VAR, enabled)
332}
333
334/// Smooth scrolling config.
335///
336/// Defines the easing animation applied to scroll offset and zoom value changes.
337///
338/// This property sets the [`SMOOTH_SCROLLING_VAR`].
339#[property(CONTEXT, default(SMOOTH_SCROLLING_VAR), widget_impl(Scroll))]
340pub fn smooth_scrolling(child: impl IntoUiNode, config: impl IntoVar<SmoothScrolling>) -> UiNode {
341 with_context_var(child, SMOOTH_SCROLLING_VAR, config)
342}
343
344/// Scroll-to mode used by scroll widgets when scrolling to make the focused child visible.
345///
346/// Default is minimal 0dip on all sides, set to `None` to disable.
347///
348/// Note that [`SCROLL_TO_CMD`] requests have priority over scroll-to focused if both requests
349/// happen in the same event cycle.
350///
351/// [`SCROLL_TO_CMD`]: crate::cmd::SCROLL_TO_CMD
352///
353/// This property sets the [`SCROLL_TO_FOCUSED_MODE_VAR`].
354#[property(CONTEXT, default(SCROLL_TO_FOCUSED_MODE_VAR), widget_impl(Scroll))]
355pub fn scroll_to_focused_mode(child: impl IntoUiNode, mode: impl IntoVar<Option<ScrollToMode>>) -> UiNode {
356 with_context_var(child, SCROLL_TO_FOCUSED_MODE_VAR, mode)
357}
358
359/// Defines how the scale is changed to fit the content to the viewport.
360///
361/// The [`ZOOM_TO_FIT_CMD`] implementation uses this value to define the zoom scale needed to fit the content.
362/// By default it scales down and up to fit, setting this to `ScaleDown` will only scale down larger content and only
363/// scale up smaller content to 100%.
364///
365/// This property sets the [`ZOOM_TO_FIT_MODE_VAR`].
366///
367/// [`ZOOM_TO_FIT_CMD`]: crate::cmd::ZOOM_TO_FIT_CMD
368#[property(CONTEXT, default(ZOOM_TO_FIT_MODE_VAR), widget_impl(Scroll))]
369pub fn zoom_to_fit_mode(child: impl IntoUiNode, mode: impl IntoVar<ZoomToFitMode>) -> UiNode {
370 with_context_var(child, ZOOM_TO_FIT_MODE_VAR, mode)
371}
372
373/// Extra space added to the viewport auto-hide rectangle.
374///
375/// The scroll sets the viewport plus these offsets as the [`FrameBuilder::auto_hide_rect`], this value is used
376/// for optimizations from the render culling to lazy widgets.
377///
378/// Scrolling can use only lightweight render updates to scroll within the extra margin, so there is an exchange of
379/// performance, a larger extra space means that more widgets are rendering, but also can mean less full frame
380/// requests, if there is no other widget requesting render.
381///
382/// By default is `500.dip().min(100.pct())`, one full viewport extra capped at 500.
383///
384/// This property sets the [`AUTO_HIDE_EXTRA_VAR`].
385///
386/// [`FrameBuilder::auto_hide_rect`]: zng_wgt::prelude::FrameBuilder::auto_hide_rect
387#[property(CONTEXT, default(AUTO_HIDE_EXTRA_VAR), widget_impl(Scroll))]
388pub fn auto_hide_extra(child: impl IntoUiNode, extra: impl IntoVar<SideOffsets>) -> UiNode {
389 with_context_var(child, AUTO_HIDE_EXTRA_VAR, extra)
390}
391
392/// Color of the overscroll indicator.
393///
394/// The overscroll indicator appears when touch scroll tries to scroll past an edge in a dimension
395/// that can scroll.
396///
397/// This property sets the [`OVERSCROLL_COLOR_VAR`].
398#[property(CONTEXT, default(OVERSCROLL_COLOR_VAR), widget_impl(Scroll))]
399pub fn overscroll_color(child: impl IntoUiNode, color: impl IntoVar<Rgba>) -> UiNode {
400 with_context_var(child, OVERSCROLL_COLOR_VAR, color)
401}
402
403/// Minimum scale allowed when [`ScrollMode::ZOOM`] is enabled.
404///
405/// This property sets the [`MIN_ZOOM_VAR`].
406#[property(CONTEXT, default(MIN_ZOOM_VAR), widget_impl(Scroll))]
407pub fn min_zoom(child: impl IntoUiNode, min: impl IntoVar<Factor>) -> UiNode {
408 with_context_var(child, MIN_ZOOM_VAR, min)
409}
410
411/// Maximum scale allowed when [`ScrollMode::ZOOM`] is enabled.
412///
413/// This property sets the [`MAX_ZOOM_VAR`].
414#[property(CONTEXT, default(MAX_ZOOM_VAR), widget_impl(Scroll))]
415pub fn max_zoom(child: impl IntoUiNode, max: impl IntoVar<Factor>) -> UiNode {
416 with_context_var(child, MAX_ZOOM_VAR, max)
417}
418
419/// Center point of zoom scaling done using the mouse scroll wheel.
420///
421/// Relative values are resolved in the viewport space. The scroll offsets so that the point in the
422/// viewport and content stays as close as possible after the scale change.
423///
424/// The default value ([`Length::Default`]) is the cursor position.
425///
426/// This property sets the [`ZOOM_WHEEL_ORIGIN_VAR`]
427///
428/// [`Length::Default`]: zng_wgt::prelude::Length::Default
429#[property(CONTEXT, default(ZOOM_WHEEL_ORIGIN_VAR), widget_impl(Scroll))]
430pub fn zoom_wheel_origin(child: impl IntoUiNode, origin: impl IntoVar<Point>) -> UiNode {
431 with_context_var(child, ZOOM_WHEEL_ORIGIN_VAR, origin)
432}
433
434/// Center point of zoom scaling done using the touch *pinch* gesture.
435///
436/// Relative values are resolved in the viewport space. The scroll offsets so that the point in the
437/// viewport and content stays as close as possible after the scale change.
438///
439/// The default value ([`Length::Default`]) is center point between the two touch contact points.
440///
441/// This property sets the [`ZOOM_TOUCH_ORIGIN_VAR`].
442///
443/// [`Length::Default`]: zng_wgt::prelude::Length::Default
444#[property(CONTEXT, default(ZOOM_TOUCH_ORIGIN_VAR), widget_impl(Scroll))]
445pub fn zoom_touch_origin(child: impl IntoUiNode, origin: impl IntoVar<Point>) -> UiNode {
446 with_context_var(child, ZOOM_TOUCH_ORIGIN_VAR, origin)
447}
448
449/// Center point of zoom scaling done using mouse scroll wheel and touch gesture.
450///
451/// This property sets both [`zoom_wheel_origin`] and [`zoom_touch_origin`] to the same point.
452///
453/// [`zoom_wheel_origin`]: fn@zoom_wheel_origin
454/// [`zoom_touch_origin`]: fn@zoom_touch_origin
455#[property(CONTEXT, default(Point::default()), widget_impl(Scroll))]
456pub fn zoom_origin(child: impl IntoUiNode, origin: impl IntoVar<Point>) -> UiNode {
457 let origin = origin.into_var();
458 let child = zoom_wheel_origin(child, origin.clone());
459 zoom_touch_origin(child, origin)
460}
461
462/// Enables or disables auto scroll on mouse middle click.
463///
464/// This is enabled by default, when enabled on middle click the [`auto_scroll_indicator`] is generated and
465/// the content auto scrolls depending on the direction the mouse pointer moves away from the indicator.
466///
467/// This property sets the [`AUTO_SCROLL_VAR`].
468///
469/// [`auto_scroll_indicator`]: fn@auto_scroll_indicator
470#[property(CONTEXT, default(AUTO_SCROLL_VAR), widget_impl(Scroll))]
471pub fn auto_scroll(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
472 with_context_var(child, AUTO_SCROLL_VAR, enabled)
473}
474
475/// Auto scroll icon/indicator node.
476///
477/// The `indicator` is instantiated on middle click if [`auto_scroll`] is enabled, the node is layered as an adorner of the
478/// scroll. All context vars and the full [`SCROLL`] context are captured and can be used in the indicator.
479///
480/// Is [`node::default_auto_scroll_indicator`] by default.
481///
482/// [`node::default_auto_scroll_indicator`]: crate::node::default_auto_scroll_indicator
483/// [`auto_scroll`]: fn@auto_scroll
484#[property(CONTEXT, default(AUTO_SCROLL_INDICATOR_VAR), widget_impl(Scroll))]
485pub fn auto_scroll_indicator(child: impl IntoUiNode, indicator: impl IntoVar<WidgetFn<AutoScrollArgs>>) -> UiNode {
486 with_context_var(child, AUTO_SCROLL_INDICATOR_VAR, indicator)
487}
488
489/// Binds the [`horizontal_offset`] scroll var to the property value.
490///
491/// The binding is bidirectional and the scroll variable is assigned on init.
492///
493/// Note that settings the offset directly overrides effects like smooth scrolling, prefer using
494/// the scroll commands to scroll over this property.
495///
496/// [`horizontal_offset`]: super::SCROLL::horizontal_offset
497#[property(EVENT, widget_impl(Scroll))]
498pub fn horizontal_offset(child: impl IntoUiNode, offset: impl IntoVar<Factor>) -> UiNode {
499 let offset = offset.into_var();
500 match_node(child, move |_, op| {
501 if let UiNodeOp::Init = op {
502 let scroll_offset = super::SCROLL.horizontal_offset();
503
504 if !offset.capabilities().is_const() {
505 let binding = offset.bind_bidi(&scroll_offset);
506 WIDGET.push_var_handles(binding);
507 }
508 scroll_offset.set_from(&offset);
509 }
510 })
511}
512
513/// Binds the [`vertical_offset`] scroll var to the property value.
514///
515/// The binding is bidirectional and the scroll variable is assigned on init.
516///
517/// Note that settings the offset directly overrides effects like smooth scrolling, prefer using
518/// the scroll commands to scroll over this property.
519///
520/// [`vertical_offset`]: super::SCROLL::vertical_offset
521#[property(EVENT, widget_impl(Scroll))]
522pub fn vertical_offset(child: impl IntoUiNode, offset: impl IntoVar<Factor>) -> UiNode {
523 let offset = offset.into_var();
524 match_node(child, move |_, op| {
525 if let UiNodeOp::Init = op {
526 let scroll_offset = super::SCROLL.vertical_offset();
527
528 if !offset.capabilities().is_const() {
529 let binding = offset.bind_bidi(&scroll_offset);
530 WIDGET.push_var_handles(binding);
531 }
532 scroll_offset.set_from(&offset);
533 }
534 })
535}
536
537/// Binds the [`zoom_scale`] scroll var to the property value.
538///
539/// The binding is bidirectional and the scroll variable is assigned on init.
540///
541/// Note that settings the offset directly overrides effects like smooth scrolling, prefer using
542/// the scroll commands to scroll over this property.
543///
544/// [`zoom_scale`]: super::SCROLL::zoom_scale
545#[property(EVENT, widget_impl(Scroll))]
546pub fn zoom_scale(child: impl IntoUiNode, scale: impl IntoVar<Factor>) -> UiNode {
547 let scale = scale.into_var();
548 match_node(child, move |_, op| {
549 if let UiNodeOp::Init = op {
550 let scroll_scale = super::SCROLL.zoom_scale();
551
552 if !scale.capabilities().is_const() {
553 let binding = scale.bind_bidi(&scroll_scale);
554 WIDGET.push_var_handles(binding);
555 }
556 scroll_scale.set_from(&scale);
557 }
558 })
559}
560
561/// Arguments for scrollbar widget functions.
562#[derive(Clone, Debug, PartialEq)]
563#[non_exhaustive]
564pub struct ScrollBarArgs {
565 /// Scrollbar orientation.
566 pub orientation: scrollbar::Orientation,
567}
568impl ScrollBarArgs {
569 /// Arguments from scroll context.
570 pub fn new(orientation: scrollbar::Orientation) -> Self {
571 Self { orientation }
572 }
573
574 /// Gets the context variable that gets and sets the offset for the orientation.
575 ///
576 /// See [`SCROLL.vertical_offset`] and [`SCROLL.horizontal_offset`] for more details.
577 ///
578 /// [`SCROLL.vertical_offset`]: SCROLL::vertical_offset
579 /// [`SCROLL.horizontal_offset`]: SCROLL::horizontal_offset
580 pub fn offset(&self) -> ContextVar<Factor> {
581 use scrollbar::Orientation::*;
582
583 match self.orientation {
584 Vertical => SCROLL_VERTICAL_OFFSET_VAR,
585 Horizontal => SCROLL_HORIZONTAL_OFFSET_VAR,
586 }
587 }
588
589 /// Gets the context variable that gets the viewport/content ratio for the orientation.
590 ///
591 /// See [`SCROLL`] for more details.
592 pub fn viewport_ratio(&self) -> Var<Factor> {
593 use scrollbar::Orientation::*;
594
595 match self.orientation {
596 Vertical => SCROLL.vertical_ratio(),
597 Horizontal => SCROLL.horizontal_ratio(),
598 }
599 }
600
601 /// Gets the context variable that gets if the scrollbar should be visible.
602 ///
603 /// See [`SCROLL`] for more details.
604 pub fn content_overflows(&self) -> Var<bool> {
605 use scrollbar::Orientation::*;
606
607 match self.orientation {
608 Vertical => SCROLL.vertical_content_overflows(),
609 Horizontal => SCROLL.horizontal_content_overflows(),
610 }
611 }
612}
613
614/// Scroll by grabbing and dragging the content with the mouse primary button.
615///
616/// This is not enabled by default. Note that touch pan is always enabled, this property implements
617/// a similar behavior for the mouse pointer.
618#[property(LAYOUT, default(false), widget_impl(Scroll))]
619pub fn mouse_pan(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
620 let enabled = enabled.into_var();
621 let mut mouse_input = EventHandle::dummy();
622
623 struct Dragging {
624 _mouse_move: EventHandle,
625 start: PxPoint,
626 applied_offset: PxVector,
627 factor: Factor,
628 }
629 let mut dragging = None;
630
631 match_node(child, move |c, op| match op {
632 UiNodeOp::Init => {
633 WIDGET.sub_var(&enabled);
634 if enabled.get() {
635 mouse_input = MOUSE_INPUT_EVENT.subscribe(WIDGET.id());
636 }
637 }
638 UiNodeOp::Deinit => {
639 mouse_input = EventHandle::dummy();
640 dragging = None;
641 }
642 UiNodeOp::Update { .. } => {
643 if let Some(enabled) = enabled.get_new() {
644 if enabled && mouse_input.is_dummy() {
645 mouse_input = MOUSE_INPUT_EVENT.subscribe(WIDGET.id());
646 } else {
647 mouse_input = EventHandle::dummy();
648 dragging = None;
649 }
650 }
651 }
652 UiNodeOp::Event { update } => {
653 if enabled.get() {
654 c.event(update);
655
656 if let Some(args) = MOUSE_INPUT_EVENT.on_unhandled(update) {
657 if args.is_primary() {
658 if args.is_mouse_down() {
659 let id = WIDGET.id();
660 POINTER_CAPTURE.capture_widget(id);
661 let factor = WINDOW.info().scale_factor();
662 dragging = Some(Dragging {
663 _mouse_move: MOUSE_MOVE_EVENT.subscribe(id),
664 start: args.position.to_px(factor),
665 applied_offset: PxVector::zero(),
666 factor,
667 });
668 } else {
669 dragging = None;
670 SCROLL.clear_vertical_overscroll();
671 SCROLL.clear_horizontal_overscroll();
672 }
673 }
674 } else if let Some(d) = &mut dragging
675 && let Some(args) = MOUSE_MOVE_EVENT.on_unhandled(update)
676 {
677 let offset = d.start - args.position.to_px(d.factor);
678 let delta = d.applied_offset - offset;
679 d.applied_offset = offset;
680
681 if delta.y != Px(0) {
682 SCROLL.scroll_vertical_touch(-delta.y);
683 }
684 if delta.x != Px(0) {
685 SCROLL.scroll_horizontal_touch(-delta.x);
686 }
687 }
688 }
689 }
690 _ => {}
691 })
692}