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#[repr(C)]
32pub struct ScrollViewer {
33 base: ContentControl,
34 child_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
35 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 fn on_scroll_changed(this, e: &ScrollChangedEventArgs);
60 }
61}
62ferroui_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 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 pub(crate) const DEFAULT_SMALL_CHANGE: f64 = 16.0;
316}
317
318ferroui_base::ferro_properties! { impl ScrollViewer {
319 ferro_property!(
320 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn allow_auto_hide_property() -> AttachedProperty<bool> {
453 FerroProperty::register_attached::<ScrollViewer, Control, _>("AllowAutoHide", true)
454 }
455 );
456
457 ferro_property!(
458 pub fn is_scroll_chaining_enabled_property() -> AttachedProperty<bool> {
460 FerroProperty::register_attached::<ScrollViewer, Control, _>("IsScrollChainingEnabled", true)
461 }
462 );
463
464 ferro_property!(
465 pub fn is_scroll_inertia_enabled_property() -> AttachedProperty<bool> {
467 FerroProperty::register_attached::<ScrollViewer, Control, _>("IsScrollInertiaEnabled", true)
468 }
469 );
470
471 ferro_property!(
472 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 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 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 pub fn new() -> Ref<Self> {
516 instantiate(Self::construct())
517 }
518
519 pub(crate) fn horizontal_scroll_bar(&self) -> Option<Ref<ScrollBar>> {
521 self.horizontal_scroll_bar.borrow().clone()
522 }
523
524 pub(crate) fn vertical_scroll_bar(&self) -> Option<Ref<ScrollBar>> {
526 self.vertical_scroll_bar.borrow().clone()
527 }
528
529 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 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 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 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 pub fn offset(&self) -> Vector {
571 self.get_value(Self::offset_property())
572 }
573
574 pub fn set_offset(&self, value: Vector) {
576 self.set_value(Self::offset_property(), value)
577 }
578
579 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 pub fn large_change(&self) -> Size {
592 self.large_change.get()
593 }
594
595 pub fn small_change(&self) -> Size {
597 self.small_change.get()
598 }
599
600 pub fn horizontal_scroll_bar_visibility(&self) -> ScrollBarVisibility {
602 self.get_value(Self::horizontal_scroll_bar_visibility_property())
603 }
604
605 pub fn set_horizontal_scroll_bar_visibility(&self, value: ScrollBarVisibility) {
607 self.set_value(Self::horizontal_scroll_bar_visibility_property(), value)
608 }
609
610 pub fn vertical_scroll_bar_visibility(&self) -> ScrollBarVisibility {
612 self.get_value(Self::vertical_scroll_bar_visibility_property())
613 }
614
615 pub fn set_vertical_scroll_bar_visibility(&self, value: ScrollBarVisibility) {
617 self.set_value(Self::vertical_scroll_bar_visibility_property(), value)
618 }
619
620 pub fn can_horizontally_scroll(&self) -> bool {
622 self.horizontal_scroll_bar_visibility() != ScrollBarVisibility::Disabled
623 }
624
625 pub fn can_vertically_scroll(&self) -> bool {
627 self.vertical_scroll_bar_visibility() != ScrollBarVisibility::Disabled
628 }
629
630 pub fn current_anchor(&self) -> Option<Ref<Control>> {
632 let presenter = self.presenter()?;
633 as_scroll_anchor_provider(&presenter)?.current_anchor()
634 }
635
636 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 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 pub fn horizontal_snap_points_type(&self) -> SnapPointsType {
659 self.get_value(Self::horizontal_snap_points_type_property())
660 }
661
662 pub fn set_horizontal_snap_points_type(&self, value: SnapPointsType) {
665 self.set_value(Self::horizontal_snap_points_type_property(), value)
666 }
667
668 pub fn vertical_snap_points_type(&self) -> SnapPointsType {
671 self.get_value(Self::vertical_snap_points_type_property())
672 }
673
674 pub fn set_vertical_snap_points_type(&self, value: SnapPointsType) {
677 self.set_value(Self::vertical_snap_points_type_property(), value)
678 }
679
680 pub fn horizontal_snap_points_alignment(&self) -> SnapPointsAlignment {
683 self.get_value(Self::horizontal_snap_points_alignment_property())
684 }
685
686 pub fn set_horizontal_snap_points_alignment(&self, value: SnapPointsAlignment) {
689 self.set_value(Self::horizontal_snap_points_alignment_property(), value)
690 }
691
692 pub fn vertical_snap_points_alignment(&self) -> SnapPointsAlignment {
695 self.get_value(Self::vertical_snap_points_alignment_property())
696 }
697
698 pub fn set_vertical_snap_points_alignment(&self, value: SnapPointsAlignment) {
701 self.set_value(Self::vertical_snap_points_alignment_property(), value)
702 }
703
704 pub fn allow_auto_hide(&self) -> bool {
707 self.get_value(Self::allow_auto_hide_property())
708 }
709
710 pub fn set_allow_auto_hide(&self, value: bool) {
713 self.set_value(Self::allow_auto_hide_property(), value)
714 }
715
716 pub fn is_scroll_chaining_enabled(&self) -> bool {
723 self.get_value(Self::is_scroll_chaining_enabled_property())
724 }
725
726 pub fn set_is_scroll_chaining_enabled(&self, value: bool) {
728 self.set_value(Self::is_scroll_chaining_enabled_property(), value)
729 }
730
731 pub fn is_scroll_inertia_enabled(&self) -> bool {
734 self.get_value(Self::is_scroll_inertia_enabled_property())
735 }
736
737 pub fn set_is_scroll_inertia_enabled(&self, value: bool) {
740 self.set_value(Self::is_scroll_inertia_enabled_property(), value)
741 }
742
743 pub fn is_deferred_scrolling_enabled(&self) -> bool {
746 self.get_value(Self::is_deferred_scrolling_enabled_property())
747 }
748
749 pub fn set_is_deferred_scrolling_enabled(&self, value: bool) {
752 self.set_value(Self::is_deferred_scrolling_enabled_property(), value)
753 }
754
755 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 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 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 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 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 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 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 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 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 pub fn scroll_to_end(&self) {
817 self.set_current_value(Self::offset_property(), Vector::new(f64::NEG_INFINITY, f64::INFINITY));
818 }
819
820 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 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 pub fn get_horizontal_scroll_bar_visibility(control: &Control) -> ScrollBarVisibility {
835 control.get_value(Self::horizontal_scroll_bar_visibility_property())
836 }
837
838 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 pub fn get_horizontal_snap_points_type(control: &Control) -> SnapPointsType {
847 control.get_value(Self::horizontal_snap_points_type_property())
848 }
849
850 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 pub fn get_vertical_snap_points_type(control: &Control) -> SnapPointsType {
859 control.get_value(Self::vertical_snap_points_type_property())
860 }
861
862 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 pub fn get_horizontal_snap_points_alignment(control: &Control) -> SnapPointsAlignment {
871 control.get_value(Self::horizontal_snap_points_alignment_property())
872 }
873
874 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 pub fn get_vertical_snap_points_alignment(control: &Control) -> SnapPointsAlignment {
883 control.get_value(Self::vertical_snap_points_alignment_property())
884 }
885
886 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 pub fn get_vertical_scroll_bar_visibility(control: &Control) -> ScrollBarVisibility {
895 control.get_value(Self::vertical_scroll_bar_visibility_property())
896 }
897
898 pub fn set_allow_auto_hide_on(control: &Control, value: bool) {
900 control.set_value(Self::allow_auto_hide_property(), value);
901 }
902
903 pub fn get_allow_auto_hide(control: &Control) -> bool {
905 control.get_value(Self::allow_auto_hide_property())
906 }
907
908 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 pub fn get_is_scroll_chaining_enabled(control: &Control) -> bool {
927 control.get_value(Self::is_scroll_chaining_enabled_property())
928 }
929
930 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 pub fn get_is_scroll_inertia_enabled(control: &Control) -> bool {
939 control.get_value(Self::is_scroll_inertia_enabled_property())
940 }
941
942 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 pub fn get_is_deferred_scrolling_enabled(control: &Control) -> bool {
951 control.get_value(Self::is_deferred_scrolling_enabled_property())
952 }
953
954 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 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 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 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 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 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 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
1154fn fo(object: &ferroui_base::FerroObject) -> &ferroui_base::FerroObject {
1157 object
1158}