1use alloc::collections::BTreeMap;
9
10use azul_core::{
11 callbacks::{EdgeType, VirtualViewCallbackReason},
12 dom::{DomId, NodeId},
13 geom::{LogicalPosition, LogicalRect, LogicalSize},
14 hit_test::PipelineId,
15};
16
17use crate::managers::scroll_state::ScrollManager;
18
19const EDGE_THRESHOLD: f32 = 200.0;
21
22#[derive(Debug, Clone, Default)]
28pub struct VirtualViewManager {
29 states: BTreeMap<(DomId, NodeId), VirtualViewState>,
31 pipeline_ids: BTreeMap<(DomId, NodeId), PipelineId>,
33 next_dom_id: usize,
35}
36
37#[derive(Debug, Clone)]
42struct VirtualViewState {
43 virtual_view_scroll_size: Option<LogicalSize>,
45 virtual_view_virtual_scroll_size: Option<LogicalSize>,
47 virtual_view_was_invoked: bool,
49 invoked_for_current_expansion: bool,
51 invoked_for_current_edge: bool,
53 last_edge_triggered: EdgeFlags,
55 nested_dom_id: DomId,
57 last_bounds: LogicalRect,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Default)]
66pub struct EdgeFlags {
67 pub top: bool,
69 pub bottom: bool,
71 pub left: bool,
73 pub right: bool,
75}
76
77impl VirtualViewManager {
78 pub fn new() -> Self {
80 Self {
81 next_dom_id: 1, ..Default::default()
83 }
84 }
85
86 pub fn debug_counts(&self) -> (usize, usize) {
88 (self.states.len(), self.pipeline_ids.len())
89 }
90
91 pub fn begin_frame(&mut self) {
93 }
95
96 pub fn get_or_create_nested_dom_id(&mut self, dom_id: DomId, node_id: NodeId) -> DomId {
101 let key = (dom_id, node_id);
102
103 if let Some(state) = self.states.get(&key) {
105 return state.nested_dom_id;
106 }
107
108 let nested_dom_id = DomId {
110 inner: self.next_dom_id,
111 };
112 self.next_dom_id += 1;
113
114 self.states.insert(key, VirtualViewState::new(nested_dom_id));
115 nested_dom_id
116 }
117
118 pub fn get_nested_dom_id(&self, dom_id: DomId, node_id: NodeId) -> Option<DomId> {
120 self.states.get(&(dom_id, node_id)).map(|s| s.nested_dom_id)
121 }
122
123 pub fn get_or_create_pipeline_id(&mut self, dom_id: DomId, node_id: NodeId) -> PipelineId {
127 *self
128 .pipeline_ids
129 .entry((dom_id, node_id))
130 .or_insert_with(|| PipelineId(dom_id.inner as u32, node_id.index() as u32))
131 }
132
133 pub fn was_virtual_view_invoked(&self, dom_id: DomId, node_id: NodeId) -> bool {
135 self.states
136 .get(&(dom_id, node_id))
137 .map(|s| s.virtual_view_was_invoked)
138 .unwrap_or(false)
139 }
140
141 pub fn get_virtual_scroll_size(&self, dom_id: DomId, node_id: NodeId) -> Option<LogicalSize> {
143 self.states
144 .get(&(dom_id, node_id))
145 .and_then(|s| s.virtual_view_virtual_scroll_size)
146 }
147
148 pub fn get_scroll_size(&self, dom_id: DomId, node_id: NodeId) -> Option<LogicalSize> {
150 self.states
151 .get(&(dom_id, node_id))
152 .and_then(|s| s.virtual_view_scroll_size)
153 }
154
155 pub fn update_virtual_view_info(
161 &mut self,
162 dom_id: DomId,
163 node_id: NodeId,
164 scroll_size: LogicalSize,
165 virtual_scroll_size: LogicalSize,
166 ) -> Option<()> {
167 let state = self.states.get_mut(&(dom_id, node_id))?;
168
169 if let Some(old_size) = state.virtual_view_scroll_size {
171 if scroll_size.width > old_size.width || scroll_size.height > old_size.height {
172 state.invoked_for_current_expansion = false;
173 }
174 }
175 state.virtual_view_scroll_size = Some(scroll_size);
176 state.virtual_view_virtual_scroll_size = Some(virtual_scroll_size);
177
178 Some(())
179 }
180
181 pub fn mark_invoked(
186 &mut self,
187 dom_id: DomId,
188 node_id: NodeId,
189 reason: VirtualViewCallbackReason,
190 ) -> Option<()> {
191 let state = self.states.get_mut(&(dom_id, node_id))?;
192
193 state.virtual_view_was_invoked = true;
194 match reason {
195 VirtualViewCallbackReason::BoundsExpanded => state.invoked_for_current_expansion = true,
196 VirtualViewCallbackReason::EdgeScrolled(edge) => {
197 state.invoked_for_current_edge = true;
198 state.last_edge_triggered = edge.into();
199 }
200 _ => {}
201 }
202
203 Some(())
204 }
205
206 pub fn reset_all_invocation_flags(&mut self) {
214 for state in self.states.values_mut() {
215 state.virtual_view_was_invoked = false;
216 state.invoked_for_current_expansion = false;
217 state.invoked_for_current_edge = false;
218 state.last_edge_triggered = EdgeFlags::default();
219 }
220 }
221
222 pub fn force_reinvoke(&mut self, dom_id: DomId, node_id: NodeId) -> Option<()> {
227 let state = self.states.get_mut(&(dom_id, node_id))?;
228
229 state.virtual_view_was_invoked = false;
230 state.invoked_for_current_expansion = false;
231 state.invoked_for_current_edge = false;
232
233 Some(())
234 }
235
236 pub fn check_reinvoke(
245 &mut self,
246 dom_id: DomId,
247 node_id: NodeId,
248 scroll_manager: &ScrollManager,
249 layout_bounds: LogicalRect,
250 ) -> Option<VirtualViewCallbackReason> {
251 let state = self.states.entry((dom_id, node_id)).or_insert_with(|| {
252 let nested_dom_id = DomId {
253 inner: self.next_dom_id,
254 };
255 self.next_dom_id += 1;
256 VirtualViewState::new(nested_dom_id)
257 });
258
259 if !state.virtual_view_was_invoked {
260 return Some(VirtualViewCallbackReason::InitialRender);
261 }
262
263 if layout_bounds.size.width > state.last_bounds.size.width
265 || layout_bounds.size.height > state.last_bounds.size.height
266 {
267 state.invoked_for_current_expansion = false;
268 }
269 state.last_bounds = layout_bounds;
270
271 let scroll_offset = scroll_manager
272 .get_current_offset(dom_id, node_id)
273 .unwrap_or_default();
274
275 state.check_reinvoke_condition(scroll_offset, layout_bounds.size)
276 }
277
278 pub fn get_all_virtual_view_infos(&self) -> alloc::vec::Vec<VirtualViewDebugInfo> {
283 self.states
284 .iter()
285 .map(|((dom_id, node_id), state)| VirtualViewDebugInfo {
286 parent_dom_id: dom_id.inner,
287 parent_node_id: node_id.index(),
288 nested_dom_id: state.nested_dom_id.inner,
289 scroll_size_width: state.virtual_view_scroll_size.map(|s| s.width),
290 scroll_size_height: state.virtual_view_scroll_size.map(|s| s.height),
291 virtual_scroll_size_width: state.virtual_view_virtual_scroll_size.map(|s| s.width),
292 virtual_scroll_size_height: state.virtual_view_virtual_scroll_size.map(|s| s.height),
293 was_invoked: state.virtual_view_was_invoked,
294 last_bounds_x: state.last_bounds.origin.x,
295 last_bounds_y: state.last_bounds.origin.y,
296 last_bounds_width: state.last_bounds.size.width,
297 last_bounds_height: state.last_bounds.size.height,
298 })
299 .collect()
300 }
301}
302
303#[derive(Debug, Clone)]
305pub struct VirtualViewDebugInfo {
306 pub parent_dom_id: usize,
307 pub parent_node_id: usize,
308 pub nested_dom_id: usize,
309 pub scroll_size_width: Option<f32>,
310 pub scroll_size_height: Option<f32>,
311 pub virtual_scroll_size_width: Option<f32>,
312 pub virtual_scroll_size_height: Option<f32>,
313 pub was_invoked: bool,
314 pub last_bounds_x: f32,
315 pub last_bounds_y: f32,
316 pub last_bounds_width: f32,
317 pub last_bounds_height: f32,
318}
319
320impl VirtualViewState {
321 fn new(nested_dom_id: DomId) -> Self {
323 Self {
324 virtual_view_scroll_size: None,
325 virtual_view_virtual_scroll_size: None,
326 virtual_view_was_invoked: false,
327 invoked_for_current_expansion: false,
328 invoked_for_current_edge: false,
329 last_edge_triggered: EdgeFlags::default(),
330 nested_dom_id,
331 last_bounds: LogicalRect::zero(),
332 }
333 }
334
335 fn check_reinvoke_condition(
342 &mut self,
343 current_offset: LogicalPosition,
344 container_size: LogicalSize,
345 ) -> Option<VirtualViewCallbackReason> {
346 let Some(scroll_size) = self.virtual_view_scroll_size else {
348 return None;
349 };
350
351 if !self.invoked_for_current_expansion
353 && (container_size.width > scroll_size.width
354 || container_size.height > scroll_size.height)
355 {
356 return Some(VirtualViewCallbackReason::BoundsExpanded);
357 }
358
359 let scrollable_width = scroll_size.width > container_size.width;
362 let scrollable_height = scroll_size.height > container_size.height;
363
364 let current_edges = EdgeFlags {
366 top: scrollable_height && current_offset.y <= EDGE_THRESHOLD,
367 bottom: scrollable_height
368 && (scroll_size.height - container_size.height - current_offset.y)
369 <= EDGE_THRESHOLD,
370 left: scrollable_width && current_offset.x <= EDGE_THRESHOLD,
371 right: scrollable_width
372 && (scroll_size.width - container_size.width - current_offset.x) <= EDGE_THRESHOLD,
373 };
374
375 if !self.invoked_for_current_edge && current_edges.any() {
378 if current_edges.bottom && !self.last_edge_triggered.bottom {
379 return Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Bottom));
380 }
381 if current_edges.right && !self.last_edge_triggered.right {
382 return Some(VirtualViewCallbackReason::EdgeScrolled(EdgeType::Right));
383 }
384 }
385
386 None
387 }
388}
389
390impl EdgeFlags {
391 fn any(&self) -> bool {
393 self.top || self.bottom || self.left || self.right
394 }
395}
396
397impl From<EdgeType> for EdgeFlags {
398 fn from(edge: EdgeType) -> Self {
399 match edge {
400 EdgeType::Top => Self {
401 top: true,
402 ..Default::default()
403 },
404 EdgeType::Bottom => Self {
405 bottom: true,
406 ..Default::default()
407 },
408 EdgeType::Left => Self {
409 left: true,
410 ..Default::default()
411 },
412 EdgeType::Right => Self {
413 right: true,
414 ..Default::default()
415 },
416 }
417 }
418}