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}