Skip to main content

ferroui_controls/
scroll_viewer.rs

1use crate::metadata::TemplatePartAttribute;
2use crate::presenters::ContentPresenter;
3use crate::primitives::{
4    as_logical_scrollable, ScrollBar, ScrollBarVisibility, SnapPointsAlignment, SnapPointsType,
5    TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt,
6};
7use crate::{
8    as_scroll_anchor_provider, register_scroll_anchor_provider, ContentControl, ContentControlImpl,
9    ContentControlImplExt, Control, ControlImpl, IScrollAnchorProvider, ScrollChangedEventArgs,
10};
11use ferroui_base::input::{
12    FocusChangedEventArgs, IScrollable, InputElementImpl, InputElementImplExt, Key, KeyEventArgs,
13};
14use ferroui_base::interactivity::{
15    Interactive, InteractiveImpl, RoutedEvent, RoutedEventHandlerToken, RoutingStrategies,
16};
17use ferroui_base::layout::LayoutableImpl;
18use ferroui_base::media::FlowDirection;
19use ferroui_base::reactive::{CompositeDisposable, IDisposable, ObservableExt};
20use ferroui_base::{
21    ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, AttachedProperty, DirectProperty,
22    FerroObject, FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
23    FerroPropertyChangedEventArgs, Ref, Size, StaticType, StyledElementImpl, StyledProperty, StyledPropertyOptions,
24    Vector, VisualImpl,
25};
26use std::cell::{Cell, RefCell};
27use std::rc::Rc;
28
29/// A control which scrolls its content if the content is bigger than the
30/// space available.
31#[repr(C)]
32pub struct ScrollViewer {
33    base: ContentControl,
34    child_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
35    /// The logically scrolling child and the subscription to its scroll
36    /// invalidated event.
37    logical_scrollable: RefCell<Option<(Ref<Control>, Rc<dyn IDisposable>)>>,
38    extent: Cell<Size>,
39    viewport: Cell<Size>,
40    old_extent: Cell<Size>,
41    old_offset: Cell<Vector>,
42    old_maximum: Cell<Vector>,
43    old_viewport: Cell<Size>,
44    large_change: Cell<Size>,
45    small_change: Cell<Size>,
46    is_expanded: Cell<bool>,
47    scroll_bar_expand_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
48    horizontal_scroll_bar: RefCell<Option<Ref<ScrollBar>>>,
49    vertical_scroll_bar: RefCell<Option<Ref<ScrollBar>>>,
50}
51
52ferro_class! {
53    ScrollViewer: ContentControl, virtuals ScrollViewerImpl: ContentControlImpl {
54        /// Called when a change in scrolling state is detected, such as a
55        /// change in scroll position, extent, or viewport size.
56        ///
57        /// If you override this method, call the parent implementation to
58        /// ensure that the `ScrollChanged` event is raised.
59        fn on_scroll_changed(this, e: &ScrollChangedEventArgs);
60    }
61}
62// The `markup:` part is declared here and not generated: the public methods of the class are
63// declared for markup, the commands of the scroll buttons of the menu scroll viewer in the control themes
64// (`Command="{Binding LineUp, RelativeSource={RelativeSource TemplatedParent}}"`).
65ferroui_base::ferro_class_info!(ScrollViewer {
66    new: ScrollViewer::new,
67    markup: {
68        properties: [
69            BringIntoViewOnFocusChange: bool {
70                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::bring_into_view_on_focus_change_property()),
71                set: |this: &ferroui_base::Ref<ScrollViewer>, value: bool| this.set_value(ScrollViewer::bring_into_view_on_focus_change_property(), value)
72            },
73            HorizontalScrollBarVisibility: crate::primitives::ScrollBarVisibility {
74                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::horizontal_scroll_bar_visibility_property()),
75                set: |this: &ferroui_base::Ref<ScrollViewer>, value: crate::primitives::ScrollBarVisibility| this.set_value(ScrollViewer::horizontal_scroll_bar_visibility_property(), value)
76            },
77            VerticalScrollBarVisibility: crate::primitives::ScrollBarVisibility {
78                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::vertical_scroll_bar_visibility_property()),
79                set: |this: &ferroui_base::Ref<ScrollViewer>, value: crate::primitives::ScrollBarVisibility| this.set_value(ScrollViewer::vertical_scroll_bar_visibility_property(), value)
80            },
81            HorizontalSnapPointsType: crate::primitives::SnapPointsType {
82                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::horizontal_snap_points_type_property()),
83                set: |this: &ferroui_base::Ref<ScrollViewer>, value: crate::primitives::SnapPointsType| this.set_value(ScrollViewer::horizontal_snap_points_type_property(), value)
84            },
85            VerticalSnapPointsType: crate::primitives::SnapPointsType {
86                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::vertical_snap_points_type_property()),
87                set: |this: &ferroui_base::Ref<ScrollViewer>, value: crate::primitives::SnapPointsType| this.set_value(ScrollViewer::vertical_snap_points_type_property(), value)
88            },
89            HorizontalSnapPointsAlignment: crate::primitives::SnapPointsAlignment {
90                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::horizontal_snap_points_alignment_property()),
91                set: |this: &ferroui_base::Ref<ScrollViewer>, value: crate::primitives::SnapPointsAlignment| this.set_value(ScrollViewer::horizontal_snap_points_alignment_property(), value)
92            },
93            VerticalSnapPointsAlignment: crate::primitives::SnapPointsAlignment {
94                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::vertical_snap_points_alignment_property()),
95                set: |this: &ferroui_base::Ref<ScrollViewer>, value: crate::primitives::SnapPointsAlignment| this.set_value(ScrollViewer::vertical_snap_points_alignment_property(), value)
96            },
97            AllowAutoHide: bool {
98                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::allow_auto_hide_property()),
99                set: |this: &ferroui_base::Ref<ScrollViewer>, value: bool| this.set_value(ScrollViewer::allow_auto_hide_property(), value)
100            },
101            IsScrollChainingEnabled: bool {
102                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::is_scroll_chaining_enabled_property()),
103                set: |this: &ferroui_base::Ref<ScrollViewer>, value: bool| this.set_value(ScrollViewer::is_scroll_chaining_enabled_property(), value)
104            },
105            IsScrollInertiaEnabled: bool {
106                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::is_scroll_inertia_enabled_property()),
107                set: |this: &ferroui_base::Ref<ScrollViewer>, value: bool| this.set_value(ScrollViewer::is_scroll_inertia_enabled_property(), value)
108            },
109            IsDeferredScrollingEnabled: bool {
110                get: |this: &ferroui_base::Ref<ScrollViewer>| this.get_value(ScrollViewer::is_deferred_scrolling_enabled_property()),
111                set: |this: &ferroui_base::Ref<ScrollViewer>, value: bool| this.set_value(ScrollViewer::is_deferred_scrolling_enabled_property(), value)
112            },
113        ],
114        methods: [
115            fn LineUp() => ScrollViewer::line_up,
116            fn LineDown() => ScrollViewer::line_down,
117            fn LineLeft() => ScrollViewer::line_left,
118            fn LineRight() => ScrollViewer::line_right,
119            fn PageUp() => ScrollViewer::page_up,
120            fn PageDown() => ScrollViewer::page_down,
121            fn PageLeft() => ScrollViewer::page_left,
122            fn PageRight() => ScrollViewer::page_right,
123            fn ScrollToHome() => ScrollViewer::scroll_to_home,
124            fn ScrollToEnd() => ScrollViewer::scroll_to_end,
125        ],
126        fields: [
127            ScrollChangedEvent: ferroui_base::interactivity::RoutedEvent<crate::ScrollChangedEventArgs> => || *ScrollViewer::scroll_changed_event(),
128        ],
129        attributes: [
130            TemplatePart("PART_HorizontalScrollBar", type(ferroui_base::Ref<crate::primitives::ScrollBar>)),
131            TemplatePart("PART_VerticalScrollBar", type(ferroui_base::Ref<crate::primitives::ScrollBar>)),
132        ],
133    },
134});
135
136ferro_impl_classes!(ScrollViewer: StyledElementImpl, VisualImpl, LayoutableImpl, InteractiveImpl);
137
138impl ControlImpl for ScrollViewer {
139    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
140        crate::automation::peers::ScrollViewerAutomationPeer::new(this).upcast()
141    }
142}
143
144impl FerroObjectImpl for ScrollViewer {
145    fn constructed(this: &Self) {
146        Self::parent_constructed(this);
147
148        let weak = this.to_ref().downgrade();
149        let _ = this.layout_updated(move || {
150            if let Some(this) = weak.upgrade() {
151                this.raise_scroll_changed();
152            }
153        });
154    }
155
156    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
157        Self::parent_on_property_changed(this, change);
158
159        let property = change.property();
160
161        if property == Self::offset_property().as_property() {
162            this.calculated_properties_changed();
163        } else if property == Self::extent_property().as_property()
164            || property == Self::viewport_property().as_property()
165        {
166            this.coerce_value(Self::offset_property().as_property());
167        }
168    }
169}
170
171impl InputElementImpl for ScrollViewer {
172    fn on_got_focus(this: &Self, e: &FocusChangedEventArgs) {
173        Self::parent_on_got_focus(this, e);
174
175        if !e.is_source(this) && this.bring_into_view_on_focus_change() {
176            if let Some(c) = e.source().and_then(|source| source.cast::<Control>()) {
177                c.bring_into_view();
178            }
179        }
180    }
181
182    fn on_key_down(this: &Self, e: &KeyEventArgs) {
183        if e.key == Key::PageUp {
184            this.page_up();
185            e.set_handled(true);
186        } else if e.key == Key::PageDown {
187            this.page_down();
188            e.set_handled(true);
189        } else if e.is_source(this) {
190            let rtl = this.flow_direction() == FlowDirection::RightToLeft;
191            if e.key == Key::Left {
192                if rtl {
193                    this.line_right();
194                } else {
195                    this.line_left();
196                }
197                e.set_handled(true);
198            } else if e.key == Key::Right {
199                if rtl {
200                    this.line_left();
201                } else {
202                    this.line_right();
203                }
204                e.set_handled(true);
205            } else if e.key == Key::Up {
206                this.line_up();
207                e.set_handled(true);
208            } else if e.key == Key::Down {
209                this.line_down();
210                e.set_handled(true);
211            }
212        }
213    }
214
215    fn as_scrollable(this: &Self) -> Option<&dyn IScrollable> {
216        Some(this)
217    }
218}
219
220impl TemplatedControlImpl for ScrollViewer {
221    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
222        Self::parent_on_apply_template(this, e);
223
224        let old_subscription = this.scroll_bar_expand_subscription.borrow_mut().take();
225        if let Some(old_subscription) = old_subscription {
226            old_subscription.dispose();
227        }
228
229        let subscription = this.subscribe_to_scroll_bars(e);
230        *this.scroll_bar_expand_subscription.borrow_mut() = subscription;
231    }
232}
233
234impl ContentControlImpl for ScrollViewer {
235    fn register_content_presenter(this: &Self, presenter: &ContentPresenter) -> bool {
236        let old_subscription = this.child_subscription.borrow_mut().take();
237        if let Some(old_subscription) = old_subscription {
238            old_subscription.dispose();
239        }
240
241        if Self::parent_register_content_presenter(this, presenter) {
242            if let Some(presenter) = this.presenter() {
243                let weak = this.to_ref().downgrade();
244                let subscription =
245                    FerroObjectExtensions::get_observable(fo(&presenter), ContentPresenter::child_property())
246                        .subscribe_fn(move |child| {
247                            if let Some(this) = weak.upgrade() {
248                                this.child_changed(child);
249                            }
250                        });
251                *this.child_subscription.borrow_mut() = Some(subscription);
252            }
253            return true;
254        }
255
256        false
257    }
258}
259
260impl ScrollViewerImpl for ScrollViewer {
261    fn on_scroll_changed(this: &Self, e: &ScrollChangedEventArgs) {
262        this.raise_event(e);
263    }
264}
265
266impl IScrollable for ScrollViewer {
267    fn extent(&self) -> Size {
268        ScrollViewer::extent(self)
269    }
270
271    fn offset(&self) -> Vector {
272        ScrollViewer::offset(self)
273    }
274
275    fn set_offset(&self, value: Vector) {
276        ScrollViewer::set_offset(self, value)
277    }
278
279    fn viewport(&self) -> Size {
280        ScrollViewer::viewport(self)
281    }
282
283    fn can_horizontally_scroll(&self) -> bool {
284        ScrollViewer::can_horizontally_scroll(self)
285    }
286
287    fn can_vertically_scroll(&self) -> bool {
288        ScrollViewer::can_vertically_scroll(self)
289    }
290}
291
292impl IScrollAnchorProvider for ScrollViewer {
293    fn current_anchor(&self) -> Option<Ref<Control>> {
294        ScrollViewer::current_anchor(self)
295    }
296
297    fn register_anchor_candidate(&self, element: &Ref<Control>) {
298        ScrollViewer::register_anchor_candidate(self, element)
299    }
300
301    fn unregister_anchor_candidate(&self, element: &Ref<Control>) {
302        ScrollViewer::unregister_anchor_candidate(self, element)
303    }
304}
305
306impl ScrollViewer {
307    /// The named parts expected in the control template, in addition to
308    /// those of the base class.
309    pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] = &[
310        TemplatePartAttribute::new("PART_HorizontalScrollBar", <ScrollBar as StaticType>::TYPE),
311        TemplatePartAttribute::new("PART_VerticalScrollBar", <ScrollBar as StaticType>::TYPE),
312    ];
313
314    /// The default small (line) change of the scroll viewer.
315    pub(crate) const DEFAULT_SMALL_CHANGE: f64 = 16.0;
316}
317
318ferroui_base::ferro_properties! { impl ScrollViewer {
319    ferro_property!(
320        /// Defines the `BringIntoViewOnFocusChange` property.
321        pub fn bring_into_view_on_focus_change_property() -> AttachedProperty<bool> {
322            FerroProperty::register_attached::<ScrollViewer, Control, _>("BringIntoViewOnFocusChange", true)
323        }
324    );
325
326    ferro_property!(
327        /// Defines the `Extent` property.
328        pub fn extent_property() -> DirectProperty<ScrollViewer, Size> {
329            FerroProperty::register_direct::<ScrollViewer, _>("Extent", |o| o.extent(), None, Size::default())
330        }
331    );
332
333    ferro_property!(
334        /// Defines the `Offset` property.
335        pub fn offset_property() -> StyledProperty<Vector> {
336            FerroProperty::register_with::<ScrollViewer, _>(
337                "Offset",
338                StyledPropertyOptions::new(Vector::default()).coerce(ScrollViewer::coerce_offset),
339            )
340        }
341    );
342
343    ferro_property!(
344        /// Defines the `Viewport` property.
345        pub fn viewport_property() -> DirectProperty<ScrollViewer, Size> {
346            FerroProperty::register_direct::<ScrollViewer, _>("Viewport", |o| o.viewport(), None, Size::default())
347        }
348    );
349
350    ferro_property!(
351        /// Defines the `LargeChange` property.
352        pub fn large_change_property() -> DirectProperty<ScrollViewer, Size> {
353            FerroProperty::register_direct::<ScrollViewer, _>(
354                "LargeChange",
355                |o| o.large_change(),
356                None,
357                Size::default(),
358            )
359        }
360    );
361
362    ferro_property!(
363        /// Defines the `SmallChange` property.
364        pub fn small_change_property() -> DirectProperty<ScrollViewer, Size> {
365            FerroProperty::register_direct::<ScrollViewer, _>(
366                "SmallChange",
367                |o| o.small_change(),
368                None,
369                Size::default(),
370            )
371        }
372    );
373
374    ferro_property!(
375        /// Defines the `ScrollBarMaximum` property.
376        pub fn scroll_bar_maximum_property() -> DirectProperty<ScrollViewer, Vector> {
377            FerroProperty::register_direct::<ScrollViewer, _>(
378                "ScrollBarMaximum",
379                |o| o.scroll_bar_maximum(),
380                None,
381                Vector::default(),
382            )
383        }
384    );
385
386    ferro_property!(
387        /// Defines the `HorizontalScrollBarVisibility` property.
388        pub fn horizontal_scroll_bar_visibility_property() -> AttachedProperty<ScrollBarVisibility> {
389            FerroProperty::register_attached::<ScrollViewer, Control, _>(
390                "HorizontalScrollBarVisibility",
391                ScrollBarVisibility::Disabled,
392            )
393        }
394    );
395
396    ferro_property!(
397        /// Defines the `HorizontalSnapPointsType` property.
398        pub fn horizontal_snap_points_type_property() -> AttachedProperty<SnapPointsType> {
399            FerroProperty::register_attached::<ScrollViewer, Control, _>(
400                "HorizontalSnapPointsType",
401                SnapPointsType::None,
402            )
403        }
404    );
405
406    ferro_property!(
407        /// Defines the `VerticalSnapPointsType` property.
408        pub fn vertical_snap_points_type_property() -> AttachedProperty<SnapPointsType> {
409            FerroProperty::register_attached::<ScrollViewer, Control, _>("VerticalSnapPointsType", SnapPointsType::None)
410        }
411    );
412
413    ferro_property!(
414        /// Defines the `HorizontalSnapPointsAlignment` property.
415        pub fn horizontal_snap_points_alignment_property() -> AttachedProperty<SnapPointsAlignment> {
416            FerroProperty::register_attached::<ScrollViewer, Control, _>(
417                "HorizontalSnapPointsAlignment",
418                SnapPointsAlignment::Near,
419            )
420        }
421    );
422
423    ferro_property!(
424        /// Defines the `VerticalSnapPointsAlignment` property.
425        pub fn vertical_snap_points_alignment_property() -> AttachedProperty<SnapPointsAlignment> {
426            FerroProperty::register_attached::<ScrollViewer, Control, _>(
427                "VerticalSnapPointsAlignment",
428                SnapPointsAlignment::Near,
429            )
430        }
431    );
432
433    ferro_property!(
434        /// Defines the `VerticalScrollBarVisibility` property.
435        pub fn vertical_scroll_bar_visibility_property() -> AttachedProperty<ScrollBarVisibility> {
436            FerroProperty::register_attached::<ScrollViewer, Control, _>(
437                "VerticalScrollBarVisibility",
438                ScrollBarVisibility::Auto,
439            )
440        }
441    );
442
443    ferro_property!(
444        /// Defines the `IsExpanded` property.
445        pub fn is_expanded_property() -> DirectProperty<ScrollViewer, bool> {
446            ScrollBar::is_expanded_property().add_owner::<ScrollViewer>(|o| o.is_expanded(), None, None)
447        }
448    );
449
450    ferro_property!(
451        /// Defines the `AllowAutoHide` property.
452        pub fn allow_auto_hide_property() -> AttachedProperty<bool> {
453            FerroProperty::register_attached::<ScrollViewer, Control, _>("AllowAutoHide", true)
454        }
455    );
456
457    ferro_property!(
458        /// Defines the `IsScrollChainingEnabled` property.
459        pub fn is_scroll_chaining_enabled_property() -> AttachedProperty<bool> {
460            FerroProperty::register_attached::<ScrollViewer, Control, _>("IsScrollChainingEnabled", true)
461        }
462    );
463
464    ferro_property!(
465        /// Defines the `IsScrollInertiaEnabled` property.
466        pub fn is_scroll_inertia_enabled_property() -> AttachedProperty<bool> {
467            FerroProperty::register_attached::<ScrollViewer, Control, _>("IsScrollInertiaEnabled", true)
468        }
469    );
470
471    ferro_property!(
472        /// Defines the `IsDeferredScrollingEnabled` property.
473        pub fn is_deferred_scrolling_enabled_property() -> AttachedProperty<bool> {
474            FerroProperty::register_attached::<ScrollViewer, Control, _>("IsDeferredScrollingEnabled", false)
475        }
476    );
477} }
478
479impl ScrollViewer {
480    ferro_routed_event!(
481        /// Defines the `ScrollChanged` event.
482        pub fn scroll_changed_event() -> RoutedEvent<ScrollChangedEventArgs> {
483            RoutedEvent::register::<ScrollViewer, _>("ScrollChanged", RoutingStrategies::BUBBLE)
484        }
485    );
486
487    fn static_constructor() {
488        Self::scroll_changed_event();
489
490        register_scroll_anchor_provider::<ScrollViewer>();
491    }
492
493    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
494    pub fn construct() -> Self {
495        Self {
496            base: ContentControl::construct(),
497            child_subscription: RefCell::new(None),
498            logical_scrollable: RefCell::new(None),
499            extent: Cell::new(Size::default()),
500            viewport: Cell::new(Size::default()),
501            old_extent: Cell::new(Size::default()),
502            old_offset: Cell::new(Vector::default()),
503            old_maximum: Cell::new(Vector::default()),
504            old_viewport: Cell::new(Size::default()),
505            large_change: Cell::new(Size::default()),
506            small_change: Cell::new(Size::new(Self::DEFAULT_SMALL_CHANGE, Self::DEFAULT_SMALL_CHANGE)),
507            is_expanded: Cell::new(false),
508            scroll_bar_expand_subscription: RefCell::new(None),
509            horizontal_scroll_bar: RefCell::new(None),
510            vertical_scroll_bar: RefCell::new(None),
511        }
512    }
513
514    /// Initializes a new instance of the class.
515    pub fn new() -> Ref<Self> {
516        instantiate(Self::construct())
517    }
518
519    /// Gets the horizontal scroll bar from the applied template, if any.
520    pub(crate) fn horizontal_scroll_bar(&self) -> Option<Ref<ScrollBar>> {
521        self.horizontal_scroll_bar.borrow().clone()
522    }
523
524    /// Gets the vertical scroll bar from the applied template, if any.
525    pub(crate) fn vertical_scroll_bar(&self) -> Option<Ref<ScrollBar>> {
526        self.vertical_scroll_bar.borrow().clone()
527    }
528
529    /// Occurs when changes are detected to the scroll position, extent, or
530    /// viewport size.
531    pub fn scroll_changed(
532        &self,
533        handler: impl Fn(&Interactive, &ScrollChangedEventArgs) + 'static,
534    ) -> RoutedEventHandlerToken {
535        self.add_handler(Self::scroll_changed_event(), handler)
536    }
537
538    /// Gets a value that determines whether the scroll viewer uses a
539    /// bring-into-view scroll behavior when an item in the view gets focus.
540    ///
541    /// True to use a behavior that brings focused items into view. False to
542    /// use a behavior that focused items do not automatically scroll into
543    /// view. The default is true.
544    ///
545    /// The value can either be set explicitly on a scroll viewer, or the
546    /// attached `ScrollViewer.BringIntoViewOnFocusChange` property can be
547    /// set on an element that hosts a scroll viewer.
548    pub fn bring_into_view_on_focus_change(&self) -> bool {
549        self.get_value(Self::bring_into_view_on_focus_change_property())
550    }
551
552    /// Sets a value that determines whether the scroll viewer uses a
553    /// bring-into-view scroll behavior when an item in the view gets focus.
554    pub fn set_bring_into_view_on_focus_change(&self, value: bool) {
555        self.set_value(Self::bring_into_view_on_focus_change_property(), value)
556    }
557
558    /// Gets the extent of the scrollable content.
559    pub fn extent(&self) -> Size {
560        self.extent.get()
561    }
562
563    pub(crate) fn set_extent(&self, value: Size) {
564        if self.set_and_raise_cell(Self::extent_property(), &self.extent, value) {
565            self.calculated_properties_changed();
566        }
567    }
568
569    /// Gets the current scroll offset.
570    pub fn offset(&self) -> Vector {
571        self.get_value(Self::offset_property())
572    }
573
574    /// Sets the current scroll offset.
575    pub fn set_offset(&self, value: Vector) {
576        self.set_value(Self::offset_property(), value)
577    }
578
579    /// Gets the size of the viewport on the scrollable content.
580    pub fn viewport(&self) -> Size {
581        self.viewport.get()
582    }
583
584    pub(crate) fn set_viewport(&self, value: Size) {
585        if self.set_and_raise_cell(Self::viewport_property(), &self.viewport, value) {
586            self.calculated_properties_changed();
587        }
588    }
589
590    /// Gets the large (page) change value for the scroll viewer.
591    pub fn large_change(&self) -> Size {
592        self.large_change.get()
593    }
594
595    /// Gets the small (line) change value for the scroll viewer.
596    pub fn small_change(&self) -> Size {
597        self.small_change.get()
598    }
599
600    /// Gets the horizontal scrollbar visibility.
601    pub fn horizontal_scroll_bar_visibility(&self) -> ScrollBarVisibility {
602        self.get_value(Self::horizontal_scroll_bar_visibility_property())
603    }
604
605    /// Sets the horizontal scrollbar visibility.
606    pub fn set_horizontal_scroll_bar_visibility(&self, value: ScrollBarVisibility) {
607        self.set_value(Self::horizontal_scroll_bar_visibility_property(), value)
608    }
609
610    /// Gets the vertical scrollbar visibility.
611    pub fn vertical_scroll_bar_visibility(&self) -> ScrollBarVisibility {
612        self.get_value(Self::vertical_scroll_bar_visibility_property())
613    }
614
615    /// Sets the vertical scrollbar visibility.
616    pub fn set_vertical_scroll_bar_visibility(&self, value: ScrollBarVisibility) {
617        self.set_value(Self::vertical_scroll_bar_visibility_property(), value)
618    }
619
620    /// Gets a value indicating whether the viewer can scroll horizontally.
621    pub fn can_horizontally_scroll(&self) -> bool {
622        self.horizontal_scroll_bar_visibility() != ScrollBarVisibility::Disabled
623    }
624
625    /// Gets a value indicating whether the viewer can scroll vertically.
626    pub fn can_vertically_scroll(&self) -> bool {
627        self.vertical_scroll_bar_visibility() != ScrollBarVisibility::Disabled
628    }
629
630    /// The currently chosen anchor element to use for scroll anchoring.
631    pub fn current_anchor(&self) -> Option<Ref<Control>> {
632        let presenter = self.presenter()?;
633        as_scroll_anchor_provider(&presenter)?.current_anchor()
634    }
635
636    /// Gets the maximum scrolling distance (which is the extent minus the
637    /// viewport).
638    pub fn scroll_bar_maximum(&self) -> Vector {
639        let extent = self.extent.get();
640        let viewport = self.viewport.get();
641        Vector::new(
642            Self::max(extent.width - viewport.width, 0.0),
643            Self::max(extent.height - viewport.height, 0.0),
644        )
645    }
646
647    /// Gets a value that indicates whether any scrollbar is expanded.
648    pub fn is_expanded(&self) -> bool {
649        self.is_expanded.get()
650    }
651
652    fn set_is_expanded(&self, value: bool) {
653        self.set_and_raise_cell(ScrollBar::is_expanded_property(), &self.is_expanded, value);
654    }
655
656    /// Gets how scroll gesture reacts to the snap points along the
657    /// horizontal axis.
658    pub fn horizontal_snap_points_type(&self) -> SnapPointsType {
659        self.get_value(Self::horizontal_snap_points_type_property())
660    }
661
662    /// Sets how scroll gesture reacts to the snap points along the
663    /// horizontal axis.
664    pub fn set_horizontal_snap_points_type(&self, value: SnapPointsType) {
665        self.set_value(Self::horizontal_snap_points_type_property(), value)
666    }
667
668    /// Gets how scroll gesture reacts to the snap points along the vertical
669    /// axis.
670    pub fn vertical_snap_points_type(&self) -> SnapPointsType {
671        self.get_value(Self::vertical_snap_points_type_property())
672    }
673
674    /// Sets how scroll gesture reacts to the snap points along the vertical
675    /// axis.
676    pub fn set_vertical_snap_points_type(&self, value: SnapPointsType) {
677        self.set_value(Self::vertical_snap_points_type_property(), value)
678    }
679
680    /// Gets how the existing snap points are horizontally aligned versus the
681    /// initial viewport.
682    pub fn horizontal_snap_points_alignment(&self) -> SnapPointsAlignment {
683        self.get_value(Self::horizontal_snap_points_alignment_property())
684    }
685
686    /// Sets how the existing snap points are horizontally aligned versus the
687    /// initial viewport.
688    pub fn set_horizontal_snap_points_alignment(&self, value: SnapPointsAlignment) {
689        self.set_value(Self::horizontal_snap_points_alignment_property(), value)
690    }
691
692    /// Gets how the existing snap points are vertically aligned versus the
693    /// initial viewport.
694    pub fn vertical_snap_points_alignment(&self) -> SnapPointsAlignment {
695        self.get_value(Self::vertical_snap_points_alignment_property())
696    }
697
698    /// Sets how the existing snap points are vertically aligned versus the
699    /// initial viewport.
700    pub fn set_vertical_snap_points_alignment(&self, value: SnapPointsAlignment) {
701        self.set_value(Self::vertical_snap_points_alignment_property(), value)
702    }
703
704    /// Gets a value that indicates whether scrollbars can hide itself when
705    /// user is not interacting with it.
706    pub fn allow_auto_hide(&self) -> bool {
707        self.get_value(Self::allow_auto_hide_property())
708    }
709
710    /// Sets a value that indicates whether scrollbars can hide itself when
711    /// user is not interacting with it.
712    pub fn set_allow_auto_hide(&self, value: bool) {
713        self.set_value(Self::allow_auto_hide_property(), value)
714    }
715
716    /// Gets if scroll chaining is enabled. The default value is true.
717    ///
718    /// After a user hits a scroll limit on an element that has been nested
719    /// within another scrollable element, you can specify whether that
720    /// parent element should continue the scrolling operation begun in its
721    /// child element. This is called scroll chaining.
722    pub fn is_scroll_chaining_enabled(&self) -> bool {
723        self.get_value(Self::is_scroll_chaining_enabled_property())
724    }
725
726    /// Sets if scroll chaining is enabled.
727    pub fn set_is_scroll_chaining_enabled(&self, value: bool) {
728        self.set_value(Self::is_scroll_chaining_enabled_property(), value)
729    }
730
731    /// Gets whether scroll gestures should include inertia in their behavior
732    /// and value.
733    pub fn is_scroll_inertia_enabled(&self) -> bool {
734        self.get_value(Self::is_scroll_inertia_enabled_property())
735    }
736
737    /// Sets whether scroll gestures should include inertia in their behavior
738    /// and value.
739    pub fn set_is_scroll_inertia_enabled(&self, value: bool) {
740        self.set_value(Self::is_scroll_inertia_enabled_property(), value)
741    }
742
743    /// Gets whether dragging of thumb elements should update the scroll
744    /// viewer only when the user releases the mouse.
745    pub fn is_deferred_scrolling_enabled(&self) -> bool {
746        self.get_value(Self::is_deferred_scrolling_enabled_property())
747    }
748
749    /// Sets whether dragging of thumb elements should update the scroll
750    /// viewer only when the user releases the mouse.
751    pub fn set_is_deferred_scrolling_enabled(&self, value: bool) {
752        self.set_value(Self::is_deferred_scrolling_enabled_property(), value)
753    }
754
755    /// Scrolls the content up one line.
756    pub fn line_up(&self) {
757        let offset = self.offset() - Vector::new(0.0, self.small_change.get().height);
758        self.set_current_value(Self::offset_property(), offset);
759    }
760
761    /// Scrolls the content down one line.
762    pub fn line_down(&self) {
763        let offset = self.offset() + Vector::new(0.0, self.small_change.get().height);
764        self.set_current_value(Self::offset_property(), offset);
765    }
766
767    /// Scrolls the content left one line.
768    pub fn line_left(&self) {
769        let offset = self.offset() - Vector::new(self.small_change.get().width, 0.0);
770        self.set_current_value(Self::offset_property(), offset);
771    }
772
773    /// Scrolls the content right one line.
774    pub fn line_right(&self) {
775        let offset = self.offset() + Vector::new(self.small_change.get().width, 0.0);
776        self.set_current_value(Self::offset_property(), offset);
777    }
778
779    /// Scrolls the content upward by one page.
780    pub fn page_up(&self) {
781        let offset = self.offset();
782        let offset = offset.with_y((offset.y - self.viewport.get().height).max(0.0));
783        self.set_current_value(Self::offset_property(), offset);
784    }
785
786    /// Scrolls the content downward by one page.
787    pub fn page_down(&self) {
788        let offset = self.offset();
789        let offset = offset.with_y((offset.y + self.viewport.get().height).min(self.scroll_bar_maximum().y));
790        self.set_current_value(Self::offset_property(), offset);
791    }
792
793    /// Scrolls the content left by one page.
794    pub fn page_left(&self) {
795        let offset = self.offset();
796        let offset = offset.with_x((offset.x - self.viewport.get().width).max(0.0));
797        self.set_current_value(Self::offset_property(), offset);
798    }
799
800    /// Scrolls the content right by one page.
801    pub fn page_right(&self) {
802        let offset = self.offset();
803        let offset = offset.with_x((offset.x + self.viewport.get().width).min(self.scroll_bar_maximum().x));
804        self.set_current_value(Self::offset_property(), offset);
805    }
806
807    /// Scrolls to the top-left corner of the content.
808    pub fn scroll_to_home(&self) {
809        self.set_current_value(
810            Self::offset_property(),
811            Vector::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
812        );
813    }
814
815    /// Scrolls to the bottom-left corner of the content.
816    pub fn scroll_to_end(&self) {
817        self.set_current_value(Self::offset_property(), Vector::new(f64::NEG_INFINITY, f64::INFINITY));
818    }
819
820    /// Gets the value of the `BringIntoViewOnFocusChange` attached property
821    /// of `control`.
822    pub fn get_bring_into_view_on_focus_change(control: &Control) -> bool {
823        control.get_value(Self::bring_into_view_on_focus_change_property())
824    }
825
826    /// Sets the value of the `BringIntoViewOnFocusChange` attached property
827    /// on `control`.
828    pub fn set_bring_into_view_on_focus_change_on(control: &Control, value: bool) {
829        control.set_value(Self::bring_into_view_on_focus_change_property(), value);
830    }
831
832    /// Gets the value of the `HorizontalScrollBarVisibility` attached
833    /// property of `control`.
834    pub fn get_horizontal_scroll_bar_visibility(control: &Control) -> ScrollBarVisibility {
835        control.get_value(Self::horizontal_scroll_bar_visibility_property())
836    }
837
838    /// Sets the value of the `HorizontalScrollBarVisibility` attached
839    /// property on `control`.
840    pub fn set_horizontal_scroll_bar_visibility_on(control: &Control, value: ScrollBarVisibility) {
841        control.set_value(Self::horizontal_scroll_bar_visibility_property(), value);
842    }
843
844    /// Gets the value of the `HorizontalSnapPointsType` attached property of
845    /// `control`.
846    pub fn get_horizontal_snap_points_type(control: &Control) -> SnapPointsType {
847        control.get_value(Self::horizontal_snap_points_type_property())
848    }
849
850    /// Sets the value of the `HorizontalSnapPointsType` attached property on
851    /// `control`.
852    pub fn set_horizontal_snap_points_type_on(control: &Control, value: SnapPointsType) {
853        control.set_value(Self::horizontal_snap_points_type_property(), value);
854    }
855
856    /// Gets the value of the `VerticalSnapPointsType` attached property of
857    /// `control`.
858    pub fn get_vertical_snap_points_type(control: &Control) -> SnapPointsType {
859        control.get_value(Self::vertical_snap_points_type_property())
860    }
861
862    /// Sets the value of the `VerticalSnapPointsType` attached property on
863    /// `control`.
864    pub fn set_vertical_snap_points_type_on(control: &Control, value: SnapPointsType) {
865        control.set_value(Self::vertical_snap_points_type_property(), value);
866    }
867
868    /// Gets the value of the `HorizontalSnapPointsAlignment` attached
869    /// property of `control`.
870    pub fn get_horizontal_snap_points_alignment(control: &Control) -> SnapPointsAlignment {
871        control.get_value(Self::horizontal_snap_points_alignment_property())
872    }
873
874    /// Sets the value of the `HorizontalSnapPointsAlignment` attached
875    /// property on `control`.
876    pub fn set_horizontal_snap_points_alignment_on(control: &Control, value: SnapPointsAlignment) {
877        control.set_value(Self::horizontal_snap_points_alignment_property(), value);
878    }
879
880    /// Gets the value of the `VerticalSnapPointsAlignment` attached property
881    /// of `control`.
882    pub fn get_vertical_snap_points_alignment(control: &Control) -> SnapPointsAlignment {
883        control.get_value(Self::vertical_snap_points_alignment_property())
884    }
885
886    /// Sets the value of the `VerticalSnapPointsAlignment` attached property
887    /// on `control`.
888    pub fn set_vertical_snap_points_alignment_on(control: &Control, value: SnapPointsAlignment) {
889        control.set_value(Self::vertical_snap_points_alignment_property(), value);
890    }
891
892    /// Gets the value of the `VerticalScrollBarVisibility` attached property
893    /// of `control`.
894    pub fn get_vertical_scroll_bar_visibility(control: &Control) -> ScrollBarVisibility {
895        control.get_value(Self::vertical_scroll_bar_visibility_property())
896    }
897
898    /// Sets the value of the `AllowAutoHide` attached property on `control`.
899    pub fn set_allow_auto_hide_on(control: &Control, value: bool) {
900        control.set_value(Self::allow_auto_hide_property(), value);
901    }
902
903    /// Gets the value of the `AllowAutoHide` attached property of `control`.
904    pub fn get_allow_auto_hide(control: &Control) -> bool {
905        control.get_value(Self::allow_auto_hide_property())
906    }
907
908    /// Sets the value of the `IsScrollChainingEnabled` attached property on
909    /// `control`.
910    ///
911    /// After a user hits a scroll limit on an element that has been nested
912    /// within another scrollable element, you can specify whether that
913    /// parent element should continue the scrolling operation begun in its
914    /// child element. This is called scroll chaining.
915    pub fn set_is_scroll_chaining_enabled_on(control: &Control, value: bool) {
916        control.set_value(Self::is_scroll_chaining_enabled_property(), value);
917    }
918
919    /// Gets the value of the `IsScrollChainingEnabled` attached property of
920    /// `control`.
921    ///
922    /// After a user hits a scroll limit on an element that has been nested
923    /// within another scrollable element, you can specify whether that
924    /// parent element should continue the scrolling operation begun in its
925    /// child element. This is called scroll chaining.
926    pub fn get_is_scroll_chaining_enabled(control: &Control) -> bool {
927        control.get_value(Self::is_scroll_chaining_enabled_property())
928    }
929
930    /// Sets the value of the `VerticalScrollBarVisibility` attached property
931    /// on `control`.
932    pub fn set_vertical_scroll_bar_visibility_on(control: &Control, value: ScrollBarVisibility) {
933        control.set_value(Self::vertical_scroll_bar_visibility_property(), value);
934    }
935
936    /// Gets whether scroll gestures should include inertia in their behavior
937    /// and value.
938    pub fn get_is_scroll_inertia_enabled(control: &Control) -> bool {
939        control.get_value(Self::is_scroll_inertia_enabled_property())
940    }
941
942    /// Sets whether scroll gestures should include inertia in their behavior
943    /// and value.
944    pub fn set_is_scroll_inertia_enabled_on(control: &Control, value: bool) {
945        control.set_value(Self::is_scroll_inertia_enabled_property(), value);
946    }
947
948    /// Gets whether dragging of thumb elements should update the scroll
949    /// viewer only when the user releases the mouse.
950    pub fn get_is_deferred_scrolling_enabled(control: &Control) -> bool {
951        control.get_value(Self::is_deferred_scrolling_enabled_property())
952    }
953
954    /// Sets whether dragging of thumb elements should update the scroll
955    /// viewer only when the user releases the mouse.
956    pub fn set_is_deferred_scrolling_enabled_on(control: &Control, value: bool) {
957        control.set_value(Self::is_deferred_scrolling_enabled_property(), value);
958    }
959
960    /// Registers a control as a potential scroll anchor candidate.
961    pub fn register_anchor_candidate(&self, element: &Ref<Control>) {
962        if let Some(presenter) = self.presenter() {
963            if let Some(provider) = as_scroll_anchor_provider(&presenter) {
964                provider.register_anchor_candidate(element);
965            }
966        }
967    }
968
969    /// Unregisters a control as a potential scroll anchor candidate.
970    pub fn unregister_anchor_candidate(&self, element: &Ref<Control>) {
971        if let Some(presenter) = self.presenter() {
972            if let Some(provider) = as_scroll_anchor_provider(&presenter) {
973                provider.unregister_anchor_candidate(element);
974            }
975        }
976    }
977
978    /// Coerces an offset to the scrollable range of `sender`.
979    pub(crate) fn coerce_offset(sender: &FerroObject, value: Vector) -> Vector {
980        let extent = sender.get_direct_value(Self::extent_property());
981        let viewport = sender.get_direct_value(Self::viewport_property());
982
983        let max_x = (extent.width - viewport.width).max(0.0);
984        let max_y = (extent.height - viewport.height).max(0.0);
985        Vector::new(Self::clamp(value.x, 0.0, max_x), Self::clamp(value.y, 0.0, max_y))
986    }
987
988    fn clamp(value: f64, min: f64, max: f64) -> f64 {
989        // A NaN offset must never survive. Offset is two-way coerced between
990        // the scroll viewer and its presenter; because NaN != NaN, a NaN
991        // offset never compares equal, so the coerce/raise cycle never
992        // converges and recurses until it overflows the stack.
993        if value.is_nan() {
994            return min;
995        }
996
997        if value < min {
998            min
999        } else if value > max {
1000            max
1001        } else {
1002            value
1003        }
1004    }
1005
1006    fn max(x: f64, y: f64) -> f64 {
1007        // The reference maximum propagates NaN, which is then replaced.
1008        if x.is_nan() || y.is_nan() {
1009            return 0.0;
1010        }
1011        x.max(y)
1012    }
1013
1014    fn child_changed(&self, child: Option<Ref<Control>>) {
1015        let old = self.logical_scrollable.borrow_mut().take();
1016        if let Some((_, subscription)) = old {
1017            subscription.dispose();
1018        }
1019
1020        if let Some(child) = child {
1021            if let Some(logical) = as_logical_scrollable(&child) {
1022                let weak = self.to_ref().downgrade();
1023                let subscription = logical.scroll_invalidated(Rc::new(move || {
1024                    if let Some(this) = weak.upgrade() {
1025                        this.calculated_properties_changed();
1026                    }
1027                }));
1028                *self.logical_scrollable.borrow_mut() = Some((child.clone(), subscription));
1029            }
1030        }
1031
1032        self.calculated_properties_changed();
1033    }
1034
1035    fn calculated_properties_changed(&self) {
1036        let new_maximum = self.scroll_bar_maximum();
1037        let old_maximum = self.old_maximum.get();
1038        if new_maximum != old_maximum {
1039            self.raise_direct_property_changed(Self::scroll_bar_maximum_property(), &old_maximum, &new_maximum);
1040            self.old_maximum.set(new_maximum);
1041        }
1042
1043        let logical_scrollable = self
1044            .logical_scrollable
1045            .borrow()
1046            .as_ref()
1047            .map(|(child, _)| child.clone());
1048        let logical = logical_scrollable
1049            .as_ref()
1050            .and_then(|child| as_logical_scrollable(child))
1051            .filter(|logical| logical.is_logical_scroll_enabled());
1052
1053        if let Some(logical) = logical {
1054            self.set_and_raise_cell(Self::small_change_property(), &self.small_change, logical.scroll_size());
1055            self.set_and_raise_cell(
1056                Self::large_change_property(),
1057                &self.large_change,
1058                logical.page_scroll_size(),
1059            );
1060        } else {
1061            self.set_and_raise_cell(
1062                Self::small_change_property(),
1063                &self.small_change,
1064                Size::new(Self::DEFAULT_SMALL_CHANGE, Self::DEFAULT_SMALL_CHANGE),
1065            );
1066            self.set_and_raise_cell(Self::large_change_property(), &self.large_change, self.viewport());
1067        }
1068    }
1069
1070    fn subscribe_to_scroll_bars(&self, e: &TemplateAppliedEventArgs) -> Option<Rc<dyn IDisposable>> {
1071        let horizontal_scroll_bar = e.name_scope().find_as::<ScrollBar>("PART_HorizontalScrollBar");
1072        let vertical_scroll_bar = e.name_scope().find_as::<ScrollBar>("PART_VerticalScrollBar");
1073
1074        *self.horizontal_scroll_bar.borrow_mut() = horizontal_scroll_bar.clone();
1075        *self.vertical_scroll_bar.borrow_mut() = vertical_scroll_bar.clone();
1076
1077        let horizontal_expanded = horizontal_scroll_bar.map(|scroll_bar| {
1078            FerroObjectExtensions::get_observable(fo(&scroll_bar), ScrollBar::is_expanded_property())
1079        });
1080        let vertical_expanded = vertical_scroll_bar.map(|scroll_bar| {
1081            FerroObjectExtensions::get_observable(fo(&scroll_bar), ScrollBar::is_expanded_property())
1082        });
1083
1084        let weak = self.to_ref().downgrade();
1085        let on_scroll_bar_expanded_changed = move |is_expanded: bool| {
1086            if let Some(this) = weak.upgrade() {
1087                this.set_is_expanded(is_expanded);
1088            }
1089        };
1090
1091        match (horizontal_expanded, vertical_expanded) {
1092            (Some(horizontal_expanded), Some(vertical_expanded)) => {
1093                // The latest values of both scroll bars are combined once
1094                // each of them has produced a value.
1095                let latest: Rc<Cell<(Option<bool>, Option<bool>)>> = Rc::new(Cell::new((None, None)));
1096                let publish = Rc::new(move |latest: (Option<bool>, Option<bool>)| {
1097                    if let (Some(h), Some(v)) = latest {
1098                        on_scroll_bar_expanded_changed(h || v);
1099                    }
1100                });
1101
1102                let horizontal = {
1103                    let latest = latest.clone();
1104                    let publish = publish.clone();
1105                    horizontal_expanded.subscribe_fn(move |h| {
1106                        latest.set((Some(h), latest.get().1));
1107                        publish(latest.get());
1108                    })
1109                };
1110                let vertical = vertical_expanded.subscribe_fn(move |v| {
1111                    latest.set((latest.get().0, Some(v)));
1112                    publish(latest.get());
1113                });
1114
1115                let subscription: Rc<dyn IDisposable> =
1116                    Rc::new(CompositeDisposable::from_disposables([horizontal, vertical]));
1117                Some(subscription)
1118            }
1119            (Some(actual_expanded), None) | (None, Some(actual_expanded)) => {
1120                Some(actual_expanded.subscribe_fn(on_scroll_bar_expanded_changed))
1121            }
1122            (None, None) => None,
1123        }
1124    }
1125
1126    fn raise_scroll_changed(&self) {
1127        let extent = self.extent();
1128        let offset = self.offset();
1129        let viewport = self.viewport();
1130        let old_extent = self.old_extent.get();
1131        let old_viewport = self.old_viewport.get();
1132
1133        let extent_delta = Vector::new(extent.width - old_extent.width, extent.height - old_extent.height);
1134        let offset_delta = offset - self.old_offset.get();
1135        let viewport_delta = Vector::new(
1136            viewport.width - old_viewport.width,
1137            viewport.height - old_viewport.height,
1138        );
1139
1140        if !extent_delta.nearly_equals(Vector::default())
1141            || !offset_delta.nearly_equals(Vector::default())
1142            || !viewport_delta.nearly_equals(Vector::default())
1143        {
1144            let e = ScrollChangedEventArgs::new(extent_delta, offset_delta, viewport_delta);
1145            self.on_scroll_changed(&e);
1146
1147            self.old_extent.set(self.extent());
1148            self.old_offset.set(self.offset());
1149            self.old_viewport.set(self.viewport());
1150        }
1151    }
1152}
1153
1154/// The object as its root class, for calls that would otherwise resolve to
1155/// a member of an intermediate class.
1156fn fo(object: &ferroui_base::FerroObject) -> &ferroui_base::FerroObject {
1157    object
1158}