1use crate::generators::{ItemContainerGenerator, RecycleKey};
2use crate::i_scroll_anchor_provider::as_scroll_anchor_provider;
3use crate::items_source::ItemsChangedEventArgs;
4use crate::primitives::{
5 register_scroll_snap_points_info, IScrollSnapPointsInfo, SnapPointsAlignment, SnapPointsChangedHandler,
6};
7use crate::utils::{RealizedStackElements, VirtualizingSnapPointsList};
8use crate::{
9 Control, ControlImpl, ItemsControl, ItemsSourceView, PanelImpl, StackPanel, VirtualizingPanel,
10 VirtualizingPanelImpl, VirtualizingPanelImplExt,
11};
12use ferroui_base::collections::NotifyCollectionChangedAction;
13use ferroui_base::input::{InputElement, InputElementImpl, KeyboardNavigation, NavigationDirection};
14use ferroui_base::interactivity::{
15 Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
16};
17use ferroui_base::layout::{EffectiveViewportChangedEventArgs, LayoutableImpl, Orientation};
18use ferroui_base::logging::{LogArea, LogEventLevel, Logger};
19use ferroui_base::reactive::IDisposable;
20use ferroui_base::utilities::MathUtilities;
21use ferroui_base::{
22 ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, AttachedProperty, BoxedValue,
23 FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Rect, Ref, Size,
24 StyledElementImpl, StyledProperty, StyledPropertyOptions, Visual, VisualImpl, VisualImplExt,
25 VisualTreeAttachmentEventArgs, WeakRef,
26};
27use std::any::Any;
28use std::cell::{Cell, RefCell};
29use std::collections::HashMap;
30use std::rc::Rc;
31
32#[repr(C)]
35pub struct VirtualizingStackPanel {
36 base: VirtualizingPanel,
37 scroll_to_index: Cell<i32>,
38 scroll_to_element: RefCell<Option<Ref<Control>>>,
39 is_in_layout: Cell<bool>,
40 is_waiting_for_viewport_update: Cell<bool>,
41 last_estimated_element_size_u: Cell<f64>,
42 measure_elements: RefCell<Option<Rc<RealizedStackElements>>>,
43 realized_elements: RefCell<Option<Rc<RealizedStackElements>>>,
44 scroll_anchor_provider: RefCell<Option<WeakRef<Visual>>>,
46 viewport: Cell<Rect>,
47 recycle_pool: RefCell<HashMap<RecycleKey, Vec<Ref<Control>>>>,
48 focused_element: RefCell<Option<Ref<Control>>>,
49 focused_index: Cell<i32>,
50 realizing_element: RefCell<Option<Ref<Control>>>,
51 realizing_index: Cell<i32>,
52 buffer_factor: Cell<f64>,
53
54 has_reached_start: Cell<bool>,
55 has_reached_end: Cell<bool>,
56 last_measured_extended_viewport: Cell<Rect>,
57 last_known_extended_viewport: Cell<Rect>,
58
59 items_control_property_changed: RefCell<Option<Rc<dyn IDisposable>>>,
61}
62
63ferro_class!(VirtualizingStackPanel: VirtualizingPanel);
64ferroui_base::ferro_class_info!(VirtualizingStackPanel { new: VirtualizingStackPanel::new });
65ferro_impl_classes!(
66 VirtualizingStackPanel: StyledElementImpl,
67 InteractiveImpl,
68 InputElementImpl,
69 ControlImpl,
70 PanelImpl
71);
72
73#[derive(Clone, Copy, Default)]
74struct MeasureViewport {
75 anchor_index: i32,
76 anchor_u: f64,
77 viewport_u_start: f64,
78 viewport_u_end: f64,
79 measured_v: f64,
80 realized_end_u: f64,
81 last_index: i32,
82 viewport_is_disjunct: bool,
83}
84
85struct InLayoutGuard<'a>(&'a Cell<bool>);
87
88impl Drop for InLayoutGuard<'_> {
89 fn drop(&mut self) {
90 self.0.set(false);
91 }
92}
93
94impl FerroObjectImpl for VirtualizingStackPanel {
95 fn constructed(this: &Self) {
96 Self::parent_constructed(this);
97
98 this.buffer_factor.set(this.cache_length().max(0.0));
99
100 let weak = this.to_ref().downgrade();
101 let _ = this.effective_viewport_changed(move |e| {
102 if let Some(this) = weak.upgrade() {
103 this.on_effective_viewport_changed(e);
104 }
105 });
106 }
107}
108
109impl LayoutableImpl for VirtualizingStackPanel {
110 fn measure_override(this: &Self, available_size: Size) -> Size {
111 let items = this.items();
112 let item_count = items.count() as i32;
113
114 if item_count == 0 {
115 return Size::default();
116 }
117
118 let orientation = this.orientation();
119
120 if this.is_waiting_for_viewport_update.get() {
123 return this.estimate_desired_size(orientation, item_count);
124 }
125
126 this.is_in_layout.set(true);
127 let _in_layout = InLayoutGuard(&this.is_in_layout);
128
129 let realized_elements = this.realized_elements.borrow().clone();
130 if let Some(realized_elements) = &realized_elements {
131 realized_elements.validate_start_u(this.orientation());
132 }
133 let realized_elements = match realized_elements {
134 Some(realized_elements) => realized_elements,
135 None => {
136 let realized_elements = Rc::new(RealizedStackElements::new());
137 *this.realized_elements.borrow_mut() = Some(realized_elements.clone());
138 realized_elements
139 }
140 };
141 let measure_elements = this.measure_elements.borrow().clone();
142 let measure_elements = match measure_elements {
143 Some(measure_elements) => measure_elements,
144 None => {
145 let measure_elements = Rc::new(RealizedStackElements::new());
146 *this.measure_elements.borrow_mut() = Some(measure_elements.clone());
147 measure_elements
148 }
149 };
150
151 let _ = this.estimate_element_size_u();
154
155 let mut viewport = this.calculate_measure_viewport(orientation, item_count, &realized_elements);
160
161 if viewport.viewport_is_disjunct {
163 realized_elements.recycle_all_elements(|element, index| this.recycle_element(element, index));
164 }
165
166 this.realize_elements(&items, available_size, &mut viewport, &realized_elements, &measure_elements);
170
171 *this.measure_elements.borrow_mut() = Some(realized_elements.clone());
173 *this.realized_elements.borrow_mut() = Some(measure_elements);
174 realized_elements.reset_for_reuse();
175
176 let focused_element = this.focused_element.borrow().clone();
179 if let Some(focused_element) = focused_element {
180 focused_element.measure(available_size);
181 }
182
183 this.calculate_desired_size(orientation, item_count, &viewport)
184 }
185
186 fn arrange_override(this: &Self, final_size: Size) -> Size {
187 let Some(realized_elements) = this.realized_elements.borrow().clone() else {
188 return Size::default();
189 };
190
191 this.is_in_layout.set(true);
192
193 {
194 let _in_layout = InLayoutGuard(&this.is_in_layout);
195 let orientation = this.orientation();
196 let mut u = realized_elements.start_u();
197
198 if u.is_nan() {
204 u = this.get_or_estimate_element_u(realized_elements.first_index());
205 }
206
207 let provider_visual = this.scroll_anchor_provider();
208 let provider = provider_visual.as_ref().and_then(|visual| as_scroll_anchor_provider(visual));
209 let this_visual: Option<Ref<Visual>> = provider.map(|_| this.to_ref().upcast());
210
211 let mut i = 0;
212 while i < realized_elements.count() {
213 if let Some(e) = realized_elements.element_at(i) {
214 let size_u = realized_elements.size_u_at(i);
215 let rect = if orientation == Orientation::Horizontal {
216 Rect::new(u, 0.0, size_u, final_size.height)
217 } else {
218 Rect::new(0.0, u, final_size.width, size_u)
219 };
220
221 e.arrange(rect);
222
223 if let Some(provider) = provider {
224 if e.is_visible() && this.viewport.get().intersects(rect) {
225 if e.visual_parent() == this_visual {
228 provider.register_anchor_candidate(&e);
229 } else if let Some(logger) = Logger::try_get(LogEventLevel::Verbose, LogArea::LAYOUT) {
230 logger.log_with_values(
234 Some(this as &dyn Any),
235 "RegisterAnchorCandidate ignored for {Element}: not a descendant of ScrollAnchorProvider. {Message}",
236 &[
237 &e.get_type().name(),
238 &"An anchor control must be a visual descendent of the scroll anchor provider.",
239 ],
240 );
241 }
242 }
243 }
244
245 u += if orientation == Orientation::Horizontal { rect.width } else { rect.height };
246 }
247
248 i += 1;
249 }
250
251 let focused_element = this.focused_element.borrow().clone();
253 let focused_index = this.focused_index.get();
254 if let Some(focused_element) = focused_element.filter(|_| focused_index >= 0) {
255 let realized_end_u = u;
256 let size_u = if orientation == Orientation::Horizontal {
257 focused_element.desired_size().width
258 } else {
259 focused_element.desired_size().height
260 };
261
262 u = this.get_or_estimate_element_u(focused_index);
263
264 if realized_elements.count() > 0 && !realized_elements.start_u().is_nan() {
270 if focused_index < realized_elements.first_index() {
271 u = u.min(realized_elements.start_u() - size_u);
272 } else if focused_index > realized_elements.last_index() {
273 u = u.max(realized_end_u);
274 }
275 }
276
277 let rect = if orientation == Orientation::Horizontal {
278 Rect::new(u, 0.0, size_u, final_size.height)
279 } else {
280 Rect::new(0.0, u, final_size.width, size_u)
281 };
282
283 focused_element.arrange(rect);
284 }
285 }
286
287 let event = if this.orientation() == Orientation::Horizontal {
288 Self::horizontal_snap_points_changed_event()
289 } else {
290 Self::vertical_snap_points_changed_event()
291 };
292 this.raise_event(&RoutedEventArgs::with_event(event));
293
294 final_size
295 }
296}
297
298impl VisualImpl for VirtualizingStackPanel {
299 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
300 Self::parent_on_attached_to_visual_tree(this, e);
301
302 let mut provider = None;
303 let mut current = this.visual_parent();
304 while let Some(visual) = current {
305 if as_scroll_anchor_provider(&visual).is_some() {
306 provider = Some(visual.downgrade());
307 break;
308 }
309 current = visual.visual_parent();
310 }
311 *this.scroll_anchor_provider.borrow_mut() = provider;
312 }
313
314 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
315 Self::parent_on_detached_from_visual_tree(this, e);
316 *this.scroll_anchor_provider.borrow_mut() = None;
317 }
318}
319
320impl VirtualizingPanelImpl for VirtualizingStackPanel {
321 fn on_items_changed(this: &Self, _items: &Rc<ItemsSourceView>, e: &ItemsChangedEventArgs<'_>) {
322 this.invalidate_measure();
323
324 this.update_special_elements_on_items_changed(e);
326
327 let Some(realized_elements) = this.realized_elements.borrow().clone() else {
328 return;
329 };
330
331 let update_element_index =
332 |element: &Ref<Control>, old_index: i32, new_index: i32| this.update_element_index(element, old_index, new_index);
333 let recycle_element_on_item_removed = |element: &Ref<Control>| this.recycle_element_on_item_removed(element);
334
335 let mut action = e.action;
336 if action == NotifyCollectionChangedAction::Move && e.old_starting_index < 0 {
337 action = NotifyCollectionChangedAction::Reset;
338 }
339
340 match action {
341 NotifyCollectionChangedAction::Add => {
342 realized_elements.items_inserted(e.new_starting_index, e.new_items.len() as i32, update_element_index);
343 }
344 NotifyCollectionChangedAction::Remove => {
345 realized_elements.items_removed(
346 e.old_starting_index,
347 e.old_items.len() as i32,
348 update_element_index,
349 recycle_element_on_item_removed,
350 );
351 }
352 NotifyCollectionChangedAction::Replace => {
353 realized_elements.items_replaced(
354 e.old_starting_index,
355 e.old_items.len() as i32,
356 recycle_element_on_item_removed,
357 );
358 }
359 NotifyCollectionChangedAction::Move => {
360 realized_elements.items_removed(
361 e.old_starting_index,
362 e.old_items.len() as i32,
363 update_element_index,
364 recycle_element_on_item_removed,
365 );
366 let mut insert_index = e.new_starting_index;
367
368 if e.new_starting_index > e.old_starting_index {
369 insert_index -= e.old_items.len() as i32 - 1;
370 }
371
372 realized_elements.items_inserted(insert_index, e.new_items.len() as i32, update_element_index);
373 }
374 NotifyCollectionChangedAction::Reset => {
375 realized_elements.items_reset(recycle_element_on_item_removed);
376 }
377 }
378 }
379
380 fn on_items_control_changed(this: &Self, old_value: Option<&Ref<ItemsControl>>) {
381 Self::parent_on_items_control_changed(this, old_value);
382
383 if old_value.is_some() {
384 if let Some(subscription) = this.items_control_property_changed.take() {
385 subscription.dispose();
386 }
387 }
388 let items_control = this.items_control();
389 if let Some(items_control) = &items_control {
390 let weak = this.to_ref().downgrade();
391 let subscription = items_control.property_changed(move |e| {
392 if let Some(this) = weak.upgrade() {
393 this.on_items_control_property_changed(e);
394 }
395 });
396 *this.items_control_property_changed.borrow_mut() = Some(subscription);
397 }
398
399 let realized_elements = this.realized_elements.borrow().clone();
400 if let Some(realized_elements) = realized_elements {
401 realized_elements.reset_for_reuse();
402 }
403 let measure_elements = this.measure_elements.borrow().clone();
404 if let Some(measure_elements) = measure_elements {
405 measure_elements.reset_for_reuse();
406 }
407 if items_control.is_some() && this.focused_element.borrow().is_some() {
408 this.recycle_focused_element();
409 }
410 if items_control.is_some() && this.scroll_to_element.borrow().is_some() {
411 this.recycle_scroll_to_element();
412 }
413 if items_control.is_none() {
414 *this.focused_element.borrow_mut() = None;
415 *this.scroll_to_element.borrow_mut() = None;
416 }
417 this.focused_index.set(-1);
418 this.scroll_to_index.set(-1);
419 }
420
421 fn get_control_in_direction(
422 this: &Self,
423 direction: NavigationDirection,
424 from: Option<&Ref<InputElement>>,
425 wrap: bool,
426 ) -> Option<Ref<InputElement>> {
427 let count = this.items().count() as i32;
428 let from_control = from.and_then(|from| from.cast::<Control>());
429
430 if count == 0
431 || (from_control.is_none()
432 && direction != NavigationDirection::First
433 && direction != NavigationDirection::Last)
434 {
435 return None;
436 }
437
438 let horiz = this.orientation() == Orientation::Horizontal;
439 let from_index = match &from_control {
440 Some(from_control) => this.index_from_container(from_control),
441 None => -1,
442 };
443 let mut to_index = from_index;
444
445 match direction {
446 NavigationDirection::First => to_index = 0,
447 NavigationDirection::Last => to_index = count - 1,
448 NavigationDirection::Next => to_index += 1,
449 NavigationDirection::Previous => to_index -= 1,
450 NavigationDirection::Left => {
451 if horiz {
452 to_index -= 1;
453 }
454 }
455 NavigationDirection::Right => {
456 if horiz {
457 to_index += 1;
458 }
459 }
460 NavigationDirection::Up => {
461 if !horiz {
462 to_index -= 1;
463 }
464 }
465 NavigationDirection::Down => {
466 if !horiz {
467 to_index += 1;
468 }
469 }
470 _ => return None,
471 }
472
473 if from_index == to_index {
474 return from.cloned();
475 }
476
477 if wrap {
478 if to_index < 0 {
479 to_index = count - 1;
480 } else if to_index >= count {
481 to_index = 0;
482 }
483 }
484
485 this.scroll_into_view(to_index).map(|element| element.upcast())
486 }
487
488 fn get_realized_containers(this: &Self) -> Option<Vec<Ref<Control>>> {
489 let realized_elements = this.realized_elements.borrow().clone()?;
490 let elements = realized_elements.elements();
491 Some(elements.iter().flatten().cloned().collect())
492 }
493
494 fn container_from_index(this: &Self, index: i32) -> Option<Ref<Control>> {
495 let items = this.items();
496
497 if index < 0 || index as usize >= items.count() {
498 return None;
499 }
500 if this.scroll_to_index.get() == index {
501 return this.scroll_to_element.borrow().clone();
502 }
503 if this.focused_index.get() == index {
504 return this.focused_element.borrow().clone();
505 }
506 if index == this.realizing_index.get() {
507 return this.realizing_element.borrow().clone();
508 }
509 if let Some(realized) = this.get_realized_element(index) {
510 return Some(realized);
511 }
512 if let Some(c) = items.get_at(index as usize).as_ref().and_then(Control::from_boxed) {
513 if c.get_value(Self::recycle_key_property()) == Some(Self::item_is_its_own_container()) {
514 return Some(c);
515 }
516 }
517 None
518 }
519
520 fn index_from_container(this: &Self, container: &Ref<Control>) -> i32 {
521 if this.scroll_to_element.borrow().as_ref() == Some(container) {
522 return this.scroll_to_index.get();
523 }
524 if this.focused_element.borrow().as_ref() == Some(container) {
525 return this.focused_index.get();
526 }
527 if this.realizing_element.borrow().as_ref() == Some(container) {
528 return this.realizing_index.get();
529 }
530 let realized_elements = this.realized_elements.borrow().clone();
531 realized_elements.map_or(-1, |realized_elements| realized_elements.get_index(container))
532 }
533
534 fn scroll_into_view(this: &Self, index: i32) -> Option<Ref<Control>> {
535 let items = this.items();
536
537 if this.is_in_layout.get()
538 || index < 0
539 || index as usize >= items.count()
540 || this.realized_elements.borrow().is_none()
541 || !this.is_effectively_visible()
542 {
543 return None;
544 }
545
546 if let Some(element) = this.get_realized_element(index) {
547 element.bring_into_view();
548 return Some(element);
549 } else if let Some(root) = this.get_layout_root() {
550 let generator = this.generator();
553 let scroll_to_element = this.get_or_create_element(&items, &generator, index);
554
555 scroll_to_element.measure(Size::INFINITY);
556
557 let anchor_u = this.get_or_estimate_element_u(index);
559 let desired_size = scroll_to_element.desired_size();
560 let rect = if this.orientation() == Orientation::Horizontal {
561 Rect::new(anchor_u, 0.0, desired_size.width, desired_size.height)
562 } else {
563 Rect::new(0.0, anchor_u, desired_size.width, desired_size.height)
564 };
565 scroll_to_element.arrange(rect);
566
567 *this.scroll_to_element.borrow_mut() = Some(scroll_to_element.clone());
570 this.scroll_to_index.set(index);
571
572 if !this.bounds().contains_rect(rect) && !this.viewport.get().contains_rect(rect) {
578 this.is_waiting_for_viewport_update.set(true);
579 root.layout_manager().execute_layout_pass();
580 this.is_waiting_for_viewport_update.set(false);
581 }
582
583 scroll_to_element.bring_into_view();
585
586 this.is_waiting_for_viewport_update.set(!this.viewport.get().contains_rect(rect));
595 root.layout_manager().execute_layout_pass();
596
597 if this.is_waiting_for_viewport_update.get() {
601 this.is_waiting_for_viewport_update.set(false);
602 this.invalidate_measure();
603 root.layout_manager().execute_layout_pass();
604 }
605
606 scroll_to_element.bring_into_view();
614
615 if this.scroll_to_element.borrow().as_ref() == Some(&scroll_to_element) {
622 let scroll_to_index = this.scroll_to_index.get();
623 *this.scroll_to_element.borrow_mut() = None;
624 this.scroll_to_index.set(-1);
625 this.recycle_element(&scroll_to_element, scroll_to_index);
626 return None;
627 }
628
629 return Some(scroll_to_element);
630 }
631
632 None
633 }
634}
635
636ferroui_base::ferro_properties! { impl VirtualizingStackPanel, also [
637 VirtualizingStackPanel::cache_length_property,
638 VirtualizingStackPanel::recycle_key_property,
639] {
640 ferro_property!(
641 pub fn orientation_property() -> StyledProperty<Orientation> {
643 StackPanel::orientation_property().add_owner::<VirtualizingStackPanel>()
644 }
645 );
646
647 ferro_property!(
648 pub fn are_horizontal_snap_points_regular_property() -> StyledProperty<bool> {
650 FerroProperty::register::<VirtualizingStackPanel, _>("AreHorizontalSnapPointsRegular", false)
651 }
652 );
653
654 ferro_property!(
655 pub fn are_vertical_snap_points_regular_property() -> StyledProperty<bool> {
657 FerroProperty::register::<VirtualizingStackPanel, _>("AreVerticalSnapPointsRegular", false)
658 }
659 );
660} }
661
662impl VirtualizingStackPanel {
663 ferro_routed_event!(
664 pub fn horizontal_snap_points_changed_event() -> RoutedEvent<RoutedEventArgs> {
666 RoutedEvent::register::<VirtualizingStackPanel, _>("HorizontalSnapPointsChanged", RoutingStrategies::BUBBLE)
667 }
668 );
669
670 ferro_routed_event!(
671 pub fn vertical_snap_points_changed_event() -> RoutedEvent<RoutedEventArgs> {
673 RoutedEvent::register::<VirtualizingStackPanel, _>("VerticalSnapPointsChanged", RoutingStrategies::BUBBLE)
674 }
675 );
676
677 ferro_property!(for VirtualizingStackPanel;
678 pub fn cache_length_property() -> StyledProperty<f64> {
680 FerroProperty::register_with::<VirtualizingStackPanel, _>(
681 "CacheLength",
682 StyledPropertyOptions::new(0.0).validate(|v| *v >= 0.0 && *v <= 2.0),
683 )
684 }
685 );
686
687 ferro_property!(for VirtualizingStackPanel;
688 fn recycle_key_property() -> AttachedProperty<Option<RecycleKey>> {
689 FerroProperty::register_attached::<VirtualizingStackPanel, Control, _>("RecycleKey", None)
690 }
691 );
692
693 fn item_is_its_own_container() -> RecycleKey {
695 ferroui_base::ferro_thread_local! {
696 static KEY: RecycleKey = RecycleKey::new_unique();
697 }
698 KEY.with(|key| *key)
699 }
700
701 fn static_constructor() {
702 register_scroll_snap_points_info::<VirtualizingStackPanel>();
703 Self::cache_length_property()
704 .changed()
705 .add_class_handler::<VirtualizingStackPanel>(|x, e| x.on_cache_length_changed(e));
706 }
707
708 pub fn construct() -> Self {
710 Self {
711 base: VirtualizingPanel::construct(),
712 scroll_to_index: Cell::new(-1),
713 scroll_to_element: RefCell::new(None),
714 is_in_layout: Cell::new(false),
715 is_waiting_for_viewport_update: Cell::new(false),
716 last_estimated_element_size_u: Cell::new(25.0),
717 measure_elements: RefCell::new(None),
718 realized_elements: RefCell::new(None),
719 scroll_anchor_provider: RefCell::new(None),
720 viewport: Cell::new(Rect::default()),
721 recycle_pool: RefCell::new(HashMap::new()),
722 focused_element: RefCell::new(None),
723 focused_index: Cell::new(-1),
724 realizing_element: RefCell::new(None),
725 realizing_index: Cell::new(-1),
726 buffer_factor: Cell::new(0.0),
727 has_reached_start: Cell::new(false),
728 has_reached_end: Cell::new(false),
729 last_measured_extended_viewport: Cell::new(Rect::default()),
730 last_known_extended_viewport: Cell::new(Rect::default()),
731 items_control_property_changed: RefCell::new(None),
732 }
733 }
734
735 pub fn new() -> Ref<Self> {
736 instantiate(Self::construct())
737 }
738
739 pub fn orientation(&self) -> Orientation {
744 self.get_value(Self::orientation_property())
745 }
746
747 pub fn set_orientation(&self, value: Orientation) {
748 self.set_value(Self::orientation_property(), value)
749 }
750
751 pub fn horizontal_snap_points_changed(
753 &self,
754 handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static,
755 ) -> RoutedEventHandlerToken {
756 self.add_handler(Self::horizontal_snap_points_changed_event(), handler)
757 }
758
759 pub fn vertical_snap_points_changed(
761 &self,
762 handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static,
763 ) -> RoutedEventHandlerToken {
764 self.add_handler(Self::vertical_snap_points_changed_event(), handler)
765 }
766
767 pub fn are_horizontal_snap_points_regular(&self) -> bool {
770 self.get_value(Self::are_horizontal_snap_points_regular_property())
771 }
772
773 pub fn set_are_horizontal_snap_points_regular(&self, value: bool) {
774 self.set_value(Self::are_horizontal_snap_points_regular_property(), value)
775 }
776
777 pub fn are_vertical_snap_points_regular(&self) -> bool {
780 self.get_value(Self::are_vertical_snap_points_regular_property())
781 }
782
783 pub fn set_are_vertical_snap_points_regular(&self, value: bool) {
784 self.set_value(Self::are_vertical_snap_points_regular_property(), value)
785 }
786
787 pub fn cache_length(&self) -> f64 {
796 self.get_value(Self::cache_length_property())
797 }
798
799 pub fn set_cache_length(&self, value: f64) {
800 self.set_value(Self::cache_length_property(), value)
801 }
802
803 pub fn first_realized_index(&self) -> i32 {
806 let realized_elements = self.realized_elements.borrow();
807 realized_elements.as_ref().map_or(-1, |realized_elements| realized_elements.first_index())
808 }
809
810 pub fn last_realized_index(&self) -> i32 {
813 let realized_elements = self.realized_elements.borrow();
814 realized_elements.as_ref().map_or(-1, |realized_elements| realized_elements.last_index())
815 }
816
817 #[allow(dead_code)]
819 pub(crate) fn view_port(&self) -> Rect {
820 self.viewport.get()
821 }
822
823 #[allow(dead_code)]
827 pub(crate) fn last_measured_extended_view_port(&self) -> Rect {
828 self.last_measured_extended_viewport.get()
829 }
830
831 #[allow(dead_code)]
834 pub(crate) fn get_realized_elements(&self) -> Vec<Option<Ref<Control>>> {
835 let realized_elements = self.realized_elements.borrow();
836 realized_elements.as_ref().map_or_else(Vec::new, |realized_elements| realized_elements.elements().clone())
837 }
838
839 #[inline]
841 fn scroll_anchor_provider(&self) -> Option<Ref<Visual>> {
842 self.scroll_anchor_provider.borrow().as_ref().and_then(WeakRef::upgrade)
843 }
844
845 #[inline]
848 fn generator(&self) -> Rc<ItemContainerGenerator> {
849 match self.item_container_generator() {
850 Some(generator) => generator,
851 None => panic!("The VirtualizingPanel does not belong to an ItemsControl."),
852 }
853 }
854
855 fn update_special_elements_on_items_changed(&self, e: &ItemsChangedEventArgs<'_>) {
856 let new_count = e.new_items.len() as i32;
857 let old_count = e.old_items.len() as i32;
858
859 let mut action = e.action;
860 if action == NotifyCollectionChangedAction::Move && e.old_starting_index < 0 {
861 action = NotifyCollectionChangedAction::Reset;
862 }
863
864 match action {
865 NotifyCollectionChangedAction::Add => {
866 let focused_element = self.focused_element.borrow().clone();
867 if let Some(focused_element) = focused_element {
868 if e.new_starting_index <= self.focused_index.get() {
869 let old_index = self.focused_index.get();
870 self.focused_index.set(old_index + new_count);
871 self.update_element_index(&focused_element, old_index, self.focused_index.get());
872 }
873 }
874 if self.scroll_to_element.borrow().is_some() && e.new_starting_index <= self.scroll_to_index.get() {
875 self.scroll_to_index.set(self.scroll_to_index.get() + new_count);
876 }
877 }
878 NotifyCollectionChangedAction::Remove => {
879 let focused_element = self.focused_element.borrow().clone();
880 if let Some(focused_element) = focused_element {
881 let focused_index = self.focused_index.get();
882 if e.old_starting_index <= focused_index && focused_index < e.old_starting_index + old_count {
883 self.recycle_focused_element();
884 } else if e.old_starting_index < focused_index {
885 let old_index = focused_index;
886 self.focused_index.set(focused_index - old_count);
887 self.update_element_index(&focused_element, old_index, self.focused_index.get());
888 }
889 }
890 if self.scroll_to_element.borrow().is_some() {
891 let scroll_to_index = self.scroll_to_index.get();
892 if e.old_starting_index <= scroll_to_index && scroll_to_index < e.old_starting_index + old_count {
893 self.recycle_scroll_to_element();
894 } else if e.old_starting_index < scroll_to_index {
895 self.scroll_to_index.set(scroll_to_index - old_count);
896 }
897 }
898 }
899 NotifyCollectionChangedAction::Replace => {
900 let focused_index = self.focused_index.get();
901 if self.focused_element.borrow().is_some()
902 && e.old_starting_index <= focused_index
903 && focused_index < e.old_starting_index + old_count
904 {
905 self.recycle_focused_element();
906 }
907 let scroll_to_index = self.scroll_to_index.get();
908 if self.scroll_to_element.borrow().is_some()
909 && e.old_starting_index <= scroll_to_index
910 && scroll_to_index < e.old_starting_index + old_count
911 {
912 self.recycle_scroll_to_element();
913 }
914 }
915 NotifyCollectionChangedAction::Move => {
916 let focused_element = self.focused_element.borrow().clone();
917 if let Some(focused_element) = focused_element {
918 let focused_index = self.focused_index.get();
919 if e.old_starting_index <= focused_index && focused_index < e.old_starting_index + old_count {
920 let old_index = focused_index;
921 self.focused_index.set(e.new_starting_index + (focused_index - e.old_starting_index));
922 self.update_element_index(&focused_element, old_index, self.focused_index.get());
923 } else {
924 let mut new_focused_index = focused_index;
925
926 if e.old_starting_index < focused_index {
927 new_focused_index -= old_count;
928 }
929
930 if e.new_starting_index <= new_focused_index {
931 new_focused_index += new_count;
932 }
933
934 if new_focused_index != focused_index {
935 let old_index = focused_index;
936 self.focused_index.set(new_focused_index);
937 self.update_element_index(&focused_element, old_index, new_focused_index);
938 }
939 }
940 }
941
942 if self.scroll_to_element.borrow().is_some() {
943 let scroll_to_index = self.scroll_to_index.get();
944 if e.old_starting_index <= scroll_to_index && scroll_to_index < e.old_starting_index + old_count {
945 self.scroll_to_index.set(e.new_starting_index + (scroll_to_index - e.old_starting_index));
946 } else {
947 let mut new_scroll_to_index = scroll_to_index;
948
949 if e.old_starting_index < scroll_to_index {
950 new_scroll_to_index -= old_count;
951 }
952
953 if e.new_starting_index <= new_scroll_to_index {
954 new_scroll_to_index += new_count;
955 }
956
957 self.scroll_to_index.set(new_scroll_to_index);
958 }
959 }
960 }
961 NotifyCollectionChangedAction::Reset => {
962 if self.focused_element.borrow().is_some() {
963 self.recycle_focused_element();
964 }
965 if self.scroll_to_element.borrow().is_some() {
966 self.recycle_scroll_to_element();
967 }
968 }
969 }
970 }
971
972 fn calculate_measure_viewport(
973 &self,
974 orientation: Orientation,
975 item_count: i32,
976 realized_elements: &RealizedStackElements,
977 ) -> MeasureViewport {
978 let viewport = self.last_measured_extended_viewport.get();
980
981 let viewport_start = if orientation == Orientation::Horizontal { viewport.x } else { viewport.y };
983 let viewport_end = if orientation == Orientation::Horizontal { viewport.right() } else { viewport.bottom() };
984
985 let anchor_index;
990 let anchor_u;
991
992 let scroll_to_bounds = if self.scroll_to_index.get() >= 0 {
993 self.scroll_to_element.borrow().as_ref().map(|element| element.bounds())
994 } else {
995 None
996 };
997
998 if let Some(bounds) = scroll_to_bounds {
999 anchor_index = self.scroll_to_index.get();
1000 anchor_u = if orientation == Orientation::Horizontal { bounds.left() } else { bounds.top() };
1001 } else {
1002 (anchor_index, anchor_u) =
1003 self.get_or_estimate_anchor_element_for_viewport(viewport_start, viewport_end, item_count);
1004 }
1005
1006 let disjunct = anchor_index < realized_elements.first_index() || anchor_index > realized_elements.last_index();
1009
1010 MeasureViewport {
1011 anchor_index,
1012 anchor_u,
1013 viewport_u_start: viewport_start,
1014 viewport_u_end: viewport_end,
1015 viewport_is_disjunct: disjunct,
1016 ..MeasureViewport::default()
1017 }
1018 }
1019
1020 fn calculate_desired_size(&self, orientation: Orientation, item_count: i32, viewport: &MeasureViewport) -> Size {
1021 let mut size_u = 0.0;
1022 let size_v = viewport.measured_v;
1023
1024 if viewport.last_index >= 0 {
1025 let remaining = item_count - viewport.last_index - 1;
1026 size_u = viewport.realized_end_u + (remaining as f64 * self.last_estimated_element_size_u.get());
1027 }
1028
1029 if orientation == Orientation::Horizontal {
1030 Size::new(size_u, size_v)
1031 } else {
1032 Size::new(size_v, size_u)
1033 }
1034 }
1035
1036 fn estimate_desired_size(&self, orientation: Orientation, item_count: i32) -> Size {
1037 let scroll_to_bounds = if self.scroll_to_index.get() >= 0 {
1038 self.scroll_to_element.borrow().as_ref().map(|element| element.bounds())
1039 } else {
1040 None
1041 };
1042
1043 if let Some(bounds) = scroll_to_bounds {
1044 let remaining = item_count - self.scroll_to_index.get() - 1;
1048 let u = if orientation == Orientation::Horizontal { bounds.right() } else { bounds.bottom() };
1049 let size_u = u + (remaining as f64 * self.last_estimated_element_size_u.get());
1050 return if orientation == Orientation::Horizontal {
1051 Size::new(size_u, self.desired_size().height)
1052 } else {
1053 Size::new(self.desired_size().width, size_u)
1054 };
1055 }
1056
1057 self.desired_size()
1058 }
1059
1060 fn estimate_element_size_u(&self) -> f64 {
1061 let realized_elements = self.realized_elements.borrow();
1062 let Some(realized_elements) = realized_elements.as_ref() else {
1063 return self.last_estimated_element_size_u.get();
1064 };
1065
1066 let orientation = self.orientation();
1067 let mut total = 0.0;
1068 let mut divisor = 0.0;
1069
1070 for element in realized_elements.elements().iter() {
1072 let Some(element) = element else { continue };
1073 if !element.is_measure_valid() {
1074 continue;
1075 }
1076 let size_u = if orientation == Orientation::Horizontal {
1077 element.desired_size().width
1078 } else {
1079 element.desired_size().height
1080 };
1081 total += size_u;
1082 divisor += 1.0;
1083 }
1084
1085 if divisor == 0.0 || total == 0.0 {
1087 return self.last_estimated_element_size_u.get();
1088 }
1089
1090 let estimate = total / divisor;
1092 self.last_estimated_element_size_u.set(estimate);
1093 estimate
1094 }
1095
1096 fn get_or_estimate_anchor_element_for_viewport(
1098 &self,
1099 viewport_start_u: f64,
1100 viewport_end_u: f64,
1101 item_count: i32,
1102 ) -> (i32, f64) {
1103 if item_count <= 0 || MathUtilities::is_zero(viewport_start_u) {
1105 return (0, 0.0);
1106 }
1107
1108 let horizontal = self.orientation() == Orientation::Horizontal;
1109 let viewport = self.viewport.get();
1110 let viewport_end = if horizontal { viewport.right() } else { viewport.bottom() };
1111
1112 if !self.has_reached_end.get()
1113 && MathUtilities::greater_than_or_close(
1114 viewport_end,
1115 if horizontal { self.bounds().width } else { self.bounds().height },
1116 )
1117 {
1118 return (item_count - 1, viewport_end - self.estimate_element_size_u());
1119 }
1120
1121 {
1124 let realized_elements = self.realized_elements.borrow();
1125 if let Some(realized_elements) = realized_elements.as_ref() {
1126 let mut u = realized_elements.start_u();
1127 if !u.is_nan() {
1128 let elements = realized_elements.elements();
1129
1130 for (i, element) in elements.iter().enumerate() {
1131 let Some(element) = element else { continue };
1132
1133 let size_u =
1134 if horizontal { element.desired_size().width } else { element.desired_size().height };
1135 let end_u = u + size_u;
1136
1137 if end_u > viewport_start_u && u < viewport_end_u {
1138 return (realized_elements.first_index() + i as i32, u);
1139 }
1140
1141 u = end_u;
1142 }
1143 }
1144 }
1145 }
1146
1147 let estimated_size = self.estimate_element_size_u();
1151
1152 let start_index = ((viewport_start_u / estimated_size) as i32).min(item_count - 1);
1154 (start_index, start_index as f64 * estimated_size)
1155 }
1156
1157 fn get_or_estimate_element_u(&self, index: i32) -> f64 {
1158 let realized_elements = self.realized_elements.borrow().clone();
1159
1160 let u = realized_elements.as_ref().map_or(f64::NAN, |realized| realized.get_element_u(index));
1162
1163 if !u.is_nan() {
1164 return u;
1165 }
1166
1167 let estimated_size = self.estimate_element_size_u();
1169
1170 if let Some(realized) = realized_elements.as_ref().filter(|realized| !realized.start_u().is_nan()) {
1173 let first = realized.first_index();
1174 let last = realized.last_index();
1175
1176 if index < first {
1177 return realized.start_u() * index as f64 / first as f64;
1182 }
1183
1184 if index > last {
1185 let sizes = realized.size_u();
1186 let mut realized_span = 0.0;
1187
1188 for size_u in sizes.iter() {
1189 realized_span += if size_u.is_nan() { estimated_size } else { *size_u };
1190 }
1191
1192 return realized.start_u() + realized_span + ((index - last - 1) as f64 * estimated_size);
1193 }
1194 }
1195
1196 index as f64 * estimated_size
1197 }
1198
1199 fn realize_elements(
1200 &self,
1201 items: &ItemsSourceView,
1202 available_size: Size,
1203 viewport: &mut MeasureViewport,
1204 realized_elements: &RealizedStackElements,
1205 measure_elements: &RealizedStackElements,
1206 ) {
1207 let item_count = items.count() as i32;
1208 debug_assert!(item_count > 0);
1209
1210 let generator = self.generator();
1211 let mut index = viewport.anchor_index;
1212 let horizontal = self.orientation() == Orientation::Horizontal;
1213 let mut u = viewport.anchor_u;
1214 let viewport_end = if horizontal { self.viewport.get().right() } else { self.viewport.get().bottom() };
1215
1216 let anchor_at_end = !self.has_reached_end.get()
1217 && index == item_count - 1
1218 && !MathUtilities::is_zero(viewport.viewport_u_start)
1219 && MathUtilities::greater_than_or_close(
1220 viewport_end,
1221 if horizontal { self.bounds().width } else { self.bounds().height },
1222 );
1223
1224 self.has_reached_start.set(false);
1226 self.has_reached_end.set(false);
1227
1228 if u <= viewport.anchor_u {
1231 realized_elements
1232 .recycle_elements_before(viewport.anchor_index, |element, index| self.recycle_element(element, index));
1233 }
1234
1235 loop {
1237 self.realizing_index.set(index);
1238 let e = self.get_or_create_element(items, &generator, index);
1239 *self.realizing_element.borrow_mut() = Some(e.clone());
1240
1241 e.measure(available_size);
1242
1243 let desired_size = e.desired_size();
1244 let size_u = if horizontal { desired_size.width } else { desired_size.height };
1245 let size_v = if horizontal { desired_size.height } else { desired_size.width };
1246
1247 if anchor_at_end && index == viewport.anchor_index {
1248 u = viewport_end - size_u;
1249 viewport.anchor_u = u;
1250 }
1251
1252 measure_elements.add(index, e, u, size_u);
1253 viewport.measured_v = viewport.measured_v.max(size_v);
1254
1255 u += size_u;
1256 index += 1;
1257 self.realizing_index.set(-1);
1258 *self.realizing_element.borrow_mut() = None;
1259
1260 if !(u < viewport.viewport_u_end && index < item_count) {
1261 break;
1262 }
1263 }
1264
1265 self.has_reached_end.set(index >= item_count);
1267
1268 viewport.last_index = index - 1;
1271 viewport.realized_end_u = u;
1272
1273 realized_elements
1275 .recycle_elements_after(viewport.last_index, |element, index| self.recycle_element(element, index));
1276
1277 index = viewport.anchor_index - 1;
1279 u = viewport.anchor_u;
1280
1281 while u > viewport.viewport_u_start && index >= 0 {
1282 let e = self.get_or_create_element(items, &generator, index);
1283
1284 e.measure(available_size);
1285 let desired_size = e.desired_size();
1286 let size_u = if horizontal { desired_size.width } else { desired_size.height };
1287 let size_v = if horizontal { desired_size.height } else { desired_size.width };
1288 u -= size_u;
1289
1290 measure_elements.add(index, e, u, size_u);
1291 viewport.measured_v = viewport.measured_v.max(size_v);
1292 index -= 1;
1293 }
1294
1295 self.has_reached_start.set(index < 0);
1297
1298 realized_elements.recycle_elements_before(index + 1, |element, index| self.recycle_element(element, index));
1300 }
1301
1302 fn get_or_create_element(
1303 &self,
1304 items: &ItemsSourceView,
1305 generator: &ItemContainerGenerator,
1306 index: i32,
1307 ) -> Ref<Control> {
1308 if let Some(realized) = self
1309 .get_realized_element(index)
1310 .or_else(|| Self::get_realized_special_element(index, &self.focused_index, &self.focused_element))
1311 .or_else(|| Self::get_realized_special_element(index, &self.scroll_to_index, &self.scroll_to_element))
1312 {
1313 return realized;
1314 }
1315
1316 let item = items.get_at(index as usize);
1317 let (needs_container, recycle_key) = generator.needs_container(&item, index);
1318
1319 if needs_container {
1320 match self.get_recycled_element(generator, &item, index, recycle_key) {
1321 Some(recycled) => recycled,
1322 None => self.create_element(generator, &item, index, recycle_key),
1323 }
1324 } else {
1325 self.get_item_as_own_container(generator, &item, index)
1326 }
1327 }
1328
1329 #[inline]
1330 fn get_realized_element(&self, index: i32) -> Option<Ref<Control>> {
1331 let realized_elements = self.realized_elements.borrow();
1332 realized_elements.as_ref().and_then(|realized_elements| realized_elements.get_element(index))
1333 }
1334
1335 #[inline]
1336 fn get_realized_special_element(
1337 index: i32,
1338 special_index: &Cell<i32>,
1339 special_element: &RefCell<Option<Ref<Control>>>,
1340 ) -> Option<Ref<Control>> {
1341 if special_index.get() == index {
1342 let result = special_element.take();
1343 debug_assert!(result.is_some());
1344 special_index.set(-1);
1345 return result;
1346 }
1347
1348 None
1349 }
1350
1351 fn get_item_as_own_container(
1352 &self,
1353 generator: &ItemContainerGenerator,
1354 item: &Option<BoxedValue>,
1355 index: i32,
1356 ) -> Ref<Control> {
1357 let control_item = match item.as_ref().and_then(Control::from_boxed) {
1358 Some(control_item) => control_item,
1359 None => panic!("An item that is its own container must be a control."),
1360 };
1361
1362 if !control_item.is_set(Self::recycle_key_property().as_property()) {
1363 generator.prepare_item_container(&control_item, item, index);
1364 self.add_internal_child(&control_item);
1365 control_item.set_value(Self::recycle_key_property(), Some(Self::item_is_its_own_container()));
1366 generator.item_container_prepared(&control_item, item, index);
1367 }
1368
1369 control_item.set_current_value(Visual::is_visible_property(), true);
1370 control_item
1371 }
1372
1373 fn get_recycled_element(
1374 &self,
1375 generator: &ItemContainerGenerator,
1376 item: &Option<BoxedValue>,
1377 index: i32,
1378 recycle_key: Option<RecycleKey>,
1379 ) -> Option<Ref<Control>> {
1380 let recycle_key = recycle_key?;
1381
1382 let recycled = self.recycle_pool.borrow_mut().get_mut(&recycle_key).and_then(Vec::pop)?;
1383 ferroui_base::perf_count!(ContainersReused);
1384
1385 recycled.set_current_value(Visual::is_visible_property(), true);
1386 generator.prepare_item_container(&recycled, item, index);
1387 self.add_internal_child(&recycled);
1388 generator.item_container_prepared(&recycled, item, index);
1389 Some(recycled)
1390 }
1391
1392 fn create_element(
1393 &self,
1394 generator: &ItemContainerGenerator,
1395 item: &Option<BoxedValue>,
1396 index: i32,
1397 recycle_key: Option<RecycleKey>,
1398 ) -> Ref<Control> {
1399 ferroui_base::perf_count!(ContainersCreated);
1400 let container = generator.create_container(item, index, recycle_key);
1401
1402 container.set_value(Self::recycle_key_property(), recycle_key);
1403 generator.prepare_item_container(&container, item, index);
1404 self.add_internal_child(&container);
1405 generator.item_container_prepared(&container, item, index);
1406
1407 container
1408 }
1409
1410 fn unregister_anchor_candidate(&self, element: &Ref<Control>) {
1411 if let Some(provider_visual) = self.scroll_anchor_provider() {
1412 if let Some(provider) = as_scroll_anchor_provider(&provider_visual) {
1413 provider.unregister_anchor_candidate(element);
1414 }
1415 }
1416 }
1417
1418 fn recycle_element(&self, element: &Ref<Control>, index: i32) {
1419 debug_assert!(self.items_control().is_some());
1420
1421 self.unregister_anchor_candidate(element);
1422
1423 let recycle_key = element.get_value(Self::recycle_key_property());
1424
1425 match recycle_key {
1426 None => {
1427 self.generator().clear_item_container(element);
1428 self.remove_internal_child(element);
1429 }
1430 Some(recycle_key) if recycle_key == Self::item_is_its_own_container() => {
1431 element.set_current_value(Visual::is_visible_property(), false);
1432 }
1433 Some(recycle_key) => {
1434 let tab_once_active_element =
1435 self.items_control().and_then(|items_control| KeyboardNavigation::get_tab_once_active_element(&items_control));
1436
1437 if tab_once_active_element.is_some_and(|active| active == *element) {
1438 *self.focused_element.borrow_mut() = Some(element.clone());
1439 self.focused_index.set(index);
1440 } else {
1441 ferroui_base::perf_count!(ContainersRecycled);
1442 self.generator().clear_item_container(element);
1443 self.push_to_recycle_pool(recycle_key, element);
1444 element.set_current_value(Visual::is_visible_property(), false);
1445 self.remove_internal_child(element);
1446 }
1447 }
1448 }
1449 }
1450
1451 fn recycle_element_on_item_removed(&self, element: &Ref<Control>) {
1452 self.unregister_anchor_candidate(element);
1453
1454 let recycle_key = element.get_value(Self::recycle_key_property());
1455
1456 match recycle_key {
1457 None => {
1458 self.generator().clear_item_container(element);
1459 self.remove_internal_child(element);
1460 }
1461 Some(recycle_key) if recycle_key == Self::item_is_its_own_container() => {
1462 self.remove_internal_child(element);
1463 }
1464 Some(recycle_key) => {
1465 self.generator().clear_item_container(element);
1466 self.push_to_recycle_pool(recycle_key, element);
1467 element.set_current_value(Visual::is_visible_property(), false);
1468 self.remove_internal_child(element);
1469 }
1470 }
1471 }
1472
1473 fn recycle_focused_element(&self) {
1474 let focused_element = self.focused_element.borrow().clone();
1475 if let Some(focused_element) = focused_element {
1476 self.recycle_element_on_item_removed(&focused_element);
1477 }
1478 *self.focused_element.borrow_mut() = None;
1479 self.focused_index.set(-1);
1480 }
1481
1482 fn recycle_scroll_to_element(&self) {
1483 let scroll_to_element = self.scroll_to_element.borrow().clone();
1484 if let Some(scroll_to_element) = scroll_to_element {
1485 self.recycle_element_on_item_removed(&scroll_to_element);
1486 }
1487 *self.scroll_to_element.borrow_mut() = None;
1488 self.scroll_to_index.set(-1);
1489 }
1490
1491 fn push_to_recycle_pool(&self, recycle_key: RecycleKey, element: &Ref<Control>) {
1492 self.recycle_pool.borrow_mut().entry(recycle_key).or_default().push(element.clone());
1493 }
1494
1495 fn update_element_index(&self, element: &Ref<Control>, old_index: i32, new_index: i32) {
1496 self.generator().item_container_index_changed(element, old_index, new_index);
1497 }
1498
1499 fn calculate_extended_viewport(&self, vertical: bool, _viewport_size: f64, buffer_size: f64) -> Rect {
1500 let viewport = self.viewport.get();
1501 let bounds = self.bounds();
1502
1503 let mut extended_viewport_start = if vertical {
1504 (viewport.top() - buffer_size).max(0.0)
1505 } else {
1506 (viewport.left() - buffer_size).max(0.0)
1507 };
1508
1509 let mut extended_viewport_end = if vertical {
1510 bounds.height.min(viewport.bottom() + buffer_size)
1511 } else {
1512 bounds.width.min(viewport.right() + buffer_size)
1513 };
1514
1515 if vertical {
1520 let space_above = viewport.top() - buffer_size;
1521 let space_below = bounds.height - (viewport.bottom() + buffer_size);
1522
1523 if space_above < 0.0 && space_below >= 0.0 {
1524 extended_viewport_end = bounds.height.min(extended_viewport_end + space_above.abs());
1525 }
1526 if space_above >= 0.0 && space_below < 0.0 {
1527 extended_viewport_start = (extended_viewport_start - space_below.abs()).max(0.0);
1528 }
1529 } else {
1530 let space_left = viewport.left() - buffer_size;
1531 let space_right = bounds.width - (viewport.right() + buffer_size);
1532
1533 if space_left < 0.0 && space_right >= 0.0 {
1534 extended_viewport_end = bounds.width.min(extended_viewport_end + space_left.abs());
1535 }
1536 if space_left >= 0.0 && space_right < 0.0 {
1537 extended_viewport_start = (extended_viewport_start - space_right.abs()).max(0.0);
1538 }
1539 }
1540
1541 if vertical {
1542 Rect::new(viewport.x, extended_viewport_start, viewport.width, extended_viewport_end - extended_viewport_start)
1543 } else {
1544 Rect::new(extended_viewport_start, viewport.y, extended_viewport_end - extended_viewport_start, viewport.height)
1545 }
1546 }
1547
1548 fn on_effective_viewport_changed(&self, e: &EffectiveViewportChangedEventArgs) {
1549 let vertical = self.orientation() == Orientation::Vertical;
1550 let old_viewport = self.viewport.get();
1551 let old_extended_viewport = self.last_measured_extended_viewport.get();
1552 let old_viewport_start = if vertical { old_viewport.top() } else { old_viewport.left() };
1553 let old_viewport_end = if vertical { old_viewport.bottom() } else { old_viewport.right() };
1554 let old_extended_viewport_start =
1555 if vertical { old_extended_viewport.top() } else { old_extended_viewport.left() };
1556 let old_extended_viewport_end =
1557 if vertical { old_extended_viewport.bottom() } else { old_extended_viewport.right() };
1558
1559 let viewport = e.effective_viewport().intersect(Rect::from_size(self.bounds().size()));
1561 self.viewport.set(viewport);
1562 self.is_waiting_for_viewport_update.set(false);
1563
1564 let viewport_size = if vertical { viewport.height } else { viewport.width };
1566 let buffer_size = viewport_size * self.buffer_factor.get();
1567
1568 let extended_viewport = self.calculate_extended_viewport(vertical, viewport_size, buffer_size);
1569
1570 let new_viewport_start = if vertical { viewport.top() } else { viewport.left() };
1572 let new_viewport_end = if vertical { viewport.bottom() } else { viewport.right() };
1573 let new_extended_viewport_start = if vertical { extended_viewport.top() } else { extended_viewport.left() };
1574 let new_extended_viewport_end = if vertical { extended_viewport.bottom() } else { extended_viewport.right() };
1575
1576 let mut needs_measure = false;
1577
1578 if !MathUtilities::are_close(old_viewport_start, new_viewport_start)
1580 || !MathUtilities::are_close(old_viewport_end, new_viewport_end)
1581 {
1582 if new_viewport_start < old_extended_viewport_start || new_viewport_end > old_extended_viewport_end {
1584 needs_measure = true;
1585 }
1586 else if !MathUtilities::are_close(old_extended_viewport_start, new_extended_viewport_start)
1588 || !MathUtilities::are_close(old_extended_viewport_end, new_extended_viewport_end)
1589 {
1590 let mut nearing_edge = false;
1594
1595 if self.realized_elements.borrow().is_some() {
1596 if new_viewport_start < old_viewport_start
1599 && new_viewport_start - new_extended_viewport_start < buffer_size
1600 {
1601 nearing_edge = !self.has_reached_start.get();
1607 }
1608
1609 if new_viewport_end > old_viewport_end
1612 && new_extended_viewport_end - new_viewport_end < buffer_size
1613 {
1614 nearing_edge = !self.has_reached_end.get();
1620 }
1621 } else {
1622 nearing_edge = true;
1623 }
1624
1625 needs_measure = nearing_edge;
1626 }
1627 }
1628
1629 if !needs_measure {
1636 let last_known = self.last_known_extended_viewport.get();
1637 let last_known_start = if vertical { last_known.top() } else { last_known.left() };
1638 let last_known_end = if vertical { last_known.bottom() } else { last_known.right() };
1639 if new_viewport_start < last_known_start || new_viewport_end > last_known_end {
1640 needs_measure = true;
1641 }
1642 }
1643
1644 self.last_known_extended_viewport.set(extended_viewport);
1645
1646 if needs_measure {
1647 self.last_measured_extended_viewport.set(extended_viewport);
1652 self.invalidate_measure();
1653 }
1654 }
1655
1656 fn on_items_control_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1657 let focused_element = self.focused_element.borrow().clone();
1658 let Some(focused_element) = focused_element else { return };
1659
1660 if e.property() == KeyboardNavigation::tab_once_active_element_property().as_property()
1661 && e.get_old_value::<Option<Ref<InputElement>>>().flatten().is_some_and(|old| old == focused_element)
1662 {
1663 self.recycle_element(&focused_element, self.focused_index.get());
1666 *self.focused_element.borrow_mut() = None;
1667 self.focused_index.set(-1);
1668 }
1669 }
1670
1671 fn on_cache_length_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1672 let new_value = e.get_new_value::<f64>();
1673 self.buffer_factor.set(new_value);
1674
1675 self.invalidate_measure();
1678 }
1679
1680 pub fn get_irregular_snap_points(
1683 &self,
1684 orientation: Orientation,
1685 snap_points_alignment: SnapPointsAlignment,
1686 ) -> Vec<f64> {
1687 let Some(realized_elements) = self.realized_elements.borrow().clone() else {
1688 return Vec::new();
1689 };
1690
1691 let count = self
1692 .items_control()
1693 .and_then(|items_control| items_control.items_source())
1694 .map_or(0, |items_source| items_source.count() as i32);
1695
1696 VirtualizingSnapPointsList::new(
1697 realized_elements,
1698 count,
1699 orientation,
1700 self.orientation(),
1701 snap_points_alignment,
1702 self.estimate_element_size_u(),
1703 )
1704 .iter()
1705 .collect()
1706 }
1707
1708 pub fn get_regular_snap_points(
1713 &self,
1714 _orientation: Orientation,
1715 snap_points_alignment: SnapPointsAlignment,
1716 ) -> (f64, f64) {
1717 let mut offset = 0.0;
1718 let first_realized_child = {
1719 let realized_elements = self.realized_elements.borrow();
1720 realized_elements.as_ref().and_then(|realized_elements| realized_elements.element_at(0))
1721 };
1722
1723 let Some(first_realized_child) = first_realized_child else {
1724 return (0.0, offset);
1725 };
1726
1727 let bounds = first_realized_child.bounds();
1728
1729 let snap_point = match self.orientation() {
1730 Orientation::Horizontal => {
1731 if !self.are_horizontal_snap_points_regular() {
1732 panic!("The horizontal snap points are not regular.");
1733 }
1734
1735 match snap_points_alignment {
1736 SnapPointsAlignment::Near => offset = 0.0,
1737 SnapPointsAlignment::Center => offset = (bounds.right() - bounds.left()) / 2.0,
1738 SnapPointsAlignment::Far => offset = bounds.width,
1739 }
1740 bounds.width
1741 }
1742 Orientation::Vertical => {
1743 if !self.are_vertical_snap_points_regular() {
1744 panic!("The vertical snap points are not regular.");
1745 }
1746
1747 match snap_points_alignment {
1748 SnapPointsAlignment::Near => offset = 0.0,
1749 SnapPointsAlignment::Center => offset = (bounds.bottom() - bounds.top()) / 2.0,
1750 SnapPointsAlignment::Far => offset = bounds.height,
1751 }
1752 bounds.height
1753 }
1754 };
1755
1756 (snap_point, offset)
1757 }
1758}
1759
1760impl IScrollSnapPointsInfo for VirtualizingStackPanel {
1761 fn are_horizontal_snap_points_regular(&self) -> bool {
1762 VirtualizingStackPanel::are_horizontal_snap_points_regular(self)
1763 }
1764
1765 fn set_are_horizontal_snap_points_regular(&self, value: bool) {
1766 VirtualizingStackPanel::set_are_horizontal_snap_points_regular(self, value)
1767 }
1768
1769 fn are_vertical_snap_points_regular(&self) -> bool {
1770 VirtualizingStackPanel::are_vertical_snap_points_regular(self)
1771 }
1772
1773 fn set_are_vertical_snap_points_regular(&self, value: bool) {
1774 VirtualizingStackPanel::set_are_vertical_snap_points_regular(self, value)
1775 }
1776
1777 fn get_irregular_snap_points(
1778 &self,
1779 orientation: Orientation,
1780 snap_points_alignment: SnapPointsAlignment,
1781 ) -> Vec<f64> {
1782 VirtualizingStackPanel::get_irregular_snap_points(self, orientation, snap_points_alignment)
1783 }
1784
1785 fn get_regular_snap_points(
1786 &self,
1787 orientation: Orientation,
1788 snap_points_alignment: SnapPointsAlignment,
1789 ) -> (f64, f64) {
1790 VirtualizingStackPanel::get_regular_snap_points(self, orientation, snap_points_alignment)
1791 }
1792
1793 fn horizontal_snap_points_changed(&self, handler: SnapPointsChangedHandler) -> Rc<dyn IDisposable> {
1794 self.add_disposable_handler(Self::horizontal_snap_points_changed_event(), move |sender, e| handler(sender, e))
1795 }
1796
1797 fn vertical_snap_points_changed(&self, handler: SnapPointsChangedHandler) -> Rc<dyn IDisposable> {
1798 self.add_disposable_handler(Self::vertical_snap_points_changed_event(), move |sender, e| handler(sender, e))
1799 }
1800}