1use super::{ContentPresenter, ItemsPresenter};
2use crate::primitives::{
3 as_logical_scrollable, as_scroll_snap_points_info, register_scroll_snap_points_info, ScrollBarVisibility,
4 SnapPointsAlignment, SnapPointsChangedHandler, SnapPointsType,
5};
6use crate::{
7 register_scroll_anchor_provider, Control, ControlImpl, IScrollAnchorProvider, ItemsControl,
8 RequestBringIntoViewEventArgs, ScrollViewer, StackPanel,
9};
10use ferroui_base::data::BindingPriority;
11use ferroui_base::input::gesture_recognizers::ScrollGestureRecognizer;
12use ferroui_base::input::{
13 IScrollable, InputElement, InputElementImpl, KeyModifiers, PointerWheelEventArgs, ScrollGestureEndedEventArgs,
14 ScrollGestureEventArgs, ScrollGestureInertiaStartingEventArgs,
15};
16use ferroui_base::interactivity::InteractiveImpl;
17use ferroui_base::layout::{LayoutHelper, Layoutable, LayoutableImpl, LayoutableImplExt, Orientation};
18use ferroui_base::media::FlowDirection;
19use ferroui_base::reactive::{CompositeDisposable, IDisposable, ObservableExt};
20use ferroui_base::utilities::MathUtilities;
21use ferroui_base::{
22 ferro_class, ferro_impl_classes, ferro_property, instantiate, AttachedProperty, BoxedValue, DirectProperty,
23 FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Rect,
24 Ref, Size, StyledElementImpl, StyledProperty, StyledPropertyMetadata, Thickness, Vector, Visual, VisualImpl,
25 VisualImplExt,
26 VisualTreeAttachmentEventArgs, WeakRef,
27};
28use std::cell::{Cell, RefCell};
29use std::cmp::Ordering;
30use rustc_hash::FxHashMap;
31use std::collections::HashMap;
32use std::rc::Rc;
33
34const EDGE_DETECTION_TOLERANCE: f64 = 0.1;
35
36#[repr(C)]
38pub struct ScrollContentPresenter {
39 base: ContentPresenter,
40 arranging: Cell<bool>,
41 extent: Cell<Size>,
42 logical_scroll_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
43 viewport: Cell<Size>,
44 active_logical_gesture_scrolls: RefCell<Option<HashMap<i32, Vector>>>,
45 scroll_gesture_snap_points: RefCell<Option<HashMap<i32, Vector>>>,
46 anchor_candidates: RefCell<Option<AnchorCandidates>>,
47 anchor_element: RefCell<Option<Ref<Control>>>,
48 anchor_element_bounds: Cell<Rect>,
49 is_anchor_element_dirty: Cell<bool>,
50 are_vertical_snap_points_regular: Cell<bool>,
51 are_horizontal_snap_points_regular: Cell<bool>,
52 horizontal_snap_points: RefCell<Option<Vec<f64>>>,
53 horizontal_snap_point: Cell<f64>,
54 vertical_snap_points: RefCell<Option<Vec<f64>>>,
55 vertical_snap_point: Cell<f64>,
56 vertical_snap_point_offset: Cell<f64>,
57 horizontal_snap_point_offset: Cell<f64>,
58 owner_subscriptions: RefCell<Option<Rc<CompositeDisposable>>>,
59 owner: RefCell<Option<WeakRef<ScrollViewer>>>,
60 scroll_snap_points_info: RefCell<Option<Ref<Control>>>,
61 scroll_snap_points_subscriptions: RefCell<Option<[Rc<dyn IDisposable>; 2]>>,
64 is_snap_points_updated: Cell<bool>,
65}
66
67ferro_class!(ScrollContentPresenter: ContentPresenter);
68ferroui_base::ferro_class_info!(ScrollContentPresenter { new: ScrollContentPresenter::new });
69ferro_impl_classes!(ScrollContentPresenter: StyledElementImpl, InteractiveImpl, ControlImpl);
70
71impl FerroObjectImpl for ScrollContentPresenter {
72 fn constructed(this: &Self) {
73 Self::parent_constructed(this);
74
75 this.add_handler(
76 Control::request_bring_into_view_event(),
77 |sender, e: &RequestBringIntoViewEventArgs| {
78 if let Some(presenter) = sender.downcast_ref::<ScrollContentPresenter>() {
79 presenter.bring_into_view_requested(e);
80 }
81 },
82 );
83 this.add_handler(
84 InputElement::scroll_gesture_event(),
85 |sender, e: &ScrollGestureEventArgs| {
86 if let Some(presenter) = sender.downcast_ref::<ScrollContentPresenter>() {
87 presenter.on_scroll_gesture(e);
88 }
89 },
90 );
91 this.add_handler(
92 InputElement::scroll_gesture_ended_event(),
93 |sender, e: &ScrollGestureEndedEventArgs| {
94 if let Some(presenter) = sender.downcast_ref::<ScrollContentPresenter>() {
95 presenter.on_scroll_gesture_ended(e);
96 }
97 },
98 );
99 this.add_handler(
100 InputElement::scroll_gesture_inertia_starting_event(),
101 |sender, e: &ScrollGestureInertiaStartingEventArgs| {
102 if let Some(presenter) = sender.downcast_ref::<ScrollContentPresenter>() {
103 presenter.on_scroll_gesture_inertia_starting_ended(e);
104 }
105 },
106 );
107
108 let weak = this.to_ref().downgrade();
109 let _ = FerroObjectExtensions::get_observable(fo(&this), ContentPresenter::child_property()).subscribe_fn(
110 move |child| {
111 if let Some(this) = weak.upgrade() {
112 this.update_scrollable_subscription(child);
113 }
114 },
115 );
116 }
117
118 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
119 let property = change.property();
120
121 if property == Self::offset_property().as_property() {
122 if !this.arranging.get() {
123 this.invalidate_arrange();
124 }
125
126 if let Some(owner) = this.owner() {
127 owner.set_current_value(ScrollViewer::offset_property(), change.get_new_value::<Vector>());
128 }
129 } else if property == ContentPresenter::child_property().as_property() {
130 this.child_changed(change);
131 } else if property == Self::horizontal_snap_points_alignment_property().as_property()
132 || property == Self::vertical_snap_points_alignment_property().as_property()
133 {
134 this.update_snap_points();
135 } else if property == Self::extent_property().as_property() {
136 if let Some(owner) = this.owner() {
137 owner.set_extent(change.get_new_value::<Size>());
138 }
139 this.coerce_value(Self::offset_property().as_property());
140 } else if property == Self::viewport_property().as_property() {
141 if let Some(owner) = this.owner() {
142 owner.set_viewport(change.get_new_value::<Size>());
143 }
144 this.coerce_value(Self::offset_property().as_property());
145 }
146
147 Self::parent_on_property_changed(this, change);
148 }
149}
150
151impl VisualImpl for ScrollContentPresenter {
152 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
153 Self::parent_on_attached_to_visual_tree(this, e);
154 this.attach_to_scroll_viewer();
155 }
156
157 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
158 Self::parent_on_detached_from_visual_tree(this, e);
159
160 if this.find_ancestor_of_type::<ScrollViewer>(false).is_none() {
164 this.detach_from_scroll_viewer();
165 }
166 }
167}
168
169impl LayoutableImpl for ScrollContentPresenter {
170 fn measure_override(this: &Self, available_size: Size) -> Size {
171 let child = match this.child() {
172 Some(child) if this.logical_scroll_subscription.borrow().is_none() => child,
173 _ => return Self::parent_measure_override(this, available_size),
174 };
175
176 let padding = this.get_child_padding();
177 let deflated = available_size.deflate(padding);
178
179 let constraint = Size::new(
180 if this.can_horizontally_scroll() {
181 f64::INFINITY
182 } else {
183 deflated.width
184 },
185 if this.can_vertically_scroll() {
186 f64::INFINITY
187 } else {
188 deflated.height
189 },
190 );
191
192 child.measure(constraint);
193
194 if !this.is_snap_points_updated.get() {
195 this.is_snap_points_updated.set(true);
196 this.update_snap_points();
197 }
198
199 child.desired_size().inflate(padding).constrain(available_size)
200 }
201
202 fn arrange_override(this: &Self, final_size: Size) -> Size {
203 let child = match this.child() {
204 Some(child) if this.logical_scroll_subscription.borrow().is_none() => child,
205 _ => return Self::parent_arrange_override(this, final_size),
206 };
207
208 this.arrange_with_anchoring(&child, final_size)
209 }
210}
211
212impl InputElementImpl for ScrollContentPresenter {
213 fn on_pointer_wheel_changed(this: &Self, e: &PointerWheelEventArgs) {
214 let extent = this.extent();
215 let viewport = this.viewport();
216
217 if extent.height > viewport.height || extent.width > viewport.width {
218 let child = this.child();
219 let scrollable = child.as_ref().and_then(|child| as_logical_scrollable(child));
220 let logical = scrollable.filter(|scrollable| scrollable.is_logical_scroll_enabled());
221
222 let offset = this.offset();
223 let mut x = offset.x;
224 let mut y = offset.y;
225 let mut delta = e.delta();
226
227 if e.key_modifiers() == KeyModifiers::SHIFT && MathUtilities::is_zero(delta.x) {
231 delta = Vector::new(delta.y, delta.x);
232 } else {
233 delta = Self::adjust_delta_for_flow_direction(delta, this.flow_direction());
234 }
235
236 if extent.height > viewport.height {
237 let height = match logical {
238 Some(scrollable) => scrollable.scroll_size().height,
239 None => 50.0,
240 };
241 y += -delta.y * height;
242 y = y.max(0.0);
243 y = y.min(extent.height - viewport.height);
244 }
245
246 if extent.width > viewport.width {
247 let width = match logical {
248 Some(scrollable) => scrollable.scroll_size().width,
249 None => 50.0,
250 };
251 x += -delta.x * width;
252 x = x.max(0.0);
253 x = x.min(extent.width - viewport.width);
254 }
255
256 let new_offset = this.snap_offset(Vector::new(x, y), delta, true);
257
258 let offset_changed = new_offset != this.offset();
259 this.set_current_value(Self::offset_property(), new_offset);
260
261 e.set_handled(!this.is_scroll_chaining_enabled() || offset_changed);
262 }
263 }
264
265 fn as_scrollable(this: &Self) -> Option<&dyn IScrollable> {
266 Some(this)
267 }
268}
269
270impl IScrollable for ScrollContentPresenter {
271 fn extent(&self) -> Size {
272 ScrollContentPresenter::extent(self)
273 }
274
275 fn offset(&self) -> Vector {
276 ScrollContentPresenter::offset(self)
277 }
278
279 fn set_offset(&self, value: Vector) {
280 ScrollContentPresenter::set_offset(self, value)
281 }
282
283 fn viewport(&self) -> Size {
284 ScrollContentPresenter::viewport(self)
285 }
286
287 fn can_horizontally_scroll(&self) -> bool {
288 ScrollContentPresenter::can_horizontally_scroll(self)
289 }
290
291 fn can_vertically_scroll(&self) -> bool {
292 ScrollContentPresenter::can_vertically_scroll(self)
293 }
294}
295
296impl IScrollAnchorProvider for ScrollContentPresenter {
297 fn current_anchor(&self) -> Option<Ref<Control>> {
298 self.ensure_anchor_element_selection();
299 self.anchor_element.borrow().clone()
300 }
301
302 fn register_anchor_candidate(&self, element: &Ref<Control>) {
303 if !self.is_visual_ancestor_of(element) {
304 panic!("An anchor control must be a visual descendent of the ScrollContentPresenter.");
305 }
306
307 self.anchor_candidates.borrow_mut().get_or_insert_with(AnchorCandidates::default).add(element);
308 self.is_anchor_element_dirty.set(true);
309 }
310
311 fn unregister_anchor_candidate(&self, element: &Ref<Control>) {
312 if let Some(candidates) = self.anchor_candidates.borrow_mut().as_mut() {
313 candidates.remove(element);
314 }
315 self.is_anchor_element_dirty.set(true);
316
317 let is_anchor = self.anchor_element.borrow().as_ref() == Some(element);
318 if is_anchor {
319 *self.anchor_element.borrow_mut() = None;
320 }
321 }
322}
323
324ferroui_base::ferro_properties! { impl ScrollContentPresenter {
325 ferro_property!(
326 pub fn can_horizontally_scroll_property() -> StyledProperty<bool> {
328 FerroProperty::register::<ScrollContentPresenter, _>("CanHorizontallyScroll", false)
329 }
330 );
331
332 ferro_property!(
333 pub fn can_vertically_scroll_property() -> StyledProperty<bool> {
335 FerroProperty::register::<ScrollContentPresenter, _>("CanVerticallyScroll", false)
336 }
337 );
338
339 ferro_property!(
340 pub fn extent_property() -> DirectProperty<ScrollContentPresenter, Size> {
342 ScrollViewer::extent_property().add_owner::<ScrollContentPresenter>(|o| o.extent(), None, None)
343 }
344 );
345
346 ferro_property!(
347 pub fn offset_property() -> StyledProperty<Vector> {
349 ScrollViewer::offset_property().add_owner_with::<ScrollContentPresenter>(
350 StyledPropertyMetadata::new(None).with_coerce(ScrollViewer::coerce_offset),
351 )
352 }
353 );
354
355 ferro_property!(
356 pub fn viewport_property() -> DirectProperty<ScrollContentPresenter, Size> {
358 ScrollViewer::viewport_property().add_owner::<ScrollContentPresenter>(|o| o.viewport(), None, None)
359 }
360 );
361
362 ferro_property!(
363 pub fn horizontal_snap_points_type_property() -> AttachedProperty<SnapPointsType> {
365 ScrollViewer::horizontal_snap_points_type_property().add_owner::<ScrollContentPresenter>()
366 }
367 );
368
369 ferro_property!(
370 pub fn vertical_snap_points_type_property() -> AttachedProperty<SnapPointsType> {
372 ScrollViewer::vertical_snap_points_type_property().add_owner::<ScrollContentPresenter>()
373 }
374 );
375
376 ferro_property!(
377 pub fn horizontal_snap_points_alignment_property() -> AttachedProperty<SnapPointsAlignment> {
379 ScrollViewer::horizontal_snap_points_alignment_property().add_owner::<ScrollContentPresenter>()
380 }
381 );
382
383 ferro_property!(
384 pub fn vertical_snap_points_alignment_property() -> AttachedProperty<SnapPointsAlignment> {
386 ScrollViewer::vertical_snap_points_alignment_property().add_owner::<ScrollContentPresenter>()
387 }
388 );
389
390 ferro_property!(
391 pub fn is_scroll_chaining_enabled_property() -> AttachedProperty<bool> {
393 ScrollViewer::is_scroll_chaining_enabled_property().add_owner::<ScrollContentPresenter>()
394 }
395 );
396} }
397
398impl ScrollContentPresenter {
399 fn static_constructor() {
401 register_scroll_anchor_provider::<ScrollContentPresenter>();
402 register_scroll_snap_points_info::<StackPanel>();
403
404 Visual::clip_to_bounds_property().override_default_value::<ScrollContentPresenter>(true);
405 Layoutable::affects_measure::<ScrollContentPresenter>(&[
406 Self::can_horizontally_scroll_property().as_property(),
407 Self::can_vertically_scroll_property().as_property(),
408 ]);
409 }
410
411 pub fn construct() -> Self {
413 Self {
414 base: ContentPresenter::construct(),
415 arranging: Cell::new(false),
416 extent: Cell::new(Size::default()),
417 logical_scroll_subscription: RefCell::new(None),
418 viewport: Cell::new(Size::default()),
419 active_logical_gesture_scrolls: RefCell::new(None),
420 scroll_gesture_snap_points: RefCell::new(None),
421 anchor_candidates: RefCell::new(None),
422 anchor_element: RefCell::new(None),
423 anchor_element_bounds: Cell::new(Rect::default()),
424 is_anchor_element_dirty: Cell::new(false),
425 are_vertical_snap_points_regular: Cell::new(false),
426 are_horizontal_snap_points_regular: Cell::new(false),
427 horizontal_snap_points: RefCell::new(None),
428 horizontal_snap_point: Cell::new(0.0),
429 vertical_snap_points: RefCell::new(None),
430 vertical_snap_point: Cell::new(0.0),
431 vertical_snap_point_offset: Cell::new(0.0),
432 horizontal_snap_point_offset: Cell::new(0.0),
433 owner_subscriptions: RefCell::new(None),
434 owner: RefCell::new(None),
435 scroll_snap_points_info: RefCell::new(None),
436 scroll_snap_points_subscriptions: RefCell::new(None),
437 is_snap_points_updated: Cell::new(false),
438 }
439 }
440
441 pub fn new() -> Ref<Self> {
443 instantiate(Self::construct())
444 }
445
446 pub fn can_horizontally_scroll(&self) -> bool {
449 self.get_value(Self::can_horizontally_scroll_property())
450 }
451
452 pub fn set_can_horizontally_scroll(&self, value: bool) {
455 self.set_value(Self::can_horizontally_scroll_property(), value)
456 }
457
458 pub fn can_vertically_scroll(&self) -> bool {
461 self.get_value(Self::can_vertically_scroll_property())
462 }
463
464 pub fn set_can_vertically_scroll(&self, value: bool) {
467 self.set_value(Self::can_vertically_scroll_property(), value)
468 }
469
470 pub fn extent(&self) -> Size {
472 self.extent.get()
473 }
474
475 fn set_extent(&self, value: Size) {
476 self.set_and_raise_cell(Self::extent_property(), &self.extent, value);
477 }
478
479 pub fn offset(&self) -> Vector {
481 self.get_value(Self::offset_property())
482 }
483
484 pub fn set_offset(&self, value: Vector) {
486 self.set_value(Self::offset_property(), value)
487 }
488
489 pub fn viewport(&self) -> Size {
491 self.viewport.get()
492 }
493
494 fn set_viewport(&self, value: Size) {
495 self.set_and_raise_cell(Self::viewport_property(), &self.viewport, value);
496 }
497
498 pub fn horizontal_snap_points_type(&self) -> SnapPointsType {
501 self.get_value(Self::horizontal_snap_points_type_property())
502 }
503
504 pub fn set_horizontal_snap_points_type(&self, value: SnapPointsType) {
507 self.set_value(Self::horizontal_snap_points_type_property(), value)
508 }
509
510 pub fn vertical_snap_points_type(&self) -> SnapPointsType {
513 self.get_value(Self::vertical_snap_points_type_property())
514 }
515
516 pub fn set_vertical_snap_points_type(&self, value: SnapPointsType) {
519 self.set_value(Self::vertical_snap_points_type_property(), value)
520 }
521
522 pub fn horizontal_snap_points_alignment(&self) -> SnapPointsAlignment {
525 self.get_value(Self::horizontal_snap_points_alignment_property())
526 }
527
528 pub fn set_horizontal_snap_points_alignment(&self, value: SnapPointsAlignment) {
531 self.set_value(Self::horizontal_snap_points_alignment_property(), value)
532 }
533
534 pub fn vertical_snap_points_alignment(&self) -> SnapPointsAlignment {
537 self.get_value(Self::vertical_snap_points_alignment_property())
538 }
539
540 pub fn set_vertical_snap_points_alignment(&self, value: SnapPointsAlignment) {
543 self.set_value(Self::vertical_snap_points_alignment_property(), value)
544 }
545
546 pub fn is_scroll_chaining_enabled(&self) -> bool {
553 self.get_value(Self::is_scroll_chaining_enabled_property())
554 }
555
556 pub fn set_is_scroll_chaining_enabled(&self, value: bool) {
558 self.set_value(Self::is_scroll_chaining_enabled_property(), value)
559 }
560
561 fn owner(&self) -> Option<Ref<ScrollViewer>> {
562 self.owner.borrow().as_ref().and_then(WeakRef::upgrade)
563 }
564
565 pub fn bring_descendant_into_view(&self, target: &Visual, target_rect: Rect) -> bool {
572 let child = match self.child() {
573 Some(child) if child.is_effectively_visible() => child,
574 _ => return false,
575 };
576
577 if let Some(scrollable) = as_logical_scrollable(&child) {
578 if scrollable.is_logical_scroll_enabled() {
579 if let Some(control) = target.to_ref().cast::<Control>() {
580 return scrollable.bring_into_view(&control, target_rect);
581 }
582 }
583 }
584
585 let Some(transform) = target.transform_to_visual(&child) else {
590 return false;
591 };
592
593 let child_padding = self.get_child_padding();
594 let child_margin = self.get_child_margin();
595 let child_content_origin = Vector::new(
596 child_padding.left + child_margin.left,
597 child_padding.top + child_margin.top,
598 );
599 let rectangle = target_rect.transform_to_aabb(transform).translate(child_content_origin);
600 let current = self.offset();
601 let viewport_size = self.viewport();
602 let viewport = Rect::new(current.x, current.y, viewport_size.width, viewport_size.height);
603
604 let min_x = Self::compute_scroll_offset_with_minimal_scroll(
605 viewport.x,
606 viewport.right(),
607 rectangle.x,
608 rectangle.right(),
609 );
610 let min_y = Self::compute_scroll_offset_with_minimal_scroll(
611 viewport.y,
612 viewport.bottom(),
613 rectangle.y,
614 rectangle.bottom(),
615 );
616 let offset = Vector::new(min_x, min_y);
617
618 if self.offset().nearly_equals(offset) {
619 return false;
620 }
621
622 let old_offset = self.offset();
623 self.set_current_value(Self::offset_property(), offset);
624
625 !self.offset().nearly_equals(old_offset)
629 }
630
631 pub(crate) fn compute_scroll_offset_with_minimal_scroll(
638 viewport_start: f64,
639 viewport_end: f64,
640 child_start: f64,
641 child_end: f64,
642 ) -> f64 {
643 let is_child_above =
646 MathUtilities::less_than(child_start, viewport_start) && MathUtilities::less_than(child_end, viewport_end);
647
648 let is_child_below = MathUtilities::greater_than(child_end, viewport_end)
651 && MathUtilities::greater_than(child_start, viewport_start);
652 let is_child_larger = (child_end - child_start) > (viewport_end - viewport_start);
653
654 let mut res = viewport_start;
657
658 if (is_child_above && !is_child_larger) || (is_child_below && is_child_larger) {
662 res = child_start;
663 }
664 else if is_child_above || is_child_below {
668 res = child_end - (viewport_end - viewport_start);
669 }
670
671 res
672 }
673
674 pub(crate) fn attach_to_scroll_viewer(&self) {
680 let Some(owner) = self.find_ancestor_of_type::<ScrollViewer>(false) else {
681 self.detach_from_scroll_viewer();
682 return;
683 };
684
685 if self.owner().as_ref() == Some(&owner) {
686 return;
687 }
688
689 let old_subscriptions = self.owner_subscriptions.borrow_mut().take();
690 if let Some(old_subscriptions) = old_subscriptions {
691 old_subscriptions.dispose();
692 }
693 *self.owner.borrow_mut() = Some(owner.downgrade());
694
695 fn not_disabled(v: ScrollBarVisibility) -> bool {
696 v != ScrollBarVisibility::Disabled
697 }
698
699 let mut subscription_disposables: Vec<Rc<dyn IDisposable>> = Vec::new();
700
701 if !self.is_set(Self::can_horizontally_scroll_property().as_property()) {
702 subscription_disposables.push(self.bind(
703 Self::can_horizontally_scroll_property(),
704 FerroObjectExtensions::get_observable_with(
705 fo(&owner),
706 ScrollViewer::horizontal_scroll_bar_visibility_property(),
707 not_disabled,
708 ),
709 BindingPriority::Template,
710 ));
711 }
712
713 if !self.is_set(Self::can_vertically_scroll_property().as_property()) {
714 subscription_disposables.push(self.bind(
715 Self::can_vertically_scroll_property(),
716 FerroObjectExtensions::get_observable_with(
717 fo(&owner),
718 ScrollViewer::vertical_scroll_bar_visibility_property(),
719 not_disabled,
720 ),
721 BindingPriority::Template,
722 ));
723 }
724
725 if !self.is_set(Self::offset_property().as_property()) {
726 subscription_disposables.push(self.bind_value(
727 Self::offset_property(),
728 FerroObjectExtensions::get_binding_observable(fo(&owner), ScrollViewer::offset_property()),
729 BindingPriority::Template,
730 ));
731 }
732
733 if !self.is_set(Self::is_scroll_chaining_enabled_property().as_property()) {
734 subscription_disposables.push(self.bind_value(
735 Self::is_scroll_chaining_enabled_property(),
736 FerroObjectExtensions::get_binding_observable(
737 fo(&owner),
738 ScrollViewer::is_scroll_chaining_enabled_property(),
739 ),
740 BindingPriority::Template,
741 ));
742 }
743
744 if !self.is_set(ContentPresenter::content_property().as_property()) {
745 subscription_disposables.push(self.bind_value(
746 ContentPresenter::content_property(),
747 FerroObjectExtensions::get_binding_observable(fo(&owner), ContentPresenter::content_property()),
748 BindingPriority::Template,
749 ));
750 }
751
752 *self.owner_subscriptions.borrow_mut() =
753 Some(Rc::new(CompositeDisposable::from_disposables(subscription_disposables)));
754 }
755
756 fn detach_from_scroll_viewer(&self) {
758 *self.owner.borrow_mut() = None;
759 let subscriptions = self.owner_subscriptions.borrow_mut().take();
760 if let Some(subscriptions) = subscriptions {
761 subscriptions.dispose();
762 }
763 }
764
765 fn arrange_with_anchoring(&self, child: &Ref<Control>, final_size: Size) -> Size {
766 let padding = self.get_child_padding();
767 let desired_size = child.desired_size().inflate(padding);
768
769 let size = Size::new(
770 if self.can_horizontally_scroll() {
771 desired_size.width.max(final_size.width)
772 } else {
773 final_size.width
774 },
775 if self.can_vertically_scroll() {
776 desired_size.height.max(final_size.height)
777 } else {
778 final_size.height
779 },
780 );
781
782 let track_anchor = || -> Vector {
783 let anchor_element = self.anchor_element.borrow().clone();
788 if let Some(anchor_element) = anchor_element {
789 if let Some(updated_bounds) = Self::try_translate_bounds(&anchor_element, child) {
790 let anchor_bounds = self.anchor_element_bounds.get();
791 if updated_bounds.position() != anchor_bounds.position() {
792 return (updated_bounds.position() - anchor_bounds.position()).into();
793 }
794 }
795 }
796
797 Vector::default()
798 };
799
800 let offset = self.offset();
801 let is_anchoring = offset.x >= EDGE_DETECTION_TOLERANCE || offset.y >= EDGE_DETECTION_TOLERANCE;
802
803 if is_anchoring {
804 self.ensure_anchor_element_selection();
806
807 self.arrange_override_impl(size, -self.offset());
809
810 let anchor_shift = track_anchor();
813
814 if anchor_shift != Vector::default() {
815 let new_offset = self.offset() + anchor_shift;
816 let extent = self.extent();
817 let viewport = self.viewport();
818 let mut new_extent = extent;
819 let max_offset = Vector::new(extent.width - viewport.width, extent.height - viewport.height);
820
821 if new_offset.x > max_offset.x {
822 new_extent = new_extent.with_width(new_offset.x + viewport.width);
823 }
824
825 if new_offset.y > max_offset.y {
826 new_extent = new_extent.with_height(new_offset.y + viewport.height);
827 }
828
829 self.set_extent(new_extent);
830
831 {
832 struct Arranging<'a>(&'a Cell<bool>);
833
834 impl Drop for Arranging<'_> {
835 fn drop(&mut self) {
836 self.0.set(false);
837 }
838 }
839
840 self.arranging.set(true);
841 let _arranging = Arranging(&self.arranging);
842 self.set_current_value(Self::offset_property(), new_offset);
843 }
844
845 self.arrange_override_impl(size, -self.offset());
846 }
847 } else {
848 self.arrange_override_impl(size, -self.offset());
849 }
850
851 self.set_viewport(final_size);
852 self.set_extent(self.compute_extent(child, final_size, padding));
853 self.is_anchor_element_dirty.set(true);
854
855 final_size
856 }
857
858 fn get_child_padding(&self) -> Thickness {
859 let mut padding = self.padding();
860 let mut border_thickness = self.border_thickness();
861
862 if self.use_layout_rounding() {
863 let scale = LayoutHelper::get_layout_scale(self);
864 padding = LayoutHelper::round_layout_thickness(padding, scale);
865 border_thickness = LayoutHelper::round_layout_thickness(border_thickness, scale);
866 }
867
868 padding + border_thickness
869 }
870
871 fn get_child_margin(&self) -> Thickness {
872 let Some(child) = self.child() else {
873 return Thickness::default();
874 };
875
876 let mut margin = child.margin();
877
878 if child.use_layout_rounding() {
879 let scale = LayoutHelper::get_layout_scale(&child);
880 margin = LayoutHelper::round_layout_thickness(margin, scale);
881 }
882
883 margin
884 }
885
886 fn compute_extent(&self, child: &Ref<Control>, viewport_size: Size, padding: Thickness) -> Size {
887 let child_margin = self.get_child_margin();
888
889 let mut extent = child.bounds().size().inflate(child_margin).inflate(padding);
890
891 if MathUtilities::are_close_eps(extent.width, viewport_size.width, LayoutHelper::LAYOUT_EPSILON) {
892 extent = extent.with_width(viewport_size.width);
893 }
894
895 if MathUtilities::are_close_eps(extent.height, viewport_size.height, LayoutHelper::LAYOUT_EPSILON) {
896 extent = extent.with_height(viewport_size.height);
897 }
898
899 extent
900 }
901
902 fn on_scroll_gesture(&self, e: &ScrollGestureEventArgs) {
903 let extent = self.extent();
904 let viewport = self.viewport();
905
906 if extent.height > viewport.height || extent.width > viewport.width {
907 let child = self.child();
908 let scrollable = child.as_ref().and_then(|child| as_logical_scrollable(child));
909 let is_logical = scrollable.is_some_and(|scrollable| scrollable.is_logical_scroll_enabled());
910 let mut logical_scroll_item_size = Vector::new(1.0, 1.0);
911 let mut can_x_scroll = false;
912 let mut can_y_scroll = false;
913
914 let offset = self.offset();
915 let mut x = offset.x;
916 let mut y = offset.y;
917
918 let mut delta = Vector::default();
919 if is_logical {
920 if let Some(active) = self.active_logical_gesture_scrolls.borrow().as_ref() {
921 if let Some(value) = active.get(&e.id()) {
922 delta = *value;
923 }
924 }
925 }
926 delta += Self::adjust_delta_for_flow_direction(e.delta(), self.flow_direction());
927
928 if let Some(scrollable) = scrollable.filter(|_| is_logical) {
929 logical_scroll_item_size = self.bounds().size() / scrollable.viewport();
930 }
931
932 if extent.height > viewport.height {
933 let dy;
934 if is_logical {
935 let logical_units = delta.y / logical_scroll_item_size.y;
936 delta = delta.with_y(delta.y - logical_units * logical_scroll_item_size.y);
937 dy = logical_units;
938 } else {
939 dy = delta.y;
940 }
941
942 y += dy;
943 y = y.max(0.0);
944 y = y.min(extent.height - viewport.height);
945
946 can_y_scroll = dy != 0.0;
947 }
948
949 if extent.width > viewport.width {
950 let dx;
951 if is_logical {
952 let logical_units = delta.x / logical_scroll_item_size.x;
953 delta = delta.with_x(delta.x - logical_units * logical_scroll_item_size.x);
954 dx = logical_units;
955 } else {
956 dx = delta.x;
957 }
958 x += dx;
959 x = x.max(0.0);
960 x = x.min(extent.width - viewport.width);
961
962 can_x_scroll = dx != 0.0;
963 }
964
965 if is_logical {
966 self.active_logical_gesture_scrolls
967 .borrow_mut()
968 .get_or_insert_with(HashMap::new)
969 .insert(e.id(), delta);
970 }
971
972 let mut new_offset = Vector::new(x, y);
973
974 let snap_point = self
975 .scroll_gesture_snap_points
976 .borrow()
977 .as_ref()
978 .and_then(|points| points.get(&e.id()).copied());
979 if let Some(snap_point) = snap_point {
980 let mut x_offset = x;
981 let mut y_offset = y;
982
983 if self.horizontal_snap_points_type() != SnapPointsType::None {
984 x_offset = if delta.x < 0.0 {
985 snap_point.x.max(new_offset.x)
986 } else {
987 snap_point.x.min(new_offset.x)
988 };
989 }
990
991 if self.vertical_snap_points_type() != SnapPointsType::None {
992 y_offset = if delta.y < 0.0 {
993 snap_point.y.max(new_offset.y)
994 } else {
995 snap_point.y.min(new_offset.y)
996 };
997 }
998
999 new_offset = Vector::new(x_offset, y_offset);
1000 }
1001
1002 let offset_changed = new_offset != self.offset();
1003 self.set_current_value(Self::offset_property(), new_offset);
1004
1005 e.set_handled(!self.is_scroll_chaining_enabled() || offset_changed);
1006
1007 if !e.handled() && !self.is_scroll_chaining_enabled() {
1008 e.set_handled(can_x_scroll || can_y_scroll);
1011 }
1012
1013 e.set_should_end_scroll_gesture(!self.is_scroll_chaining_enabled() && !offset_changed);
1014 }
1015 }
1016
1017 fn on_scroll_gesture_ended(&self, e: &ScrollGestureEndedEventArgs) {
1018 if let Some(active) = self.active_logical_gesture_scrolls.borrow_mut().as_mut() {
1019 active.remove(&e.id());
1020 }
1021 if let Some(points) = self.scroll_gesture_snap_points.borrow_mut().as_mut() {
1022 points.remove(&e.id());
1023 }
1024
1025 let offset = self.snap_offset(self.offset(), Vector::default(), false);
1026 self.set_current_value(Self::offset_property(), offset);
1027 }
1028
1029 fn on_scroll_gesture_inertia_starting_ended(&self, e: &ScrollGestureInertiaStartingEventArgs) {
1030 let mut scrollable = self.content().as_ref().and_then(Control::from_boxed);
1031
1032 if let Some(items_control) = scrollable.as_ref().and_then(|content| content.downcast_ref::<ItemsControl>()) {
1033 scrollable = items_control.presenter().and_then(|presenter| presenter.panel()).map(Ref::upcast);
1034 }
1035
1036 if !scrollable.is_some_and(|scrollable| as_scroll_snap_points_info(&scrollable).is_some()) {
1037 return;
1038 }
1039
1040 self.scroll_gesture_snap_points
1041 .borrow_mut()
1042 .get_or_insert_with(HashMap::new);
1043
1044 let mut offset = self.offset();
1045
1046 if self.horizontal_snap_points_type() != SnapPointsType::None
1047 && self.vertical_snap_points_type() != SnapPointsType::None
1048 {
1049 return;
1050 }
1051
1052 fn get_distance(speed: f64) -> f64 {
1053 let time =
1054 (ScrollGestureRecognizer::INERTIAL_SCROLL_SPEED_END / speed.abs()).ln() / ScrollGestureRecognizer::INERTIAL_RESISTANCE.ln();
1055
1056 let mut time_elapsed = 0.0;
1057 let mut distance = 0.0;
1058 let mut step = 0.0;
1059
1060 while time_elapsed <= time {
1061 let s = speed * ScrollGestureRecognizer::INERTIAL_RESISTANCE.powf(time_elapsed);
1062 distance += s * step;
1063
1064 time_elapsed += 0.016f32 as f64;
1065 step = 0.016f32 as f64;
1066 }
1067
1068 distance
1069 }
1070
1071 let mut x_distance = 0.0;
1072 let mut y_distance = 0.0;
1073
1074 if self.horizontal_snap_points_type() != SnapPointsType::None {
1075 x_distance = if self.horizontal_snap_points_type() == SnapPointsType::Mandatory {
1076 get_distance(e.inertia().x)
1077 } else {
1078 0.0
1079 };
1080 }
1081
1082 if self.vertical_snap_points_type() != SnapPointsType::None {
1083 y_distance = if self.vertical_snap_points_type() == SnapPointsType::Mandatory {
1084 get_distance(e.inertia().y)
1085 } else {
1086 0.0
1087 };
1088 }
1089
1090 offset = Vector::new(offset.x + x_distance, offset.y + y_distance);
1091
1092 let snapped = self.snap_offset(offset, Vector::default(), false);
1093 let mut points = self.scroll_gesture_snap_points.borrow_mut();
1094 let points = points.get_or_insert_with(HashMap::new);
1095 points.insert(e.id(), snapped);
1096 }
1097
1098 fn bring_into_view_requested(&self, e: &RequestBringIntoViewEventArgs) {
1099 if let Some(target_object) = &e.target_object {
1100 e.set_handled(self.bring_descendant_into_view(target_object, e.target_rect()));
1101 }
1102 }
1103
1104 fn child_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1105 let (old_value, new_value) = e.get_old_and_new_value::<Option<Ref<Control>>>();
1106 self.update_scrollable_subscription(new_value);
1107
1108 if old_value.is_some() {
1109 self.set_current_value(Self::offset_property(), Vector::default());
1110 }
1111 }
1112
1113 fn update_scrollable_subscription(&self, child: Option<Ref<Control>>) {
1114 let old_subscription = self.logical_scroll_subscription.borrow_mut().take();
1115 if let Some(old_subscription) = old_subscription {
1116 old_subscription.dispose();
1117 }
1118
1119 let Some(child) = child else { return };
1120 let Some(scrollable) = as_logical_scrollable(&child) else {
1121 return;
1122 };
1123
1124 let weak_this = self.to_ref().downgrade();
1125 let weak_sender = child.downgrade();
1126 let scroll_invalidated = scrollable.scroll_invalidated(Rc::new(move || {
1127 if let (Some(this), Some(sender)) = (weak_this.upgrade(), weak_sender.upgrade()) {
1128 this.update_from_scrollable(&sender);
1129 }
1130 }));
1131
1132 if scrollable.is_logical_scroll_enabled() {
1133 let target = child.clone();
1134 let can_horizontally_scroll =
1135 FerroObjectExtensions::get_observable(fo(&self), Self::can_horizontally_scroll_property())
1136 .subscribe_fn(move |x| {
1137 if let Some(scrollable) = as_logical_scrollable(&target) {
1138 scrollable.set_can_horizontally_scroll(x);
1139 }
1140 });
1141
1142 let target = child.clone();
1143 let can_vertically_scroll =
1144 FerroObjectExtensions::get_observable(fo(&self), Self::can_vertically_scroll_property()).subscribe_fn(
1145 move |x| {
1146 if let Some(scrollable) = as_logical_scrollable(&target) {
1147 scrollable.set_can_vertically_scroll(x);
1148 }
1149 },
1150 );
1151
1152 let target = child.clone();
1153 let skipped = Cell::new(false);
1154 let offset =
1155 FerroObjectExtensions::get_observable(fo(&self), Self::offset_property()).subscribe_fn(move |x| {
1156 if !skipped.replace(true) {
1157 return;
1158 }
1159 if let Some(scrollable) = as_logical_scrollable(&target) {
1160 scrollable.set_offset(x);
1161 }
1162 });
1163
1164 let subscription: Rc<dyn IDisposable> = Rc::new(CompositeDisposable::from_disposables([
1165 can_horizontally_scroll,
1166 can_vertically_scroll,
1167 offset,
1168 scroll_invalidated,
1169 ]));
1170 *self.logical_scroll_subscription.borrow_mut() = Some(subscription);
1171 self.update_from_scrollable(&child);
1172 }
1173 }
1174
1175 fn update_from_scrollable(&self, child: &Ref<Control>) {
1176 let Some(scrollable) = as_logical_scrollable(child) else {
1177 return;
1178 };
1179 let logical_scroll = self.logical_scroll_subscription.borrow().is_some();
1180
1181 if logical_scroll != scrollable.is_logical_scroll_enabled() {
1182 self.update_scrollable_subscription(self.child());
1183 self.set_current_value(Self::offset_property(), Vector::default());
1184 self.invalidate_measure();
1185 } else if scrollable.is_logical_scroll_enabled() {
1186 self.set_viewport(scrollable.viewport());
1187 self.set_extent(scrollable.extent());
1188 self.set_current_value(Self::offset_property(), scrollable.offset());
1189 }
1190 }
1191
1192 fn ensure_anchor_element_selection(&self) {
1193 if !self.is_anchor_element_dirty.get() {
1194 return;
1195 }
1196 let Some(candidates) = self.anchor_candidates.borrow().as_ref().map(|candidates| candidates.items.clone()) else {
1197 return;
1198 };
1199
1200 *self.anchor_element.borrow_mut() = None;
1201 self.anchor_element_bounds.set(Rect::default());
1202 self.is_anchor_element_dirty.set(false);
1203
1204 let Some(child) = self.child() else {
1205 panic!("The scroll content presenter has anchor candidates but no child.");
1206 };
1207
1208 let mut best_candidate: Option<Ref<Control>> = None;
1209 let mut best_candidate_distance = f64::MAX;
1210
1211 for element in candidates.iter() {
1214 if !element.is_visible() {
1215 continue;
1216 }
1217
1218 if let Some(bounds) = self.get_viewport_bounds(element, &child) {
1219 let distance: Vector = bounds.position().into();
1220 let candidate_distance = distance.length().abs();
1221
1222 if candidate_distance < best_candidate_distance {
1223 best_candidate = Some(element.clone());
1224 best_candidate_distance = candidate_distance;
1225 }
1226 }
1227 }
1228
1229 if let Some(best_candidate) = best_candidate {
1230 let unscrolled_bounds = Self::translate_bounds(&best_candidate, &child);
1235 *self.anchor_element.borrow_mut() = Some(best_candidate);
1236 self.anchor_element_bounds.set(unscrolled_bounds);
1237 }
1238 }
1239
1240 fn get_viewport_bounds(&self, element: &Ref<Control>, child: &Ref<Control>) -> Option<Rect> {
1243 let child_bounds = Self::try_translate_bounds(element, child)?;
1244
1245 let this_bounds = Rect::from_size(self.bounds().size());
1250 let bounds = Rect::from_position_size(child_bounds.position() - self.offset(), child_bounds.size());
1251 bounds.intersects(this_bounds).then_some(bounds)
1252 }
1253
1254 fn translate_bounds(control: &Ref<Control>, to: &Ref<Control>) -> Rect {
1255 match Self::try_translate_bounds(control, to) {
1256 Some(bounds) => bounds,
1257 None => panic!("The control's bounds could not be translated to the requested control."),
1258 }
1259 }
1260
1261 fn try_translate_bounds(control: &Ref<Control>, to: &Ref<Control>) -> Option<Rect> {
1262 if !control.is_visible() {
1263 return None;
1264 }
1265
1266 let p = control.translate_point(Default::default(), to)?;
1267 Some(Rect::from_position_size(p, control.bounds().size()))
1268 }
1269
1270 fn update_snap_points(&self) {
1271 let content = self.content();
1272 let scrollable = self.get_scroll_snap_points_info(content.as_ref());
1273
1274 if let Some(scroll_snap_points_info) = scrollable.as_ref().and_then(|s| as_scroll_snap_points_info(s)) {
1275 self.are_vertical_snap_points_regular
1276 .set(scroll_snap_points_info.are_vertical_snap_points_regular());
1277 self.are_horizontal_snap_points_regular
1278 .set(scroll_snap_points_info.are_horizontal_snap_points_regular());
1279
1280 if !self.are_vertical_snap_points_regular.get() {
1281 let points = scroll_snap_points_info
1282 .get_irregular_snap_points(Orientation::Vertical, self.vertical_snap_points_alignment());
1283 *self.vertical_snap_points.borrow_mut() = Some(points);
1284 } else {
1285 *self.vertical_snap_points.borrow_mut() = Some(Vec::new());
1286 let (snap_point, offset) = scroll_snap_points_info
1287 .get_regular_snap_points(Orientation::Vertical, self.vertical_snap_points_alignment());
1288 self.vertical_snap_point.set(snap_point);
1289 self.vertical_snap_point_offset.set(offset);
1290 }
1291
1292 if !self.are_horizontal_snap_points_regular.get() {
1293 let points = scroll_snap_points_info
1294 .get_irregular_snap_points(Orientation::Horizontal, self.horizontal_snap_points_alignment());
1295 *self.horizontal_snap_points.borrow_mut() = Some(points);
1296 } else {
1297 *self.horizontal_snap_points.borrow_mut() = Some(Vec::new());
1298 let (snap_point, offset) = scroll_snap_points_info
1299 .get_regular_snap_points(Orientation::Horizontal, self.horizontal_snap_points_alignment());
1300 self.horizontal_snap_point.set(snap_point);
1301 self.horizontal_snap_point_offset.set(offset);
1302 }
1303 } else {
1304 *self.horizontal_snap_points.borrow_mut() = Some(Vec::new());
1305 *self.vertical_snap_points.borrow_mut() = Some(Vec::new());
1306 }
1307 }
1308
1309 fn snap_offset(&self, mut offset: Vector, direction: Vector, snap_to_next: bool) -> Vector {
1310 let content = self.content();
1311 let scrollable = self.get_scroll_snap_points_info(content.as_ref());
1312
1313 if scrollable.is_none()
1314 || (self.vertical_snap_points_type() == SnapPointsType::None
1315 && self.horizontal_snap_points_type() == SnapPointsType::None)
1316 {
1317 return offset;
1318 }
1319
1320 let diff = self.get_alignment_diff();
1321
1322 let has_vertical_points = self
1323 .vertical_snap_points
1324 .borrow()
1325 .as_ref()
1326 .is_some_and(|points| !points.is_empty());
1327 if self.vertical_snap_points_type() != SnapPointsType::None
1328 && (self.are_vertical_snap_points_regular.get() || has_vertical_points)
1329 && (!snap_to_next || direction.y != 0.0)
1330 {
1331 let estimated_offset = Vector::new(offset.x, offset.y + diff.y);
1332 let mut previous_snap_point = 0.0;
1333 let mut next_snap_point = 0.0;
1334 let mut mid_point = 0.0;
1335
1336 if self.are_vertical_snap_points_regular.get() {
1337 let snap_point = self.vertical_snap_point.get();
1338 previous_snap_point = ((estimated_offset.y / snap_point) as i32) as f64 * snap_point
1339 + self.vertical_snap_point_offset.get();
1340 next_snap_point = previous_snap_point + snap_point;
1341 mid_point = (previous_snap_point + next_snap_point) / 2.0;
1342 } else if has_vertical_points {
1343 if let Some(points) = self.vertical_snap_points.borrow().as_ref() {
1344 (previous_snap_point, next_snap_point) = Self::find_nearest_snap_point(points, estimated_offset.y);
1345 }
1346 mid_point = (previous_snap_point + next_snap_point) / 2.0;
1347 }
1348
1349 let nearest_snap_point = if snap_to_next {
1350 if direction.y > 0.0 {
1351 previous_snap_point
1352 } else {
1353 next_snap_point
1354 }
1355 } else if estimated_offset.y < mid_point {
1356 previous_snap_point
1357 } else {
1358 next_snap_point
1359 };
1360
1361 offset = Vector::new(offset.x, nearest_snap_point - diff.y);
1362 }
1363
1364 let has_horizontal_points = self
1365 .horizontal_snap_points
1366 .borrow()
1367 .as_ref()
1368 .is_some_and(|points| !points.is_empty());
1369 if self.horizontal_snap_points_type() != SnapPointsType::None
1370 && (self.are_horizontal_snap_points_regular.get() || has_horizontal_points)
1371 && (!snap_to_next || direction.x != 0.0)
1372 {
1373 let estimated_offset = Vector::new(offset.x + diff.x, offset.y);
1374 let mut previous_snap_point = 0.0;
1375 let mut next_snap_point = 0.0;
1376 let mut mid_point = 0.0;
1377
1378 if self.are_horizontal_snap_points_regular.get() {
1379 let snap_point = self.horizontal_snap_point.get();
1380 previous_snap_point = ((estimated_offset.x / snap_point) as i32) as f64 * snap_point
1381 + self.horizontal_snap_point_offset.get();
1382 next_snap_point = previous_snap_point + snap_point;
1383 mid_point = (previous_snap_point + next_snap_point) / 2.0;
1384 } else if has_horizontal_points {
1385 if let Some(points) = self.horizontal_snap_points.borrow().as_ref() {
1386 (previous_snap_point, next_snap_point) = Self::find_nearest_snap_point(points, estimated_offset.x);
1387 }
1388 mid_point = (previous_snap_point + next_snap_point) / 2.0;
1389 }
1390
1391 let nearest_snap_point = if snap_to_next {
1392 if direction.x > 0.0 {
1393 previous_snap_point
1394 } else {
1395 next_snap_point
1396 }
1397 } else if estimated_offset.x < mid_point {
1398 previous_snap_point
1399 } else {
1400 next_snap_point
1401 };
1402
1403 offset = Vector::new(nearest_snap_point - diff.x, offset.y);
1404 }
1405
1406 offset
1407 }
1408
1409 fn get_alignment_diff(&self) -> Vector {
1410 let mut vector = Vector::default();
1411 let viewport = self.viewport();
1412
1413 match self.vertical_snap_points_alignment() {
1414 SnapPointsAlignment::Center => vector += Vector::new(0.0, viewport.height / 2.0),
1415 SnapPointsAlignment::Far => vector += Vector::new(0.0, viewport.height),
1416 SnapPointsAlignment::Near => {}
1417 }
1418
1419 match self.horizontal_snap_points_alignment() {
1420 SnapPointsAlignment::Center => vector += Vector::new(viewport.width / 2.0, 0.0),
1421 SnapPointsAlignment::Far => vector += Vector::new(viewport.width, 0.0),
1422 SnapPointsAlignment::Near => {}
1423 }
1424
1425 vector
1426 }
1427
1428 fn find_nearest_snap_point(snap_points: &[f64], value: f64) -> (f64, f64) {
1431 match snap_points.binary_search_by(|point| point.partial_cmp(&value).unwrap_or(Ordering::Equal)) {
1432 Err(point) => {
1433 let previous_snap_point = snap_points[point.saturating_sub(1)];
1434 let next_snap_point = if point >= snap_points.len() {
1435 snap_points[snap_points.len() - 1]
1436 } else {
1437 snap_points[point]
1438 };
1439 (previous_snap_point, next_snap_point)
1440 }
1441 Ok(point) => (snap_points[point], snap_points[point]),
1442 }
1443 }
1444
1445 fn get_scroll_snap_points_info(&self, content: Option<&BoxedValue>) -> Option<Ref<Control>> {
1448 let mut scrollable = content.and_then(Control::from_boxed);
1449
1450 let own_content = self.content().as_ref().and_then(Control::from_boxed);
1451
1452 if let Some(items_control) = own_content.as_ref().and_then(|content| content.downcast_ref::<ItemsControl>()) {
1453 scrollable = items_control.presenter().and_then(|presenter| presenter.panel()).map(Ref::upcast);
1454 }
1455
1456 if let Some(items_presenter) = own_content.as_ref().and_then(|content| content.downcast_ref::<ItemsPresenter>())
1457 {
1458 scrollable = items_presenter.panel().map(Ref::upcast);
1459 }
1460
1461 let snap_points_info = scrollable.filter(|scrollable| as_scroll_snap_points_info(scrollable).is_some());
1462
1463 let current = self.scroll_snap_points_info.borrow().clone();
1464 if snap_points_info != current {
1465 let old_subscriptions = self.scroll_snap_points_subscriptions.borrow_mut().take();
1466 if let Some([vertical, horizontal]) = old_subscriptions {
1467 vertical.dispose();
1468 horizontal.dispose();
1469 }
1470
1471 *self.scroll_snap_points_info.borrow_mut() = snap_points_info.clone();
1472
1473 if let Some(info) = snap_points_info
1474 .as_ref()
1475 .and_then(|info| as_scroll_snap_points_info(info))
1476 {
1477 let weak = self.to_ref().downgrade();
1478 let handler: SnapPointsChangedHandler = Rc::new(move |_, _| {
1479 if let Some(this) = weak.upgrade() {
1480 this.update_snap_points();
1481 }
1482 });
1483 let vertical = info.vertical_snap_points_changed(handler.clone());
1484 let horizontal = info.horizontal_snap_points_changed(handler);
1485 *self.scroll_snap_points_subscriptions.borrow_mut() = Some([vertical, horizontal]);
1486 }
1487 }
1488
1489 snap_points_info
1490 }
1491
1492 fn adjust_delta_for_flow_direction(delta: Vector, flow_direction: FlowDirection) -> Vector {
1493 if flow_direction == FlowDirection::RightToLeft {
1494 return delta.with_x(-delta.x);
1495 }
1496 delta
1497 }
1498}
1499
1500fn fo(object: &ferroui_base::FerroObject) -> &ferroui_base::FerroObject {
1503 object
1504}
1505
1506#[derive(Default)]
1510struct AnchorCandidates {
1511 items: Vec<Ref<Control>>,
1512 positions: FxHashMap<*const Control, usize>,
1513}
1514
1515impl AnchorCandidates {
1516 fn add(&mut self, element: &Ref<Control>) {
1517 let key: *const Control = &**element;
1518 if !self.positions.contains_key(&key) {
1519 self.positions.insert(key, self.items.len());
1520 self.items.push(element.clone());
1521 }
1522 }
1523
1524 fn remove(&mut self, element: &Ref<Control>) {
1525 let key: *const Control = &**element;
1526 let Some(position) = self.positions.remove(&key) else { return };
1527 self.items.swap_remove(position);
1528 if let Some(moved) = self.items.get(position) {
1529 let moved: *const Control = &**moved;
1530 self.positions.insert(moved, position);
1531 }
1532 }
1533}