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