1use alloc::collections::BTreeMap;
8
9use azul_core::{
10 callbacks::{EdgeType, VirtualViewCallbackReason},
11 dom::{DomId, NodeId},
12 geom::{LogicalPosition, LogicalRect, LogicalSize},
13};
14
15use crate::managers::scroll_state::ScrollManager;
16
17const EDGE_THRESHOLD: f32 = 200.0;
19
20#[derive(Debug, Clone, Default)]
26pub struct VirtualViewManager {
27 states: BTreeMap<(DomId, NodeId), VirtualViewState>,
29 next_dom_id: usize,
31 reason_overrides: Vec<((DomId, NodeId), VirtualViewCallbackReason)>,
37}
38
39#[derive(Debug, Clone)]
44struct VirtualViewState {
45 virtual_view_scroll_size: Option<LogicalSize>,
47 virtual_view_virtual_scroll_size: Option<LogicalSize>,
49 virtual_view_was_invoked: bool,
51 invoked_for_current_expansion: bool,
53 invoked_for_current_edge: bool,
55 last_edge_triggered: EdgeFlags,
57 nested_dom_id: DomId,
59 last_bounds: LogicalRect,
61 initial_scroll_offset: LogicalPosition,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Default)]
73#[allow(clippy::struct_excessive_bools)] struct EdgeFlags {
75 top: bool,
77 bottom: bool,
79 left: bool,
81 right: bool,
83}
84
85impl VirtualViewManager {
86 #[must_use] pub fn new() -> Self {
88 Self {
89 next_dom_id: 1, ..Default::default()
91 }
92 }
93
94 #[must_use] pub fn debug_counts(&self) -> usize {
96 self.states.len()
97 }
98
99 pub fn set_reason_override(
102 &mut self,
103 dom_id: DomId,
104 node_id: NodeId,
105 reason: VirtualViewCallbackReason,
106 ) {
107 self.reason_overrides
108 .retain(|((d, n), _)| !(*d == dom_id && *n == node_id));
109 self.reason_overrides.push(((dom_id, node_id), reason));
110 }
111
112 pub fn get_or_create_nested_dom_id(&mut self, dom_id: DomId, node_id: NodeId) -> DomId {
117 let key = (dom_id, node_id);
118
119 if let Some(state) = self.states.get(&key) {
121 return state.nested_dom_id;
122 }
123
124 let nested_dom_id = DomId {
126 inner: self.next_dom_id,
127 };
128 self.next_dom_id += 1;
129
130 self.states.insert(key, VirtualViewState::new(nested_dom_id));
131 nested_dom_id
132 }
133
134 #[must_use] pub fn get_nested_dom_id(&self, dom_id: DomId, node_id: NodeId) -> Option<DomId> {
136 self.states.get(&(dom_id, node_id)).map(|s| s.nested_dom_id)
137 }
138
139 #[must_use] pub fn was_virtual_view_invoked(&self, dom_id: DomId, node_id: NodeId) -> bool {
141 self.states
142 .get(&(dom_id, node_id))
143 .is_some_and(|s| s.virtual_view_was_invoked)
144 }
145
146 pub fn update_virtual_view_info(
152 &mut self,
153 dom_id: DomId,
154 node_id: NodeId,
155 scroll_size: LogicalSize,
156 virtual_scroll_size: LogicalSize,
157 ) -> Option<()> {
158 let state = self.states.get_mut(&(dom_id, node_id))?;
159
160 if let Some(old_size) = state.virtual_view_scroll_size {
162 if scroll_size.width > old_size.width || scroll_size.height > old_size.height {
163 state.invoked_for_current_expansion = false;
164 }
165 }
166 state.virtual_view_scroll_size = Some(scroll_size);
167 state.virtual_view_virtual_scroll_size = Some(virtual_scroll_size);
168
169 Some(())
170 }
171
172 pub fn mark_invoked(
177 &mut self,
178 dom_id: DomId,
179 node_id: NodeId,
180 reason: VirtualViewCallbackReason,
181 ) -> Option<()> {
182 let state = self.states.get_mut(&(dom_id, node_id))?;
183
184 state.virtual_view_was_invoked = true;
185 match reason {
186 VirtualViewCallbackReason::BoundsExpanded => state.invoked_for_current_expansion = true,
187 VirtualViewCallbackReason::EdgeScrolled(edge) => {
188 state.invoked_for_current_edge = true;
189 state.last_edge_triggered = edge.into();
190 }
191 _ => {}
192 }
193
194 Some(())
195 }
196
197 pub fn reset_all_invocation_flags(&mut self) {
205 for state in self.states.values_mut() {
206 state.virtual_view_was_invoked = false;
207 state.invoked_for_current_expansion = false;
208 state.invoked_for_current_edge = false;
209 state.last_edge_triggered = EdgeFlags::default();
210 }
211 }
212
213 pub fn force_reinvoke(&mut self, dom_id: DomId, node_id: NodeId) -> Option<()> {
218 let state = self.states.get_mut(&(dom_id, node_id))?;
219
220 state.virtual_view_was_invoked = false;
221 state.invoked_for_current_expansion = false;
222 state.invoked_for_current_edge = false;
223
224 Some(())
225 }
226
227 #[must_use] pub fn all_view_keys(&self) -> Vec<(DomId, NodeId)> {
232 self.states.keys().copied().collect()
233 }
234
235 pub fn check_reinvoke(
244 &mut self,
245 dom_id: DomId,
246 node_id: NodeId,
247 scroll_manager: &ScrollManager,
248 layout_bounds: LogicalRect,
249 ) -> Option<VirtualViewCallbackReason> {
250 if let Some(pos) = self
257 .reason_overrides
258 .iter()
259 .position(|((d, n), _)| *d == dom_id && *n == node_id)
260 {
261 let (_, reason) = self.reason_overrides.remove(pos);
262 return Some(reason);
263 }
264
265 let state = self.states.entry((dom_id, node_id)).or_insert_with(|| {
266 let nested_dom_id = DomId {
267 inner: self.next_dom_id,
268 };
269 self.next_dom_id += 1;
270 VirtualViewState::new(nested_dom_id)
271 });
272
273 if !state.virtual_view_was_invoked {
274 state.initial_scroll_offset = scroll_manager
277 .get_current_offset(dom_id, node_id)
278 .unwrap_or_default();
279 return Some(VirtualViewCallbackReason::InitialRender);
280 }
281
282 if layout_bounds.size.width > state.last_bounds.size.width
284 || layout_bounds.size.height > state.last_bounds.size.height
285 {
286 state.invoked_for_current_expansion = false;
287 }
288 state.last_bounds = layout_bounds;
289
290 let scroll_offset = scroll_manager
291 .get_current_offset(dom_id, node_id)
292 .unwrap_or_default();
293
294 state.check_reinvoke_condition(scroll_offset, layout_bounds.size)
295 }
296
297 #[must_use] pub fn get_all_virtual_view_infos(&self) -> Vec<VirtualViewDebugInfo> {
302 self.states
303 .iter()
304 .map(|((dom_id, node_id), state)| VirtualViewDebugInfo {
305 parent_dom_id: dom_id.inner,
306 parent_node_id: node_id.index(),
307 nested_dom_id: state.nested_dom_id.inner,
308 scroll_size_width: state.virtual_view_scroll_size.map(|s| s.width),
309 scroll_size_height: state.virtual_view_scroll_size.map(|s| s.height),
310 virtual_scroll_size_width: state.virtual_view_virtual_scroll_size.map(|s| s.width),
311 virtual_scroll_size_height: state.virtual_view_virtual_scroll_size.map(|s| s.height),
312 was_invoked: state.virtual_view_was_invoked,
313 last_bounds_x: state.last_bounds.origin.x,
314 last_bounds_y: state.last_bounds.origin.y,
315 last_bounds_width: state.last_bounds.size.width,
316 last_bounds_height: state.last_bounds.size.height,
317 })
318 .collect()
319 }
320}
321
322#[derive(Copy, Debug, Clone)]
324pub struct VirtualViewDebugInfo {
325 pub parent_dom_id: usize,
326 pub parent_node_id: usize,
327 pub nested_dom_id: usize,
328 pub scroll_size_width: Option<f32>,
329 pub scroll_size_height: Option<f32>,
330 pub virtual_scroll_size_width: Option<f32>,
331 pub virtual_scroll_size_height: Option<f32>,
332 pub was_invoked: bool,
333 pub last_bounds_x: f32,
334 pub last_bounds_y: f32,
335 pub last_bounds_width: f32,
336 pub last_bounds_height: f32,
337}
338
339impl VirtualViewState {
340 fn new(nested_dom_id: DomId) -> Self {
342 Self {
343 virtual_view_scroll_size: None,
344 virtual_view_virtual_scroll_size: None,
345 virtual_view_was_invoked: false,
346 invoked_for_current_expansion: false,
347 invoked_for_current_edge: false,
348 last_edge_triggered: EdgeFlags::default(),
349 nested_dom_id,
350 last_bounds: LogicalRect::zero(),
351 initial_scroll_offset: LogicalPosition::zero(),
352 }
353 }
354
355 fn check_reinvoke_condition(
362 &self,
363 current_offset: LogicalPosition,
364 container_size: LogicalSize,
365 ) -> Option<VirtualViewCallbackReason> {
366 let scroll_size = self.virtual_view_scroll_size?;
368
369 if !self.invoked_for_current_expansion
371 && (container_size.width > scroll_size.width
372 || container_size.height > scroll_size.height)
373 {
374 return Some(VirtualViewCallbackReason::BoundsExpanded);
375 }
376
377 let scrollable_width = scroll_size.width > container_size.width;
380 let scrollable_height = scroll_size.height > container_size.height;
381
382 let current_edges = EdgeFlags {
384 top: scrollable_height && current_offset.y <= EDGE_THRESHOLD,
385 bottom: scrollable_height
386 && (scroll_size.height - container_size.height - current_offset.y)
387 <= EDGE_THRESHOLD,
388 left: scrollable_width && current_offset.x <= EDGE_THRESHOLD,
389 right: scrollable_width
390 && (scroll_size.width - container_size.width - current_offset.x) <= EDGE_THRESHOLD,
391 };
392
393 let has_scrolled = current_offset != self.initial_scroll_offset;
397
398 if has_scrolled && !self.invoked_for_current_edge && current_edges.any() {
401 if current_edges.bottom && !self.last_edge_triggered.bottom {
402 return Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom));
403 }
404 if current_edges.right && !self.last_edge_triggered.right {
405 return Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Right));
406 }
407 if current_edges.top && !self.last_edge_triggered.top {
408 return Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Top));
409 }
410 if current_edges.left && !self.last_edge_triggered.left {
411 return Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Left));
412 }
413 }
414
415 None
416 }
417}
418
419impl EdgeFlags {
420 #[allow(clippy::trivially_copy_pass_by_ref)] const fn any(&self) -> bool {
423 self.top || self.bottom || self.left || self.right
424 }
425}
426
427impl From<EdgeType> for EdgeFlags {
428 fn from(edge: EdgeType) -> Self {
429 match edge {
430 EdgeType::Top => Self {
431 top: true,
432 ..Default::default()
433 },
434 EdgeType::Bottom => Self {
435 bottom: true,
436 ..Default::default()
437 },
438 EdgeType::Left => Self {
439 left: true,
440 ..Default::default()
441 },
442 EdgeType::Right => Self {
443 right: true,
444 ..Default::default()
445 },
446 }
447 }
448}
449
450impl crate::managers::NodeIdRemap for VirtualViewManager {
451 fn remap_node_ids(&mut self, dom: DomId, map: &crate::managers::NodeIdMap) {
458 crate::managers::remap_dom_keys(&mut self.states, dom, map);
459
460 self.reason_overrides.retain_mut(|((d, node_id), _)| {
461 if *d != dom {
462 return true;
463 }
464 map.resolve(*node_id).is_some_and(|new_id| {
465 *node_id = new_id;
466 true
467 })
468 });
469 }
470}
471
472#[cfg(all(test, feature = "std"))]
493mod autotest_generated {
494 #![allow(clippy::float_cmp)] use std::collections::BTreeSet;
497
498 use azul_core::task::{Instant, SystemTick};
499
500 use super::*;
501
502 const DOM: DomId = DomId::ROOT_ID;
505 const DOM1: DomId = DomId { inner: 1 };
506 const DOM_MAX: DomId = DomId {
507 inner: usize::MAX,
508 };
509
510 fn n(i: usize) -> NodeId {
511 NodeId::new(i)
512 }
513
514 fn sz(width: f32, height: f32) -> LogicalSize {
515 LogicalSize::new(width, height)
516 }
517
518 fn pos(x: f32, y: f32) -> LogicalPosition {
519 LogicalPosition::new(x, y)
520 }
521
522 fn rect(width: f32, height: f32) -> LogicalRect {
523 LogicalRect::new(LogicalPosition::zero(), sz(width, height))
524 }
525
526 fn at(t: u64) -> Instant {
528 Instant::Tick(SystemTick::new(t))
529 }
530
531 fn scrolled(dom: DomId, node: NodeId, x: f32, y: f32) -> ScrollManager {
534 let mut sm = ScrollManager::new();
535 sm.set_scroll_position_unclamped(dom, node, pos(x, y), at(0));
536 sm
537 }
538
539 fn st(m: &VirtualViewManager, dom: DomId, node: NodeId) -> &VirtualViewState {
540 m.states.get(&(dom, node)).expect("state must exist")
541 }
542
543 fn ready_view(scroll: LogicalSize) -> VirtualViewManager {
547 let mut m = VirtualViewManager::new();
548 m.get_or_create_nested_dom_id(DOM, n(1));
549 m.mark_invoked(DOM, n(1), VirtualViewCallbackReason::InitialRender)
550 .expect("view exists");
551 m.update_virtual_view_info(DOM, n(1), scroll, scroll)
552 .expect("view exists");
553 m
554 }
555
556 fn invoked_state(scroll: LogicalSize) -> VirtualViewState {
559 let mut s = VirtualViewState::new(DomId { inner: 7 });
560 s.virtual_view_was_invoked = true;
561 s.virtual_view_scroll_size = Some(scroll);
562 s
563 }
564
565 fn mark(m: &mut VirtualViewManager, dom: DomId, node: NodeId, r: VirtualViewCallbackReason) {
568 m.mark_invoked(dom, node, r).expect("view exists");
569 }
570
571 fn set_sizes(
572 m: &mut VirtualViewManager,
573 dom: DomId,
574 node: NodeId,
575 scroll: LogicalSize,
576 virt: LogicalSize,
577 ) {
578 m.update_virtual_view_info(dom, node, scroll, virt)
579 .expect("view exists");
580 }
581
582 #[test]
585 fn new_is_empty_and_reserves_dom_id_zero_for_root() {
586 let m = VirtualViewManager::new();
587
588 assert_eq!(m.debug_counts(), 0);
589 assert!(m.all_view_keys().is_empty());
590 assert!(m.get_all_virtual_view_infos().is_empty());
591 assert!(m.reason_overrides.is_empty());
592 assert_eq!(m.next_dom_id, 1);
594
595 assert_eq!(m.get_nested_dom_id(DOM, n(0)), None);
597 assert_eq!(m.get_nested_dom_id(DOM_MAX, n(usize::MAX)), None);
598 assert!(!m.was_virtual_view_invoked(DOM, n(0)));
599 assert!(!m.was_virtual_view_invoked(DOM_MAX, n(usize::MAX)));
600 }
601
602 #[test]
603 fn derived_default_hands_out_root_dom_id_unlike_new() {
604 assert_eq!(VirtualViewManager::default().next_dom_id, 0);
610 assert_eq!(VirtualViewManager::new().next_dom_id, 1);
611
612 let mut d = VirtualViewManager::default();
613 assert_eq!(d.get_or_create_nested_dom_id(DOM, n(0)), DomId::ROOT_ID);
614
615 let mut fresh = VirtualViewManager::new();
616 assert_ne!(fresh.get_or_create_nested_dom_id(DOM, n(0)), DomId::ROOT_ID);
617 }
618
619 #[test]
620 fn virtual_view_state_new_invariants_at_extreme_dom_id() {
621 let s = VirtualViewState::new(DomId { inner: usize::MAX });
622
623 assert_eq!(s.nested_dom_id.inner, usize::MAX);
624 assert!(s.virtual_view_scroll_size.is_none());
625 assert!(s.virtual_view_virtual_scroll_size.is_none());
626 assert!(!s.virtual_view_was_invoked);
627 assert!(!s.invoked_for_current_expansion);
628 assert!(!s.invoked_for_current_edge);
629 assert_eq!(s.last_edge_triggered, EdgeFlags::default());
630 assert!(!s.last_edge_triggered.any());
631 assert_eq!(s.last_bounds, LogicalRect::zero());
632 assert_eq!(s.initial_scroll_offset, LogicalPosition::zero());
633
634 assert_eq!(
637 s.check_reinvoke_condition(pos(0.0, 0.0), sz(f32::INFINITY, f32::INFINITY)),
638 None
639 );
640 }
641
642 #[test]
645 fn get_or_create_is_idempotent_and_unique_per_key() {
646 let mut m = VirtualViewManager::new();
647
648 let a = m.get_or_create_nested_dom_id(DOM, n(0));
649 let a_again = m.get_or_create_nested_dom_id(DOM, n(0));
650 assert_eq!(a, a_again, "re-registering a view must not re-allocate");
651 assert_eq!(a, DomId { inner: 1 });
652 assert_eq!(m.debug_counts(), 1);
653
654 let b = m.get_or_create_nested_dom_id(DOM_MAX, n(usize::MAX));
656 assert_eq!(b, DomId { inner: 2 });
657 assert_ne!(a, b);
658 assert_eq!(m.debug_counts(), 2);
659
660 assert_eq!(m.get_nested_dom_id(DOM, n(0)), Some(a));
661 assert_eq!(m.get_nested_dom_id(DOM_MAX, n(usize::MAX)), Some(b));
662 assert_eq!(m.get_nested_dom_id(DOM, n(1)), None);
663 assert_eq!(m.get_nested_dom_id(DOM1, n(0)), None);
664 }
665
666 #[test]
667 fn nested_dom_ids_are_unique_across_many_views() {
668 let mut m = VirtualViewManager::new();
669 let mut seen = BTreeSet::new();
670
671 for dom in 0..8_usize {
672 for node in 0..32_usize {
673 let id = m.get_or_create_nested_dom_id(DomId { inner: dom }, n(node));
674 assert!(id.inner >= 1, "nested id must never collide with the root");
675 assert!(seen.insert(id.inner), "nested DOM id {id:?} handed out twice");
676 }
677 }
678
679 assert_eq!(seen.len(), 8 * 32);
680 assert_eq!(m.debug_counts(), 8 * 32);
681 assert_eq!(m.next_dom_id, 8 * 32 + 1);
682 }
683
684 #[test]
685 fn all_view_keys_is_sorted_and_matches_the_tracked_states() {
686 let mut m = VirtualViewManager::new();
687 assert!(m.all_view_keys().is_empty());
688
689 m.get_or_create_nested_dom_id(DOM1, n(9));
692 m.get_or_create_nested_dom_id(DOM1, n(2));
693 m.get_or_create_nested_dom_id(DOM, n(7));
694
695 let keys = m.all_view_keys();
696 assert_eq!(keys, vec![(DOM, n(7)), (DOM1, n(2)), (DOM1, n(9))]);
697 assert_eq!(keys.len(), m.debug_counts());
698
699 let mut sorted = keys.clone();
700 sorted.sort_unstable();
701 assert_eq!(keys, sorted);
702 }
703
704 #[test]
707 fn mutators_return_none_for_unknown_view_and_never_insert() {
708 let mut m = VirtualViewManager::new();
709
710 assert_eq!(
711 m.update_virtual_view_info(DOM, n(3), sz(1.0, 1.0), sz(1.0, 1.0)),
712 None
713 );
714 assert_eq!(
715 m.mark_invoked(DOM, n(3), VirtualViewCallbackReason::InitialRender),
716 None
717 );
718 assert_eq!(m.force_reinvoke(DOM, n(3)), None);
719 assert_eq!(
720 m.update_virtual_view_info(DOM_MAX, n(usize::MAX), sz(0.0, 0.0), sz(0.0, 0.0)),
721 None
722 );
723
724 assert_eq!(m.debug_counts(), 0);
726 assert_eq!(m.next_dom_id, 1);
727 }
728
729 #[test]
732 fn set_reason_override_keeps_only_the_latest_per_key() {
733 let mut m = VirtualViewManager::new();
734
735 for _ in 0..1_000 {
736 m.set_reason_override(DOM, n(2), VirtualViewCallbackReason::DomRecreated);
737 }
738 m.set_reason_override(DOM, n(2), VirtualViewCallbackReason::BoundsExpanded);
739
740 assert_eq!(m.reason_overrides.len(), 1);
742
743 let sm = ScrollManager::new();
744 assert_eq!(
745 m.check_reinvoke(DOM, n(2), &sm, rect(10.0, 10.0)),
746 Some(VirtualViewCallbackReason::BoundsExpanded)
747 );
748 }
749
750 #[test]
751 fn reason_override_is_consumed_exactly_once_and_does_not_create_state() {
752 let mut m = VirtualViewManager::new();
753 let sm = ScrollManager::new();
754
755 m.set_reason_override(DOM, n(2), VirtualViewCallbackReason::ScrollBeyondContent);
756 assert_eq!(
757 m.check_reinvoke(DOM, n(2), &sm, rect(10.0, 10.0)),
758 Some(VirtualViewCallbackReason::ScrollBeyondContent)
759 );
760
761 assert!(m.reason_overrides.is_empty());
763 assert_eq!(m.debug_counts(), 0);
764
765 assert_eq!(
767 m.check_reinvoke(DOM, n(2), &sm, rect(10.0, 10.0)),
768 Some(VirtualViewCallbackReason::InitialRender)
769 );
770 assert_eq!(m.debug_counts(), 1);
771 assert_eq!(m.get_nested_dom_id(DOM, n(2)), Some(DomId { inner: 1 }));
772 }
773
774 #[test]
775 fn reason_overrides_do_not_leak_across_keys() {
776 let mut m = VirtualViewManager::new();
777 let sm = ScrollManager::new();
778
779 m.set_reason_override(
780 DOM,
781 n(1),
782 VirtualViewCallbackReason::EdgeScrolled(EdgeType::Left),
783 );
784 m.set_reason_override(DOM1, n(1), VirtualViewCallbackReason::DomRecreated);
785 m.set_reason_override(DOM, n(2), VirtualViewCallbackReason::BoundsExpanded);
786 assert_eq!(m.reason_overrides.len(), 3);
787
788 assert_eq!(
790 m.check_reinvoke(DOM, n(2), &sm, rect(1.0, 1.0)),
791 Some(VirtualViewCallbackReason::BoundsExpanded)
792 );
793 assert_eq!(
794 m.check_reinvoke(DOM1, n(1), &sm, rect(1.0, 1.0)),
795 Some(VirtualViewCallbackReason::DomRecreated)
796 );
797 assert_eq!(
798 m.check_reinvoke(DOM, n(1), &sm, rect(1.0, 1.0)),
799 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Left))
800 );
801 assert!(m.reason_overrides.is_empty());
802 }
803
804 #[test]
807 fn update_virtual_view_info_zero_and_extreme_sizes_do_not_panic() {
808 let mut m = VirtualViewManager::new();
809 m.get_or_create_nested_dom_id(DOM, n(1));
810
811 for size in [
812 sz(0.0, 0.0),
813 sz(-0.0, -0.0),
814 sz(f32::MAX, f32::MAX),
815 sz(f32::MIN, f32::MIN),
816 sz(f32::INFINITY, f32::NEG_INFINITY),
817 sz(-1.0e30, 1.0e30),
818 sz(f32::MIN_POSITIVE, f32::EPSILON),
819 ] {
820 assert_eq!(
821 m.update_virtual_view_info(DOM, n(1), size, size),
822 Some(()),
823 "size {size:?} must be recorded without panicking"
824 );
825 assert_eq!(st(&m, DOM, n(1)).virtual_view_scroll_size, Some(size));
826 assert_eq!(
827 st(&m, DOM, n(1)).virtual_view_virtual_scroll_size,
828 Some(size)
829 );
830 }
831
832 assert_eq!(
834 m.update_virtual_view_info(DOM, n(1), sz(f32::NAN, f32::NAN), sz(f32::NAN, 1.0)),
835 Some(())
836 );
837 let stored = st(&m, DOM, n(1)).virtual_view_scroll_size.unwrap();
838 assert!(stored.width.is_nan() && stored.height.is_nan());
839 }
840
841 #[test]
842 fn update_virtual_view_info_clears_expansion_flag_only_when_content_grows() {
843 let mut m = ready_view(sz(100.0, 100.0));
844 mark(&mut m, DOM, n(1), VirtualViewCallbackReason::BoundsExpanded);
845 assert!(st(&m, DOM, n(1)).invoked_for_current_expansion);
846
847 set_sizes(&mut m, DOM, n(1), sz(50.0, 50.0), sz(50.0, 50.0));
849 assert!(st(&m, DOM, n(1)).invoked_for_current_expansion);
850
851 set_sizes(&mut m, DOM, n(1), sz(50.0, 50.0), sz(50.0, 50.0));
853 assert!(st(&m, DOM, n(1)).invoked_for_current_expansion);
854
855 set_sizes(&mut m, DOM, n(1), sz(50.000_01, 50.0), sz(50.0, 50.0));
857 assert!(!st(&m, DOM, n(1)).invoked_for_current_expansion);
858 }
859
860 #[test]
861 fn update_virtual_view_info_infinite_growth_clears_the_flag() {
862 let mut m = ready_view(sz(100.0, 100.0));
863 mark(&mut m, DOM, n(1), VirtualViewCallbackReason::BoundsExpanded);
864
865 set_sizes(
866 &mut m,
867 DOM,
868 n(1),
869 sz(f32::INFINITY, 100.0),
870 sz(f32::INFINITY, 100.0),
871 );
872 assert!(!st(&m, DOM, n(1)).invoked_for_current_expansion);
873 }
874
875 #[test]
876 fn update_virtual_view_info_nan_size_poisons_the_growth_check() {
877 let mut m = ready_view(sz(100.0, 100.0));
882 mark(&mut m, DOM, n(1), VirtualViewCallbackReason::BoundsExpanded);
883 assert!(st(&m, DOM, n(1)).invoked_for_current_expansion);
884
885 set_sizes(
887 &mut m,
888 DOM,
889 n(1),
890 sz(f32::NAN, f32::NAN),
891 sz(f32::NAN, f32::NAN),
892 );
893 assert!(st(&m, DOM, n(1)).invoked_for_current_expansion);
894
895 set_sizes(&mut m, DOM, n(1), sz(1.0e9, 1.0e9), sz(1.0e9, 1.0e9));
897 assert!(st(&m, DOM, n(1)).invoked_for_current_expansion);
898
899 m.force_reinvoke(DOM, n(1)).expect("view exists");
901 assert!(!st(&m, DOM, n(1)).invoked_for_current_expansion);
902 }
903
904 #[test]
907 fn mark_invoked_sets_only_the_flags_the_reason_owns() {
908 for reason in [
909 VirtualViewCallbackReason::InitialRender,
910 VirtualViewCallbackReason::DomRecreated,
911 VirtualViewCallbackReason::ScrollBeyondContent,
912 ] {
913 let mut m = VirtualViewManager::new();
914 m.get_or_create_nested_dom_id(DOM, n(1));
915 assert_eq!(m.mark_invoked(DOM, n(1), reason), Some(()));
916
917 let s = st(&m, DOM, n(1));
918 assert!(s.virtual_view_was_invoked, "{reason:?} must mark invoked");
919 assert!(!s.invoked_for_current_expansion, "{reason:?}");
920 assert!(!s.invoked_for_current_edge, "{reason:?}");
921 assert_eq!(s.last_edge_triggered, EdgeFlags::default(), "{reason:?}");
922 assert!(m.was_virtual_view_invoked(DOM, n(1)));
923 }
924
925 let mut m = VirtualViewManager::new();
926 m.get_or_create_nested_dom_id(DOM, n(1));
927 mark(&mut m, DOM, n(1), VirtualViewCallbackReason::BoundsExpanded);
928 let s = st(&m, DOM, n(1));
929 assert!(s.virtual_view_was_invoked);
930 assert!(s.invoked_for_current_expansion);
931 assert!(!s.invoked_for_current_edge);
932 assert_eq!(s.last_edge_triggered, EdgeFlags::default());
933 }
934
935 #[test]
936 fn edge_type_round_trips_through_mark_invoked_into_edge_flags() {
937 for edge in [
938 EdgeType::Top,
939 EdgeType::Bottom,
940 EdgeType::Left,
941 EdgeType::Right,
942 ] {
943 let mut m = VirtualViewManager::new();
944 m.get_or_create_nested_dom_id(DOM, n(1));
945 mark(
946 &mut m,
947 DOM,
948 n(1),
949 VirtualViewCallbackReason::EdgeScrolled(edge),
950 );
951
952 let s = st(&m, DOM, n(1));
953 assert!(s.virtual_view_was_invoked);
954 assert!(s.invoked_for_current_edge);
955 assert_eq!(s.last_edge_triggered, EdgeFlags::from(edge), "{edge:?}");
957 assert!(s.last_edge_triggered.any(), "{edge:?}");
958
959 let f = s.last_edge_triggered;
960 let set = usize::from(f.top)
961 + usize::from(f.bottom)
962 + usize::from(f.left)
963 + usize::from(f.right);
964 assert_eq!(set, 1, "{edge:?} must set exactly one flag");
965 assert!(!s.invoked_for_current_expansion, "{edge:?}");
967 }
968 }
969
970 #[test]
973 fn reset_all_invocation_flags_on_empty_manager_is_a_noop() {
974 let mut m = VirtualViewManager::new();
975 m.reset_all_invocation_flags();
976 assert_eq!(m.debug_counts(), 0);
977 assert_eq!(m.next_dom_id, 1);
978 assert!(m.all_view_keys().is_empty());
979 }
980
981 #[test]
982 fn reset_all_clears_every_flag_but_preserves_identity_sizes_and_bounds() {
983 let mut m = ready_view(sz(100.0, 1000.0));
984 let nested = m.get_nested_dom_id(DOM, n(1)).expect("view exists");
985 m.get_or_create_nested_dom_id(DOM1, n(4));
986 mark(
987 &mut m,
988 DOM,
989 n(1),
990 VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom),
991 );
992 mark(&mut m, DOM1, n(4), VirtualViewCallbackReason::BoundsExpanded);
993
994 let sm = scrolled(DOM, n(1), 0.0, 900.0);
996 let _ = m.check_reinvoke(DOM, n(1), &sm, rect(100.0, 100.0));
997 m.set_reason_override(DOM, n(1), VirtualViewCallbackReason::DomRecreated);
999
1000 m.reset_all_invocation_flags();
1001
1002 for (dom, node) in [(DOM, n(1)), (DOM1, n(4))] {
1003 let s = st(&m, dom, node);
1004 assert!(!s.virtual_view_was_invoked);
1005 assert!(!s.invoked_for_current_expansion);
1006 assert!(!s.invoked_for_current_edge);
1007 assert_eq!(s.last_edge_triggered, EdgeFlags::default());
1008 assert!(!m.was_virtual_view_invoked(dom, node));
1009 }
1010
1011 let s = st(&m, DOM, n(1));
1013 assert_eq!(s.nested_dom_id, nested);
1014 assert_eq!(s.virtual_view_scroll_size, Some(sz(100.0, 1000.0)));
1015 assert_eq!(s.last_bounds, rect(100.0, 100.0));
1016 assert_eq!(m.debug_counts(), 2);
1017 assert_eq!(m.reason_overrides.len(), 1);
1018 }
1019
1020 #[test]
1021 fn force_reinvoke_yields_initial_render_but_leaves_last_edge_triggered_set() {
1022 let mut m = ready_view(sz(100.0, 1000.0));
1023 mark(
1024 &mut m,
1025 DOM,
1026 n(1),
1027 VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom),
1028 );
1029
1030 assert_eq!(m.force_reinvoke(DOM, n(1)), Some(()));
1031 let s = st(&m, DOM, n(1));
1032 assert!(!s.virtual_view_was_invoked);
1033 assert!(!s.invoked_for_current_expansion);
1034 assert!(!s.invoked_for_current_edge);
1035 assert_eq!(s.last_edge_triggered, EdgeFlags::from(EdgeType::Bottom));
1039
1040 let sm = scrolled(DOM, n(1), 0.0, 900.0);
1042 assert_eq!(
1043 m.check_reinvoke(DOM, n(1), &sm, rect(100.0, 100.0)),
1044 Some(VirtualViewCallbackReason::InitialRender)
1045 );
1046 }
1047
1048 #[test]
1051 fn check_reinvoke_creates_the_state_for_an_unknown_view() {
1052 let mut m = VirtualViewManager::new();
1053 let sm = ScrollManager::new();
1054
1055 assert_eq!(
1056 m.check_reinvoke(DOM, n(3), &sm, rect(100.0, 100.0)),
1057 Some(VirtualViewCallbackReason::InitialRender)
1058 );
1059 assert_eq!(m.debug_counts(), 1);
1060 assert_eq!(m.get_nested_dom_id(DOM, n(3)), Some(DomId { inner: 1 }));
1061 assert!(!m.was_virtual_view_invoked(DOM, n(3)));
1062
1063 for _ in 0..16 {
1066 assert_eq!(
1067 m.check_reinvoke(DOM, n(3), &sm, rect(100.0, 100.0)),
1068 Some(VirtualViewCallbackReason::InitialRender)
1069 );
1070 }
1071 assert_eq!(m.debug_counts(), 1);
1072 assert_eq!(m.next_dom_id, 2);
1073
1074 assert_eq!(
1076 m.check_reinvoke(DOM_MAX, n(usize::MAX), &sm, rect(0.0, 0.0)),
1077 Some(VirtualViewCallbackReason::InitialRender)
1078 );
1079 assert_eq!(
1080 m.get_nested_dom_id(DOM_MAX, n(usize::MAX)),
1081 Some(DomId { inner: 2 })
1082 );
1083 }
1084
1085 #[test]
1086 fn check_reinvoke_is_none_while_no_content_size_is_known() {
1087 let mut m = VirtualViewManager::new();
1088 m.get_or_create_nested_dom_id(DOM, n(1));
1089 mark(&mut m, DOM, n(1), VirtualViewCallbackReason::InitialRender);
1090
1091 let sm = scrolled(DOM, n(1), 0.0, 5_000.0);
1092 assert_eq!(
1094 m.check_reinvoke(DOM, n(1), &sm, rect(f32::MAX, f32::MAX)),
1095 None
1096 );
1097 assert_eq!(m.check_reinvoke(DOM, n(1), &sm, rect(0.0, 0.0)), None);
1098 }
1099
1100 #[test]
1101 fn check_reinvoke_bounds_expanded_fires_once_per_growth() {
1102 let mut m = ready_view(sz(100.0, 100.0));
1103 let sm = ScrollManager::new();
1104
1105 assert_eq!(
1106 m.check_reinvoke(DOM, n(1), &sm, rect(200.0, 200.0)),
1107 Some(VirtualViewCallbackReason::BoundsExpanded)
1108 );
1109 mark(&mut m, DOM, n(1), VirtualViewCallbackReason::BoundsExpanded);
1110
1111 assert_eq!(m.check_reinvoke(DOM, n(1), &sm, rect(200.0, 200.0)), None);
1113 assert_eq!(m.check_reinvoke(DOM, n(1), &sm, rect(150.0, 150.0)), None);
1115 assert_eq!(
1117 m.check_reinvoke(DOM, n(1), &sm, rect(300.0, 300.0)),
1118 Some(VirtualViewCallbackReason::BoundsExpanded)
1119 );
1120 assert_eq!(st(&m, DOM, n(1)).last_bounds, rect(300.0, 300.0));
1121 }
1122
1123 #[test]
1124 fn check_reinvoke_does_not_fire_an_edge_for_the_resting_start_position() {
1125 let mut m = VirtualViewManager::new();
1129 m.get_or_create_nested_dom_id(DOM, n(1));
1130 let sm = scrolled(DOM, n(1), 0.0, 0.0);
1131
1132 assert_eq!(
1133 m.check_reinvoke(DOM, n(1), &sm, rect(100.0, 100.0)),
1134 Some(VirtualViewCallbackReason::InitialRender)
1135 );
1136 assert_eq!(st(&m, DOM, n(1)).initial_scroll_offset, pos(0.0, 0.0));
1137 mark(&mut m, DOM, n(1), VirtualViewCallbackReason::InitialRender);
1138 set_sizes(&mut m, DOM, n(1), sz(100.0, 1000.0), sz(100.0, 1000.0));
1139
1140 assert_eq!(m.check_reinvoke(DOM, n(1), &sm, rect(100.0, 100.0)), None);
1142 }
1143
1144 #[test]
1145 fn check_reinvoke_edge_scrolled_bottom_then_stays_quiet() {
1146 let mut m = ready_view(sz(100.0, 1000.0));
1147 let sm = scrolled(DOM, n(1), 0.0, 900.0);
1148
1149 assert_eq!(
1150 m.check_reinvoke(DOM, n(1), &sm, rect(100.0, 100.0)),
1151 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom))
1152 );
1153 mark(
1154 &mut m,
1155 DOM,
1156 n(1),
1157 VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom),
1158 );
1159
1160 assert_eq!(m.check_reinvoke(DOM, n(1), &sm, rect(100.0, 100.0)), None);
1162 }
1163
1164 #[test]
1165 fn bottom_edge_is_suppressed_after_a_force_reinvoke_but_not_after_a_reset() {
1166 let bottom = scrolled(DOM, n(1), 0.0, 900.0);
1173 let middle = scrolled(DOM, n(1), 0.0, 400.0);
1174 let bounds = rect(100.0, 100.0);
1175
1176 let mut m = ready_view(sz(100.0, 1000.0));
1177 assert_eq!(
1178 m.check_reinvoke(DOM, n(1), &bottom, bounds),
1179 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom))
1180 );
1181 mark(
1182 &mut m,
1183 DOM,
1184 n(1),
1185 VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom),
1186 );
1187
1188 m.force_reinvoke(DOM, n(1)).expect("view exists");
1190 assert_eq!(
1192 m.check_reinvoke(DOM, n(1), &middle, bounds),
1193 Some(VirtualViewCallbackReason::InitialRender)
1194 );
1195 mark(&mut m, DOM, n(1), VirtualViewCallbackReason::InitialRender);
1196 assert_eq!(st(&m, DOM, n(1)).initial_scroll_offset, pos(0.0, 400.0));
1197
1198 assert_eq!(
1201 m.check_reinvoke(DOM, n(1), &bottom, bounds),
1202 None,
1203 "stale last_edge_triggered.bottom suppresses the second bottom-edge load"
1204 );
1205 assert!(st(&m, DOM, n(1)).last_edge_triggered.bottom);
1206
1207 m.reset_all_invocation_flags();
1209 assert_eq!(
1210 m.check_reinvoke(DOM, n(1), &middle, bounds),
1211 Some(VirtualViewCallbackReason::InitialRender)
1212 );
1213 mark(&mut m, DOM, n(1), VirtualViewCallbackReason::InitialRender);
1214 assert_eq!(
1215 m.check_reinvoke(DOM, n(1), &bottom, bounds),
1216 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom)),
1217 "reset_all clears the edge memory, so the identical scroll does fire"
1218 );
1219 }
1220
1221 #[test]
1222 fn check_reinvoke_with_nan_bounds_is_quiet_and_stores_nan() {
1223 let mut m = ready_view(sz(100.0, 100.0));
1224 let sm = ScrollManager::new();
1225 let nan_rect = LogicalRect::new(pos(f32::NAN, f32::NAN), sz(f32::NAN, f32::NAN));
1226
1227 assert_eq!(m.check_reinvoke(DOM, n(1), &sm, nan_rect), None);
1229
1230 let info = m.get_all_virtual_view_infos();
1231 assert_eq!(info.len(), 1);
1232 assert!(info[0].last_bounds_x.is_nan());
1233 assert!(info[0].last_bounds_width.is_nan());
1234 assert!(info[0].last_bounds_height.is_nan());
1235 }
1236
1237 #[test]
1238 fn check_reinvoke_with_infinite_bounds_reports_bounds_expanded() {
1239 let mut m = ready_view(sz(100.0, 100.0));
1240 let sm = ScrollManager::new();
1241
1242 assert_eq!(
1243 m.check_reinvoke(
1244 DOM,
1245 n(1),
1246 &sm,
1247 rect(f32::INFINITY, f32::INFINITY)
1248 ),
1249 Some(VirtualViewCallbackReason::BoundsExpanded)
1250 );
1251 }
1252
1253 #[test]
1256 fn edge_threshold_is_exactly_200_px_and_inclusive() {
1257 assert_eq!(EDGE_THRESHOLD, 200.0);
1258
1259 let s = invoked_state(sz(100.0, 1000.0));
1260 let container = sz(100.0, 100.0); assert_eq!(
1264 s.check_reinvoke_condition(pos(0.0, 700.0), container),
1265 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom))
1266 );
1267 assert_eq!(s.check_reinvoke_condition(pos(0.0, 699.0), container), None);
1269
1270 assert_eq!(
1272 s.check_reinvoke_condition(pos(0.0, 200.0), container),
1273 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Top))
1274 );
1275 assert_eq!(s.check_reinvoke_condition(pos(0.0, 201.0), container), None);
1277 }
1278
1279 #[test]
1280 fn edge_priority_is_bottom_right_top_left_and_drains() {
1281 let mut s = invoked_state(sz(1000.0, 1000.0));
1284 let container = sz(900.0, 900.0);
1285 let offset = pos(100.0, 100.0);
1286
1287 assert_eq!(
1288 s.check_reinvoke_condition(offset, container),
1289 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom))
1290 );
1291
1292 s.last_edge_triggered.bottom = true;
1293 assert_eq!(
1294 s.check_reinvoke_condition(offset, container),
1295 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Right))
1296 );
1297
1298 s.last_edge_triggered.right = true;
1299 assert_eq!(
1300 s.check_reinvoke_condition(offset, container),
1301 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Top))
1302 );
1303
1304 s.last_edge_triggered.top = true;
1305 assert_eq!(
1306 s.check_reinvoke_condition(offset, container),
1307 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Left))
1308 );
1309
1310 s.last_edge_triggered.left = true;
1312 assert_eq!(s.check_reinvoke_condition(offset, container), None);
1313 }
1314
1315 #[test]
1316 fn check_reinvoke_condition_at_zero_is_quiet() {
1317 let s = invoked_state(sz(0.0, 0.0));
1320 assert_eq!(s.check_reinvoke_condition(pos(0.0, 0.0), sz(0.0, 0.0)), None);
1321
1322 assert_eq!(
1324 s.check_reinvoke_condition(pos(0.0, 0.0), sz(1.0, 1.0)),
1325 Some(VirtualViewCallbackReason::BoundsExpanded)
1326 );
1327 }
1328
1329 #[test]
1330 fn check_reinvoke_condition_handles_nan_offset_and_nan_sizes() {
1331 let nan = f32::NAN;
1332
1333 let s = invoked_state(sz(nan, nan));
1336 assert_eq!(s.check_reinvoke_condition(pos(0.0, 0.0), sz(100.0, 100.0)), None);
1337 assert_eq!(s.check_reinvoke_condition(pos(nan, nan), sz(nan, nan)), None);
1338
1339 let s = invoked_state(sz(100.0, 1000.0));
1341 assert_eq!(s.check_reinvoke_condition(pos(0.0, 0.0), sz(nan, nan)), None);
1342
1343 assert_eq!(
1346 s.check_reinvoke_condition(pos(nan, nan), sz(100.0, 100.0)),
1347 None
1348 );
1349 }
1350
1351 #[test]
1352 fn check_reinvoke_condition_handles_negative_overscroll_offsets() {
1353 let s = invoked_state(sz(100.0, 1000.0));
1354 let container = sz(100.0, 100.0);
1355
1356 assert_eq!(
1359 s.check_reinvoke_condition(pos(0.0, -1.0e9), container),
1360 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Top))
1361 );
1362 assert_eq!(
1363 s.check_reinvoke_condition(pos(-50.0, -1.0), container),
1364 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Top))
1365 );
1366
1367 assert_eq!(
1369 s.check_reinvoke_condition(pos(0.0, 1.0e9), container),
1370 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom))
1371 );
1372
1373 let s = invoked_state(sz(-100.0, -100.0));
1375 assert_eq!(
1376 s.check_reinvoke_condition(pos(0.0, 0.0), sz(-200.0, -200.0)),
1377 None
1378 );
1379 }
1380
1381 #[test]
1382 fn check_reinvoke_condition_saturates_at_f32_extremes() {
1383 let s = invoked_state(sz(f32::MAX, f32::MAX));
1387 assert_eq!(
1388 s.check_reinvoke_condition(pos(f32::MAX, f32::MAX), sz(0.0, 0.0)),
1389 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom))
1390 );
1391
1392 assert_eq!(
1395 s.check_reinvoke_condition(pos(-f32::MAX, -f32::MAX), sz(0.0, 0.0)),
1396 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Top))
1397 );
1398
1399 assert_eq!(
1401 s.check_reinvoke_condition(pos(0.0, 0.0), sz(f32::MAX, f32::MAX)),
1402 None
1403 );
1404
1405 let s = invoked_state(sz(100.0, 100.0));
1407 assert_eq!(
1408 s.check_reinvoke_condition(pos(0.0, 0.0), sz(f32::INFINITY, f32::INFINITY)),
1409 Some(VirtualViewCallbackReason::BoundsExpanded)
1410 );
1411 let mut s = invoked_state(sz(100.0, 100.0));
1414 s.invoked_for_current_expansion = true;
1415 assert_eq!(
1416 s.check_reinvoke_condition(pos(0.0, 0.0), sz(f32::INFINITY, f32::INFINITY)),
1417 None
1418 );
1419 }
1420
1421 #[test]
1422 fn check_reinvoke_condition_needs_a_real_scroll_before_any_edge_fires() {
1423 let mut s = invoked_state(sz(100.0, 1000.0));
1424 s.initial_scroll_offset = pos(0.0, 900.0);
1425 let container = sz(100.0, 100.0);
1426
1427 assert_eq!(s.check_reinvoke_condition(pos(0.0, 900.0), container), None);
1429
1430 assert_eq!(
1432 s.check_reinvoke_condition(pos(0.0, 899.0), container),
1433 Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom))
1434 );
1435
1436 s.invoked_for_current_edge = true;
1438 assert_eq!(s.check_reinvoke_condition(pos(0.0, 899.0), container), None);
1439 }
1440
1441 #[test]
1444 fn debug_counts_matches_the_number_of_tracked_views() {
1445 let mut m = VirtualViewManager::new();
1446 assert_eq!(m.debug_counts(), 0);
1447
1448 for i in 0..10_usize {
1449 m.get_or_create_nested_dom_id(DOM, n(i));
1450 assert_eq!(m.debug_counts(), i + 1);
1451 }
1452 for i in 0..10_usize {
1454 m.get_or_create_nested_dom_id(DOM, n(i));
1455 }
1456 assert_eq!(m.debug_counts(), 10);
1457 assert_eq!(m.debug_counts(), m.all_view_keys().len());
1458 assert_eq!(m.debug_counts(), m.get_all_virtual_view_infos().len());
1459 }
1460
1461 #[test]
1462 fn was_virtual_view_invoked_is_false_until_marked() {
1463 let mut m = VirtualViewManager::new();
1464 assert!(!m.was_virtual_view_invoked(DOM, n(1)));
1465
1466 m.get_or_create_nested_dom_id(DOM, n(1));
1467 assert!(
1468 !m.was_virtual_view_invoked(DOM, n(1)),
1469 "registration alone is not an invocation"
1470 );
1471
1472 mark(&mut m, DOM, n(1), VirtualViewCallbackReason::InitialRender);
1473 assert!(m.was_virtual_view_invoked(DOM, n(1)));
1474 assert!(!m.was_virtual_view_invoked(DOM, n(2)));
1476 assert!(!m.was_virtual_view_invoked(DOM1, n(1)));
1477
1478 assert_eq!(m.force_reinvoke(DOM, n(1)), Some(()));
1479 assert!(!m.was_virtual_view_invoked(DOM, n(1)));
1480 }
1481
1482 #[test]
1483 fn get_all_virtual_view_infos_reports_every_field() {
1484 let m = VirtualViewManager::new();
1485 assert!(m.get_all_virtual_view_infos().is_empty());
1486
1487 let mut m = VirtualViewManager::new();
1488 let nested = m.get_or_create_nested_dom_id(DOM1, n(5));
1489
1490 let info = m.get_all_virtual_view_infos();
1492 assert_eq!(info.len(), 1);
1493 assert_eq!(info[0].parent_dom_id, 1);
1494 assert_eq!(info[0].parent_node_id, 5);
1495 assert_eq!(info[0].nested_dom_id, nested.inner);
1496 assert!(info[0].scroll_size_width.is_none());
1497 assert!(info[0].scroll_size_height.is_none());
1498 assert!(info[0].virtual_scroll_size_width.is_none());
1499 assert!(info[0].virtual_scroll_size_height.is_none());
1500 assert!(!info[0].was_invoked);
1501 assert_eq!(info[0].last_bounds_x, 0.0);
1502 assert_eq!(info[0].last_bounds_y, 0.0);
1503 assert_eq!(info[0].last_bounds_width, 0.0);
1504 assert_eq!(info[0].last_bounds_height, 0.0);
1505
1506 set_sizes(&mut m, DOM1, n(5), sz(3.0, 4.0), sz(5.0, 6.0));
1507 mark(&mut m, DOM1, n(5), VirtualViewCallbackReason::InitialRender);
1508
1509 let info = m.get_all_virtual_view_infos();
1510 assert_eq!(info[0].scroll_size_width, Some(3.0));
1511 assert_eq!(info[0].scroll_size_height, Some(4.0));
1512 assert_eq!(info[0].virtual_scroll_size_width, Some(5.0));
1513 assert_eq!(info[0].virtual_scroll_size_height, Some(6.0));
1514 assert!(info[0].was_invoked);
1515
1516 m.get_or_create_nested_dom_id(DOM, n(9));
1518 let keys = m.all_view_keys();
1519 let infos = m.get_all_virtual_view_infos();
1520 assert_eq!(keys.len(), infos.len());
1521 for (k, i) in keys.iter().zip(infos.iter()) {
1522 assert_eq!(k.0.inner, i.parent_dom_id);
1523 assert_eq!(k.1.index(), i.parent_node_id);
1524 }
1525 }
1526
1527 #[test]
1528 fn edge_flags_any_is_the_or_of_all_four_edges() {
1529 assert!(!EdgeFlags::default().any());
1530
1531 let mut all = EdgeFlags::default();
1532 for edge in [
1533 EdgeType::Top,
1534 EdgeType::Bottom,
1535 EdgeType::Left,
1536 EdgeType::Right,
1537 ] {
1538 let f = EdgeFlags::from(edge);
1539 assert!(f.any(), "{edge:?} alone must satisfy any()");
1540 all.top |= f.top;
1541 all.bottom |= f.bottom;
1542 all.left |= f.left;
1543 all.right |= f.right;
1544 }
1545
1546 assert_eq!(
1547 all,
1548 EdgeFlags {
1549 top: true,
1550 bottom: true,
1551 left: true,
1552 right: true,
1553 }
1554 );
1555 assert!(all.any());
1556 }
1557
1558 #[test]
1559 fn edge_flags_from_edge_type_sets_exactly_that_edge() {
1560 assert_eq!(
1561 EdgeFlags::from(EdgeType::Top),
1562 EdgeFlags {
1563 top: true,
1564 ..Default::default()
1565 }
1566 );
1567 assert_eq!(
1568 EdgeFlags::from(EdgeType::Bottom),
1569 EdgeFlags {
1570 bottom: true,
1571 ..Default::default()
1572 }
1573 );
1574 assert_eq!(
1575 EdgeFlags::from(EdgeType::Left),
1576 EdgeFlags {
1577 left: true,
1578 ..Default::default()
1579 }
1580 );
1581 assert_eq!(
1582 EdgeFlags::from(EdgeType::Right),
1583 EdgeFlags {
1584 right: true,
1585 ..Default::default()
1586 }
1587 );
1588 }
1589}