1use alloc::collections::BTreeMap;
9use std::sync::atomic::{AtomicUsize, Ordering};
10
11use azul_core::{
12 callbacks::{EdgeType, IFrameCallbackReason},
13 dom::{DomId, NodeId},
14 geom::{LogicalPosition, LogicalRect, LogicalSize},
15 hit_test::PipelineId,
16};
17
18use crate::managers::scroll_state::ScrollManager;
19
20static NEXT_PIPELINE_ID: AtomicUsize = AtomicUsize::new(1);
21
22const EDGE_THRESHOLD: f32 = 200.0;
24
25#[derive(Debug, Clone, Default)]
31pub struct IFrameManager {
32 states: BTreeMap<(DomId, NodeId), IFrameState>,
34 pipeline_ids: BTreeMap<(DomId, NodeId), PipelineId>,
36 next_dom_id: usize,
38}
39
40#[derive(Debug, Clone)]
45struct IFrameState {
46 iframe_scroll_size: Option<LogicalSize>,
48 iframe_virtual_scroll_size: Option<LogicalSize>,
50 iframe_was_invoked: bool,
52 invoked_for_current_expansion: bool,
54 invoked_for_current_edge: bool,
56 last_edge_triggered: EdgeFlags,
58 nested_dom_id: DomId,
60 last_bounds: LogicalRect,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Default)]
69pub struct EdgeFlags {
70 pub top: bool,
72 pub bottom: bool,
74 pub left: bool,
76 pub right: bool,
78}
79
80impl IFrameManager {
81 pub fn new() -> Self {
83 Self {
84 next_dom_id: 1, ..Default::default()
86 }
87 }
88
89 pub fn begin_frame(&mut self) {
91 }
93
94 pub fn get_or_create_nested_dom_id(&mut self, dom_id: DomId, node_id: NodeId) -> DomId {
99 let key = (dom_id, node_id);
100
101 if let Some(state) = self.states.get(&key) {
103 return state.nested_dom_id;
104 }
105
106 let nested_dom_id = DomId {
108 inner: self.next_dom_id,
109 };
110 self.next_dom_id += 1;
111
112 self.states.insert(key, IFrameState::new(nested_dom_id));
113 nested_dom_id
114 }
115
116 pub fn get_nested_dom_id(&self, dom_id: DomId, node_id: NodeId) -> Option<DomId> {
118 self.states.get(&(dom_id, node_id)).map(|s| s.nested_dom_id)
119 }
120
121 pub fn get_or_create_pipeline_id(&mut self, dom_id: DomId, node_id: NodeId) -> PipelineId {
125 *self
126 .pipeline_ids
127 .entry((dom_id, node_id))
128 .or_insert_with(|| PipelineId(dom_id.inner as u32, node_id.index() as u32))
129 }
130
131 pub fn was_iframe_invoked(&self, dom_id: DomId, node_id: NodeId) -> bool {
133 self.states
134 .get(&(dom_id, node_id))
135 .map(|s| s.iframe_was_invoked)
136 .unwrap_or(false)
137 }
138
139 pub fn update_iframe_info(
145 &mut self,
146 dom_id: DomId,
147 node_id: NodeId,
148 scroll_size: LogicalSize,
149 virtual_scroll_size: LogicalSize,
150 ) -> Option<()> {
151 let state = self.states.get_mut(&(dom_id, node_id))?;
152
153 if let Some(old_size) = state.iframe_scroll_size {
155 if scroll_size.width > old_size.width || scroll_size.height > old_size.height {
156 state.invoked_for_current_expansion = false;
157 }
158 }
159 state.iframe_scroll_size = Some(scroll_size);
160 state.iframe_virtual_scroll_size = Some(virtual_scroll_size);
161
162 Some(())
163 }
164
165 pub fn mark_invoked(
170 &mut self,
171 dom_id: DomId,
172 node_id: NodeId,
173 reason: IFrameCallbackReason,
174 ) -> Option<()> {
175 let state = self.states.get_mut(&(dom_id, node_id))?;
176
177 state.iframe_was_invoked = true;
178 match reason {
179 IFrameCallbackReason::BoundsExpanded => state.invoked_for_current_expansion = true,
180 IFrameCallbackReason::EdgeScrolled(edge) => {
181 state.invoked_for_current_edge = true;
182 state.last_edge_triggered = edge.into();
183 }
184 _ => {}
185 }
186
187 Some(())
188 }
189
190 pub fn force_reinvoke(&mut self, dom_id: DomId, node_id: NodeId) -> Option<()> {
195 let state = self.states.get_mut(&(dom_id, node_id))?;
196
197 state.iframe_was_invoked = false;
198 state.invoked_for_current_expansion = false;
199 state.invoked_for_current_edge = false;
200
201 Some(())
202 }
203
204 pub fn check_reinvoke(
213 &mut self,
214 dom_id: DomId,
215 node_id: NodeId,
216 scroll_manager: &ScrollManager,
217 layout_bounds: LogicalRect,
218 ) -> Option<IFrameCallbackReason> {
219 let state = self.states.entry((dom_id, node_id)).or_insert_with(|| {
220 let nested_dom_id = DomId {
221 inner: self.next_dom_id,
222 };
223 self.next_dom_id += 1;
224 IFrameState::new(nested_dom_id)
225 });
226
227 if !state.iframe_was_invoked {
228 return Some(IFrameCallbackReason::InitialRender);
229 }
230
231 if layout_bounds.size.width > state.last_bounds.size.width
233 || layout_bounds.size.height > state.last_bounds.size.height
234 {
235 state.invoked_for_current_expansion = false;
236 }
237 state.last_bounds = layout_bounds;
238
239 let scroll_offset = scroll_manager
240 .get_current_offset(dom_id, node_id)
241 .unwrap_or_default();
242
243 state.check_reinvoke_condition(scroll_offset, layout_bounds.size)
244 }
245}
246
247impl IFrameState {
248 fn new(nested_dom_id: DomId) -> Self {
250 Self {
251 iframe_scroll_size: None,
252 iframe_virtual_scroll_size: None,
253 iframe_was_invoked: false,
254 invoked_for_current_expansion: false,
255 invoked_for_current_edge: false,
256 last_edge_triggered: EdgeFlags::default(),
257 nested_dom_id,
258 last_bounds: LogicalRect::zero(),
259 }
260 }
261
262 fn check_reinvoke_condition(
269 &mut self,
270 current_offset: LogicalPosition,
271 container_size: LogicalSize,
272 ) -> Option<IFrameCallbackReason> {
273 let Some(scroll_size) = self.iframe_scroll_size else {
275 return None;
276 };
277
278 if !self.invoked_for_current_expansion
280 && (container_size.width > scroll_size.width
281 || container_size.height > scroll_size.height)
282 {
283 return Some(IFrameCallbackReason::BoundsExpanded);
284 }
285
286 let scrollable_width = scroll_size.width > container_size.width;
289 let scrollable_height = scroll_size.height > container_size.height;
290
291 let current_edges = EdgeFlags {
293 top: scrollable_height && current_offset.y <= EDGE_THRESHOLD,
294 bottom: scrollable_height
295 && (scroll_size.height - container_size.height - current_offset.y)
296 <= EDGE_THRESHOLD,
297 left: scrollable_width && current_offset.x <= EDGE_THRESHOLD,
298 right: scrollable_width
299 && (scroll_size.width - container_size.width - current_offset.x) <= EDGE_THRESHOLD,
300 };
301
302 if !self.invoked_for_current_edge && current_edges.any() {
305 if current_edges.bottom && !self.last_edge_triggered.bottom {
306 return Some(IFrameCallbackReason::EdgeScrolled(EdgeType::Bottom));
307 }
308 if current_edges.right && !self.last_edge_triggered.right {
309 return Some(IFrameCallbackReason::EdgeScrolled(EdgeType::Right));
310 }
311 }
312
313 None
314 }
315}
316
317impl EdgeFlags {
318 fn any(&self) -> bool {
320 self.top || self.bottom || self.left || self.right
321 }
322}
323
324impl From<EdgeType> for EdgeFlags {
325 fn from(edge: EdgeType) -> Self {
326 match edge {
327 EdgeType::Top => Self {
328 top: true,
329 ..Default::default()
330 },
331 EdgeType::Bottom => Self {
332 bottom: true,
333 ..Default::default()
334 },
335 EdgeType::Left => Self {
336 left: true,
337 ..Default::default()
338 },
339 EdgeType::Right => Self {
340 right: true,
341 ..Default::default()
342 },
343 }
344 }
345}