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