azul_core/
window_state.rs

1//! Event and callback filtering module
2//!
3//! # Example
4//!
5//! ```rust
6//! let mut app_state = RefAny::new(AppState { counter: 5 });
7//! let pipeline_id = PipelineId::new(0);
8//!
9//! // initial layout
10//! let mut styled_dom = render_dom(&app_state, layout_info);
11//! let mut layout_results = do_layout(&styled_dom);
12//!
13//! let mut previous_window_state = None;
14//! let mut current_window_state = FulLWindowState::default();
15//!
16//! draw_display_list_to_screen(CachedDisplayList::new(&layout_results));
17//!
18//! loop { // window loop
19//!
20//!      // update the current_window_state from your preferred OS windowing library
21//!      current_window_state.cursor = CursorPosition::InWindow(200, 500);
22//!
23//!      let events = Events::new(&current_window_state, &previous_window_state);
24//!      let hit_test = HitTest::new(&current_window_state, &layout_results, &current_window_state.scroll_states);
25//!
26//!      previous_window_state = Some(current_window_state.clone());
27//!      current_window_state.focused_node = hit_test.focused_node;
28//!      current_window_state.hovered_nodes = hit_test.hovered_nodes;
29//!
30//!      let nodes_to_check = NodesToCheck::new(&hit_test, &events);
31//!      let callbacks = CallbacksOfHitTest::new(&nodes_to_check, &events, &window.layout_results);
32//!      let callback_result = call_callbacks(&callbacks, &hit_test);
33//!
34//!      if callbacks.update_screen = Update::Relayout {
35//!
36//!         // redo the entire layout
37//!         styled_dom = render_dom(&app_state, layout_info);
38//!         layout_results = do_layout(&styled_dom);
39//!         draw_display_list_to_screen(CachedDisplayList::new(&layout_results));
40//!
41//!      } else {
42//!
43//!           // only relayout what is necessary
44//!           let style_and_layout_changes = StyleAndLayoutChanges::new(
45//!               &nodes_to_check,
46//!               &mut layout_results,
47//!               &mut renderer_resources,
48//!               &current_window_state.dimensions.size,
49//!               pipeline_id,
50//!               azul_layout::do_the_relayout
51//!           );
52//!
53//!           if !style_and_layout_changes.is_empty() {
54//!               draw_display_list_to_screen(CachedDisplayList::new(&layout_results));
55//!           // } else if let Some(iframes) = style_and_layout_changes.get_iframes_to_relayout() { }
56//!           // } else if let Some(gl_textures) = style_and_layout_changes.get_gltextures_to_redraw() { }
57//!           } else {
58//!               // nothing to do
59//!           }
60//!      }
61//!
62//!      #break; // - for doc test
63//! }
64//! ```rust
65
66#[cfg(not(feature = "std"))]
67use alloc::string::{String, ToString};
68use alloc::{
69    boxed::Box,
70    collections::{btree_map::BTreeMap, btree_set::BTreeSet},
71    vec::Vec,
72};
73
74use azul_css::{AzString, CssProperty, LayoutPoint, LayoutRect, LayoutSize};
75use rust_fontconfig::FcFontCache;
76
77use crate::{
78    app_resources::{ImageCache, RendererResources},
79    callbacks::{DocumentId, DomNodeId, HitTestItem, ScrollPosition, Update},
80    dom::{EventFilter, FocusEventFilter, HoverEventFilter, NotEventFilter, WindowEventFilter},
81    gl::OptionGlContextPtr,
82    id_tree::NodeId,
83    styled_dom::{ChangedCssProperty, DomId, NodeHierarchyItemId},
84    task::ExternalSystemCallbacks,
85    ui_solver::{GpuEventChanges, LayoutResult, RelayoutChanges},
86    window::{CallCallbacksResult, FullHitTest, FullWindowState, RawWindowHandle, ScrollStates},
87    FastBTreeSet, FastHashMap,
88};
89
90#[derive(Debug, Clone, PartialEq)]
91pub struct Events {
92    pub window_events: Vec<WindowEventFilter>,
93    pub hover_events: Vec<HoverEventFilter>,
94    pub focus_events: Vec<FocusEventFilter>,
95    pub old_hit_node_ids: BTreeMap<DomId, BTreeMap<NodeId, HitTestItem>>,
96    pub old_focus_node: Option<DomNodeId>,
97    pub current_window_state_mouse_is_down: bool,
98    pub previous_window_state_mouse_is_down: bool,
99    pub event_was_mouse_down: bool,
100    pub event_was_mouse_leave: bool,
101    pub event_was_mouse_release: bool,
102}
103
104impl Events {
105    /// Warning: if the previous_window_state is none, this will return an empty Vec!
106    pub fn new(
107        current_window_state: &FullWindowState,
108        previous_window_state: &Option<FullWindowState>,
109    ) -> Self {
110        let mut current_window_events =
111            get_window_events(current_window_state, previous_window_state);
112        let mut current_hover_events = get_hover_events(&current_window_events);
113        let mut current_focus_events = get_focus_events(&current_hover_events);
114
115        let event_was_mouse_down = current_window_events
116            .iter()
117            .any(|e| *e == WindowEventFilter::MouseDown);
118        let event_was_mouse_release = current_window_events
119            .iter()
120            .any(|e| *e == WindowEventFilter::MouseUp);
121        let event_was_mouse_leave = current_window_events
122            .iter()
123            .any(|e| *e == WindowEventFilter::MouseLeave);
124        let current_window_state_mouse_is_down = current_window_state.mouse_state.mouse_down();
125        let previous_window_state_mouse_is_down = previous_window_state
126            .as_ref()
127            .map(|f| f.mouse_state.mouse_down())
128            .unwrap_or(false);
129
130        let old_focus_node = previous_window_state
131            .as_ref()
132            .and_then(|f| f.focused_node.clone());
133        let old_hit_node_ids = previous_window_state
134            .as_ref()
135            .map(|f| {
136                if f.last_hit_test.hovered_nodes.is_empty() {
137                    BTreeMap::new()
138                } else {
139                    f.last_hit_test
140                        .hovered_nodes
141                        .iter()
142                        .map(|(dom_id, hit_test)| {
143                            (*dom_id, hit_test.regular_hit_test_nodes.clone())
144                        })
145                        .collect()
146                }
147            })
148            .unwrap_or_default();
149
150        if let Some(prev_state) = previous_window_state.as_ref() {
151            if prev_state.theme != current_window_state.theme {
152                current_window_events.push(WindowEventFilter::ThemeChanged);
153            }
154            if current_window_state.last_hit_test.hovered_nodes
155                != prev_state.last_hit_test.hovered_nodes.clone()
156            {
157                current_hover_events.push(HoverEventFilter::MouseLeave);
158                current_hover_events.push(HoverEventFilter::MouseEnter);
159            }
160        }
161
162        // even if there are no window events, the focus node can changed
163        if current_window_state.focused_node != old_focus_node {
164            current_focus_events.push(FocusEventFilter::FocusReceived);
165            current_focus_events.push(FocusEventFilter::FocusLost);
166        }
167
168        Events {
169            window_events: current_window_events,
170            hover_events: current_hover_events,
171            focus_events: current_focus_events,
172            event_was_mouse_down,
173            event_was_mouse_release,
174            event_was_mouse_leave,
175            current_window_state_mouse_is_down,
176            previous_window_state_mouse_is_down,
177            old_focus_node,
178            old_hit_node_ids,
179        }
180    }
181
182    pub fn is_empty(&self) -> bool {
183        self.window_events.is_empty()
184            && self.hover_events.is_empty()
185            && self.focus_events.is_empty()
186    }
187
188    /// Checks whether the event was a resize event
189    pub fn contains_resize_event(&self) -> bool {
190        self.window_events.contains(&WindowEventFilter::Resized)
191    }
192
193    pub fn event_was_mouse_scroll(&self) -> bool {
194        // TODO: also need to look at TouchStart / TouchDrag
195        self.window_events.contains(&WindowEventFilter::Scroll)
196    }
197
198    pub fn needs_hit_test(&self) -> bool {
199        !(self.hover_events.is_empty() && self.focus_events.is_empty())
200    }
201}
202
203#[derive(Debug, Clone, PartialEq)]
204pub struct NodesToCheck {
205    pub new_hit_node_ids: BTreeMap<DomId, BTreeMap<NodeId, HitTestItem>>,
206    pub old_hit_node_ids: BTreeMap<DomId, BTreeMap<NodeId, HitTestItem>>,
207    pub onmouseenter_nodes: BTreeMap<DomId, BTreeMap<NodeId, HitTestItem>>,
208    pub onmouseleave_nodes: BTreeMap<DomId, BTreeMap<NodeId, HitTestItem>>,
209    pub old_focus_node: Option<DomNodeId>,
210    pub new_focus_node: Option<DomNodeId>,
211    pub current_window_state_mouse_is_down: bool,
212}
213
214impl NodesToCheck {
215    // Usually we need to perform a hit-test when the DOM is re-generated,
216    // this function simulates that behaviour
217    pub fn simulated_mouse_move(
218        hit_test: &FullHitTest,
219        old_focus_node: Option<DomNodeId>,
220        mouse_down: bool,
221    ) -> Self {
222        let new_hit_node_ids = hit_test
223            .hovered_nodes
224            .iter()
225            .map(|(k, v)| (k.clone(), v.regular_hit_test_nodes.clone()))
226            .collect::<BTreeMap<_, _>>();
227
228        Self {
229            new_hit_node_ids: new_hit_node_ids.clone(),
230            old_hit_node_ids: BTreeMap::new(),
231            onmouseenter_nodes: new_hit_node_ids,
232            onmouseleave_nodes: BTreeMap::new(),
233            old_focus_node,
234            new_focus_node: old_focus_node,
235            current_window_state_mouse_is_down: mouse_down,
236        }
237    }
238
239    /// Determine which nodes are even relevant for callbacks or restyling
240    //
241    // TODO: avoid iteration / allocation!
242    pub fn new(hit_test: &FullHitTest, events: &Events) -> Self {
243        // TODO: If the current mouse is down, but the event wasn't a click, that means it was a
244        // drag
245
246        // Figure out what the hovered NodeIds are
247        let new_hit_node_ids = if events.event_was_mouse_leave {
248            BTreeMap::new()
249        } else {
250            hit_test
251                .hovered_nodes
252                .iter()
253                .map(|(k, v)| (k.clone(), v.regular_hit_test_nodes.clone()))
254                .collect()
255        };
256
257        // Figure out what the current focused NodeId is
258        let new_focus_node = if events.event_was_mouse_release {
259            hit_test.focused_node.clone().map(|o| DomNodeId {
260                dom: o.0,
261                node: NodeHierarchyItemId::from_crate_internal(Some(o.1)),
262            })
263        } else {
264            events.old_focus_node.clone()
265        };
266
267        // Collect all On::MouseEnter nodes (for both hover and focus events)
268        let default_map = BTreeMap::new();
269        let onmouseenter_nodes = new_hit_node_ids
270            .iter()
271            .filter_map(|(dom_id, nhnid)| {
272                let old_hit_node_ids = events.old_hit_node_ids.get(dom_id).unwrap_or(&default_map);
273                let new = nhnid
274                    .iter()
275                    .filter(|(current_node_id, _)| old_hit_node_ids.get(current_node_id).is_none())
276                    .map(|(x, y)| (*x, y.clone()))
277                    .collect::<BTreeMap<_, _>>();
278                if new.is_empty() {
279                    None
280                } else {
281                    Some((*dom_id, new))
282                }
283            })
284            .collect::<BTreeMap<_, _>>();
285
286        // Collect all On::MouseLeave nodes (for both hover and focus events)
287        let onmouseleave_nodes = events
288            .old_hit_node_ids
289            .iter()
290            .filter_map(|(dom_id, ohnid)| {
291                let old = ohnid
292                    .iter()
293                    .filter(|(prev_node_id, _)| {
294                        new_hit_node_ids
295                            .get(dom_id)
296                            .and_then(|d| d.get(prev_node_id))
297                            .is_none()
298                    })
299                    .map(|(x, y)| (*x, y.clone()))
300                    .collect::<BTreeMap<_, _>>();
301                if old.is_empty() {
302                    None
303                } else {
304                    Some((*dom_id, old))
305                }
306            })
307            .collect::<BTreeMap<_, _>>();
308
309        NodesToCheck {
310            new_hit_node_ids,
311            old_hit_node_ids: events.old_hit_node_ids.clone(),
312            onmouseenter_nodes,
313            onmouseleave_nodes,
314            old_focus_node: events.old_focus_node.clone(),
315            new_focus_node,
316            current_window_state_mouse_is_down: events.current_window_state_mouse_is_down,
317        }
318    }
319
320    pub fn empty(mouse_down: bool, old_focus_node: Option<DomNodeId>) -> Self {
321        Self {
322            new_hit_node_ids: BTreeMap::new(),
323            old_hit_node_ids: BTreeMap::new(),
324            onmouseenter_nodes: BTreeMap::new(),
325            onmouseleave_nodes: BTreeMap::new(),
326            old_focus_node,
327            new_focus_node: old_focus_node,
328            current_window_state_mouse_is_down: mouse_down,
329        }
330    }
331
332    pub fn needs_hover_active_restyle(&self) -> bool {
333        !(self.onmouseenter_nodes.is_empty() && self.onmouseleave_nodes.is_empty())
334    }
335
336    pub fn needs_focus_result(&self) -> bool {
337        self.old_focus_node != self.new_focus_node
338    }
339}
340
341pub type RestyleNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
342pub type RelayoutNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
343pub type RelayoutWords = BTreeMap<NodeId, AzString>;
344
345/// Style and layout changes
346#[derive(Debug, Clone, PartialEq)]
347pub struct StyleAndLayoutChanges {
348    /// Changes that were made to style properties of nodes
349    pub style_changes: Option<BTreeMap<DomId, RestyleNodes>>,
350    /// Changes that were made to layout properties of nodes
351    pub layout_changes: Option<BTreeMap<DomId, RelayoutNodes>>,
352    /// Whether the focus has actually changed
353    pub focus_change: Option<FocusChange>,
354    /// Used to call `On::Resize` handlers
355    pub nodes_that_changed_size: Option<BTreeMap<DomId, Vec<NodeId>>>,
356    /// Changes to the text content
357    pub nodes_that_changed_text_content: Option<BTreeMap<DomId, Vec<NodeId>>>,
358    /// Changes to GPU-cached opacity / transform values
359    pub gpu_key_changes: Option<BTreeMap<DomId, GpuEventChanges>>,
360}
361
362#[derive(Debug, Clone, PartialEq)]
363pub struct FocusChange {
364    pub old: Option<DomNodeId>,
365    pub new: Option<DomNodeId>,
366}
367
368// azul_layout::do_the_relayout satifies this
369pub type RelayoutFn = fn(
370    DomId,
371    LayoutRect,
372    &mut LayoutResult,
373    &ImageCache,
374    &mut RendererResources,
375    &DocumentId,
376    Option<&RelayoutNodes>,
377    Option<&RelayoutWords>,
378) -> RelayoutChanges;
379
380impl StyleAndLayoutChanges {
381    /// Determines and immediately applies the changes to the layout results
382    pub fn new(
383        nodes: &NodesToCheck,
384        layout_results: &mut [LayoutResult],
385        image_cache: &ImageCache,
386        renderer_resources: &mut RendererResources,
387        window_size: LayoutSize,
388        document_id: &DocumentId,
389        css_changes: Option<&BTreeMap<DomId, BTreeMap<NodeId, Vec<CssProperty>>>>,
390        word_changes: Option<&BTreeMap<DomId, BTreeMap<NodeId, AzString>>>,
391        callbacks_new_focus: &Option<Option<DomNodeId>>,
392        relayout_cb: RelayoutFn,
393    ) -> StyleAndLayoutChanges {
394        // immediately restyle the DOM to reflect the new :hover, :active and :focus nodes
395        // and determine if the DOM needs a redraw or a relayout
396        let mut style_changes = None;
397        let mut layout_changes = None;
398
399        let is_mouse_down = nodes.current_window_state_mouse_is_down;
400        let nodes_that_changed_text_content = word_changes.and_then(|word_changes| {
401            if word_changes.is_empty() {
402                None
403            } else {
404                Some(
405                    word_changes
406                        .iter()
407                        .map(|(dom_id, m)| (*dom_id, m.keys().cloned().collect()))
408                        .collect(),
409                )
410            }
411        });
412
413        macro_rules! insert_props {
414            ($dom_id:expr, $prop_map:expr) => {{
415                let dom_id: DomId = $dom_id;
416                for (node_id, prop_map) in $prop_map.into_iter() {
417                    for changed_prop in prop_map.into_iter() {
418                        let prop_key = changed_prop.previous_prop.get_type();
419                        if prop_key.can_trigger_relayout() {
420                            layout_changes
421                                .get_or_insert_with(|| BTreeMap::new())
422                                .entry(dom_id)
423                                .or_insert_with(|| BTreeMap::new())
424                                .entry(node_id)
425                                .or_insert_with(|| Vec::new())
426                                .push(changed_prop);
427                        } else {
428                            style_changes
429                                .get_or_insert_with(|| BTreeMap::new())
430                                .entry(dom_id)
431                                .or_insert_with(|| BTreeMap::new())
432                                .entry(node_id)
433                                .or_insert_with(|| Vec::new())
434                                .push(changed_prop);
435                        }
436                    }
437                }
438            }};
439        }
440
441        for (dom_id, onmouseenter_nodes) in nodes.onmouseenter_nodes.iter() {
442            let layout_result = &mut layout_results[dom_id.inner];
443
444            let keys = onmouseenter_nodes.keys().copied().collect::<Vec<_>>();
445            let onmouseenter_nodes_hover_restyle_props = layout_result
446                .styled_dom
447                .restyle_nodes_hover(&keys, /* currently_hovered = */ true);
448            let onmouseleave_nodes_active_restyle_props = layout_result
449                .styled_dom
450                .restyle_nodes_active(&keys, /* currently_active = */ is_mouse_down);
451
452            insert_props!(*dom_id, onmouseenter_nodes_hover_restyle_props);
453            insert_props!(*dom_id, onmouseleave_nodes_active_restyle_props);
454        }
455
456        for (dom_id, onmouseleave_nodes) in nodes.onmouseleave_nodes.iter() {
457            let layout_result = &mut layout_results[dom_id.inner];
458            let keys = onmouseleave_nodes.keys().copied().collect::<Vec<_>>();
459            let onmouseleave_nodes_hover_restyle_props = layout_result
460                .styled_dom
461                .restyle_nodes_hover(&keys, /* currently_hovered = */ false);
462            let onmouseleave_nodes_active_restyle_props = layout_result
463                .styled_dom
464                .restyle_nodes_active(&keys, /* currently_active = */ false);
465
466            insert_props!(*dom_id, onmouseleave_nodes_hover_restyle_props);
467            insert_props!(*dom_id, onmouseleave_nodes_active_restyle_props);
468        }
469
470        let new_focus_node = if let Some(new) = callbacks_new_focus.as_ref() {
471            new
472        } else {
473            &nodes.new_focus_node
474        };
475
476        let focus_change = if nodes.old_focus_node != *new_focus_node {
477            if let Some(DomNodeId { dom, node }) = nodes.old_focus_node.as_ref() {
478                if let Some(node_id) = node.into_crate_internal() {
479                    let layout_result = &mut layout_results[dom.inner];
480                    let onfocus_leave_restyle_props = layout_result
481                        .styled_dom
482                        .restyle_nodes_focus(&[node_id], /* currently_focused = */ false);
483                    let dom_id: DomId = *dom;
484                    insert_props!(dom_id, onfocus_leave_restyle_props);
485                }
486            }
487
488            if let Some(DomNodeId { dom, node }) = new_focus_node.as_ref() {
489                if let Some(node_id) = node.into_crate_internal() {
490                    let layout_result = &mut layout_results[dom.inner];
491                    let onfocus_enter_restyle_props = layout_result
492                        .styled_dom
493                        .restyle_nodes_focus(&[node_id], /* currently_focused = */ true);
494                    let dom_id: DomId = *dom;
495                    insert_props!(dom_id, onfocus_enter_restyle_props);
496                }
497            }
498
499            Some(FocusChange {
500                old: nodes.old_focus_node,
501                new: *new_focus_node,
502            })
503        } else {
504            None
505        };
506
507        // restyle all the nodes according to the existing_changed_styles
508        if let Some(css_changes) = css_changes {
509            for (dom_id, existing_changes_map) in css_changes.iter() {
510                let layout_result = &mut layout_results[dom_id.inner];
511                let dom_id: DomId = *dom_id;
512                for (node_id, changed_css_property_vec) in existing_changes_map.iter() {
513                    let current_prop_changes = layout_result
514                        .styled_dom
515                        .restyle_user_property(node_id, &changed_css_property_vec);
516                    insert_props!(dom_id, current_prop_changes);
517                }
518            }
519        }
520
521        let mut nodes_that_changed_size = None;
522        let mut gpu_key_change_events = None;
523
524        // recursively relayout if there are layout_changes or the window size has changed
525        let window_was_resized = window_size != layout_results[DomId::ROOT_ID.inner].root_size;
526        let need_root_relayout = layout_changes.is_some()
527            || window_was_resized
528            || nodes_that_changed_text_content.is_some();
529
530        let mut doms_to_relayout = Vec::new();
531        if need_root_relayout {
532            doms_to_relayout.push(DomId::ROOT_ID);
533        } else {
534            // if no nodes were resized or styles changed,
535            // still update the GPU-only properties
536            for (dom_id, layout_result) in layout_results.iter_mut().enumerate() {
537                let gpu_key_changes = layout_result
538                    .gpu_value_cache
539                    .synchronize(&layout_result.rects.as_ref(), &layout_result.styled_dom);
540
541                if !gpu_key_changes.is_empty() {
542                    gpu_key_change_events
543                        .get_or_insert_with(|| BTreeMap::new())
544                        .insert(DomId { inner: dom_id }, gpu_key_changes);
545                }
546            }
547        }
548
549        loop {
550            let mut new_iframes_to_relayout = Vec::new();
551
552            for dom_id in doms_to_relayout.drain(..) {
553                let parent_rect = match layout_results[dom_id.inner].parent_dom_id.as_ref() {
554                    None => LayoutRect::new(LayoutPoint::zero(), window_size),
555                    Some(parent_dom_id) => {
556                        let parent_layout_result = &layout_results[parent_dom_id.inner];
557                        let parent_iframe_node_id = parent_layout_result
558                            .iframe_mapping
559                            .iter()
560                            .find_map(|(k, v)| if *v == dom_id { Some(*k) } else { None })
561                            .unwrap();
562                        parent_layout_result.rects.as_ref()[parent_iframe_node_id]
563                            .get_approximate_static_bounds()
564                    }
565                };
566
567                let layout_changes = layout_changes.as_ref().and_then(|w| w.get(&dom_id));
568                let word_changes = word_changes.and_then(|w| w.get(&dom_id));
569
570                // TODO: avoid allocation
571                let RelayoutChanges {
572                    resized_nodes,
573                    gpu_key_changes,
574                } = (relayout_cb)(
575                    dom_id,
576                    parent_rect,
577                    &mut layout_results[dom_id.inner],
578                    image_cache,
579                    renderer_resources,
580                    document_id,
581                    layout_changes,
582                    word_changes,
583                );
584
585                if !gpu_key_changes.is_empty() {
586                    gpu_key_change_events
587                        .get_or_insert_with(|| BTreeMap::new())
588                        .insert(dom_id, gpu_key_changes);
589                }
590
591                if !resized_nodes.is_empty() {
592                    new_iframes_to_relayout.extend(
593                        layout_results[dom_id.inner]
594                            .iframe_mapping
595                            .iter()
596                            .filter_map(|(node_id, dom_id)| {
597                                if resized_nodes.contains(node_id) {
598                                    Some(dom_id)
599                                } else {
600                                    None
601                                }
602                            }),
603                    );
604                    nodes_that_changed_size
605                        .get_or_insert_with(|| BTreeMap::new())
606                        .insert(dom_id, resized_nodes);
607                }
608            }
609
610            if new_iframes_to_relayout.is_empty() {
611                break;
612            } else {
613                doms_to_relayout = new_iframes_to_relayout;
614            }
615        }
616
617        StyleAndLayoutChanges {
618            style_changes,
619            layout_changes,
620            nodes_that_changed_size,
621            nodes_that_changed_text_content,
622            focus_change,
623            gpu_key_changes: gpu_key_change_events,
624        }
625    }
626
627    pub fn did_resize_nodes(&self) -> bool {
628        use azul_css::CssPropertyType;
629
630        if let Some(l) = self.nodes_that_changed_size.as_ref() {
631            if !l.is_empty() {
632                return true;
633            }
634        }
635
636        if let Some(l) = self.nodes_that_changed_text_content.as_ref() {
637            if !l.is_empty() {
638                return true;
639            }
640        }
641
642        // check if any changed node is a CSS transform
643        if let Some(s) = self.style_changes.as_ref() {
644            for restyle_nodes in s.values() {
645                for changed in restyle_nodes.values() {
646                    for changed in changed.iter() {
647                        if changed.current_prop.get_type() == CssPropertyType::Transform {
648                            return true;
649                        }
650                    }
651                }
652            }
653        }
654        false
655    }
656
657    // Note: this can be false in case that only opacity: / transform: properties changed!
658    pub fn need_regenerate_display_list(&self) -> bool {
659        if !self.nodes_that_changed_size.is_none() {
660            return true;
661        }
662        if !self.nodes_that_changed_text_content.is_none() {
663            return true;
664        }
665        if !self.need_redraw() {
666            return false;
667        }
668
669        // is_gpu_only_property = is the changed CSS property an opacity /
670        // transform / rotate property (which doesn't require to regenerate the display list)
671        if let Some(style_changes) = self.style_changes.as_ref() {
672            !(style_changes.iter().all(|(_, restyle_nodes)| {
673                restyle_nodes.iter().all(|(_, changed_css_properties)| {
674                    changed_css_properties.iter().all(|changed_prop| {
675                        changed_prop.current_prop.get_type().is_gpu_only_property()
676                    })
677                })
678            }))
679        } else {
680            false
681        }
682    }
683
684    pub fn is_empty(&self) -> bool {
685        self.style_changes.is_none()
686            && self.layout_changes.is_none()
687            && self.focus_change.is_none()
688            && self.nodes_that_changed_size.is_none()
689            && self.nodes_that_changed_text_content.is_none()
690            && self.gpu_key_changes.is_none()
691    }
692
693    pub fn need_redraw(&self) -> bool {
694        !(self.style_changes.is_none()
695            && self.layout_changes.is_none()
696            && self.nodes_that_changed_text_content.is_none()
697            && self.nodes_that_changed_size.is_none())
698    }
699}
700
701#[derive(Debug, Clone, PartialEq)]
702pub struct CallbackToCall {
703    pub node_id: NodeId,
704    pub hit_test_item: Option<HitTestItem>,
705    pub event_filter: EventFilter,
706}
707
708#[derive(Debug, Clone)]
709pub struct CallbacksOfHitTest {
710    /// A BTreeMap where each item is already filtered by the proper hit-testing type,
711    /// meaning in order to get the proper callbacks, you simply have to iterate through
712    /// all node IDs
713    pub nodes_with_callbacks: BTreeMap<DomId, Vec<CallbackToCall>>,
714}
715
716impl CallbacksOfHitTest {
717    /// Determine which event / which callback(s) should be called and in which order
718    ///
719    /// This function also updates / mutates the current window states `focused_node`
720    /// as well as the `window_state.previous_state`
721    pub fn new(
722        nodes_to_check: &NodesToCheck,
723        events: &Events,
724        layout_results: &[LayoutResult],
725    ) -> Self {
726        let mut nodes_with_callbacks = BTreeMap::new();
727
728        if events.is_empty() {
729            return Self {
730                nodes_with_callbacks,
731            };
732        }
733
734        let default_map = BTreeMap::new();
735        let mouseenter_filter = EventFilter::Hover(HoverEventFilter::MouseEnter);
736        let mouseleave_filter = EventFilter::Hover(HoverEventFilter::MouseEnter);
737        let focus_received_filter = EventFilter::Focus(FocusEventFilter::FocusReceived);
738        let focus_lost_filter = EventFilter::Focus(FocusEventFilter::FocusLost);
739
740        for (dom_id, layout_result) in layout_results.iter().enumerate() {
741            let dom_id = DomId { inner: dom_id };
742
743            // Insert Window:: event filters
744            let mut window_callbacks_this_dom = layout_result
745                .styled_dom
746                .nodes_with_window_callbacks
747                .iter()
748                .flat_map(|nid| {
749                    let node_id = match nid.into_crate_internal() {
750                        Some(s) => s,
751                        None => return Vec::new(),
752                    };
753                    layout_result.styled_dom.node_data.as_container()[node_id]
754                        .get_callbacks()
755                        .iter()
756                        .filter_map(|cb| match cb.event {
757                            EventFilter::Window(wev) => {
758                                if events.window_events.contains(&wev) {
759                                    Some(CallbackToCall {
760                                        event_filter: EventFilter::Window(wev),
761                                        hit_test_item: None,
762                                        node_id,
763                                    })
764                                } else {
765                                    None
766                                }
767                            }
768                            _ => None,
769                        })
770                        .collect::<Vec<_>>()
771                })
772                .collect::<Vec<_>>();
773
774            // window_callbacks_this_dom now contains all WindowEvent filters
775
776            // insert Hover::MouseEnter events
777            window_callbacks_this_dom.extend(
778                nodes_to_check
779                    .onmouseenter_nodes
780                    .get(&dom_id)
781                    .unwrap_or(&default_map)
782                    .iter()
783                    .filter_map(|(node_id, ht)| {
784                        if layout_result.styled_dom.node_data.as_container()[*node_id]
785                            .get_callbacks()
786                            .iter()
787                            .any(|e| e.event == mouseenter_filter)
788                        {
789                            Some(CallbackToCall {
790                                event_filter: mouseenter_filter.clone(),
791                                hit_test_item: Some(*ht),
792                                node_id: *node_id,
793                            })
794                        } else {
795                            None
796                        }
797                    }),
798            );
799
800            // insert Hover::MouseLeave events
801            window_callbacks_this_dom.extend(
802                nodes_to_check
803                    .onmouseleave_nodes
804                    .get(&dom_id)
805                    .unwrap_or(&default_map)
806                    .iter()
807                    .filter_map(|(node_id, ht)| {
808                        if layout_result.styled_dom.node_data.as_container()[*node_id]
809                            .get_callbacks()
810                            .iter()
811                            .any(|e| e.event == mouseleave_filter)
812                        {
813                            Some(CallbackToCall {
814                                event_filter: mouseleave_filter.clone(),
815                                hit_test_item: Some(*ht),
816                                node_id: *node_id,
817                            })
818                        } else {
819                            None
820                        }
821                    }),
822            );
823
824            // insert other Hover:: events
825            for (nid, ht) in nodes_to_check
826                .new_hit_node_ids
827                .get(&dom_id)
828                .unwrap_or(&default_map)
829                .iter()
830            {
831                for hev in events.hover_events.iter() {
832                    window_callbacks_this_dom.extend(
833                        layout_result.styled_dom.node_data.as_container()[*nid]
834                            .get_callbacks()
835                            .iter()
836                            .filter_map(|e| {
837                                if e.event == EventFilter::Hover(*hev)
838                                    && e.event != mouseenter_filter
839                                    && e.event != mouseleave_filter
840                                {
841                                    Some(CallbackToCall {
842                                        event_filter: EventFilter::Hover(hev.clone()),
843                                        hit_test_item: Some(*ht),
844                                        node_id: *nid,
845                                    })
846                                } else {
847                                    None
848                                }
849                            }),
850                    );
851                }
852            }
853
854            // insert Focus(FocusReceived / FocusLost) event
855            if nodes_to_check.new_focus_node != nodes_to_check.old_focus_node {
856                if let Some(DomNodeId {
857                    dom,
858                    node: az_node_id,
859                }) = nodes_to_check.old_focus_node
860                {
861                    if dom == dom_id {
862                        if let Some(nid) = az_node_id.into_crate_internal() {
863                            if layout_result.styled_dom.node_data.as_container()[nid]
864                                .get_callbacks()
865                                .iter()
866                                .any(|e| e.event == focus_lost_filter)
867                            {
868                                window_callbacks_this_dom.push(CallbackToCall {
869                                    event_filter: focus_lost_filter.clone(),
870                                    hit_test_item: events
871                                        .old_hit_node_ids
872                                        .get(&dom_id)
873                                        .and_then(|map| map.get(&nid))
874                                        .cloned(),
875                                    node_id: nid,
876                                })
877                            }
878                        }
879                    }
880                }
881
882                if let Some(DomNodeId {
883                    dom,
884                    node: az_node_id,
885                }) = nodes_to_check.new_focus_node
886                {
887                    if dom == dom_id {
888                        if let Some(nid) = az_node_id.into_crate_internal() {
889                            if layout_result.styled_dom.node_data.as_container()[nid]
890                                .get_callbacks()
891                                .iter()
892                                .any(|e| e.event == focus_received_filter)
893                            {
894                                window_callbacks_this_dom.push(CallbackToCall {
895                                    event_filter: focus_received_filter.clone(),
896                                    hit_test_item: events
897                                        .old_hit_node_ids
898                                        .get(&dom_id)
899                                        .and_then(|map| map.get(&nid))
900                                        .cloned(),
901                                    node_id: nid,
902                                })
903                            }
904                        }
905                    }
906                }
907            }
908
909            // Insert other Focus: events
910            if let Some(DomNodeId {
911                dom,
912                node: az_node_id,
913            }) = nodes_to_check.new_focus_node
914            {
915                if dom == dom_id {
916                    if let Some(nid) = az_node_id.into_crate_internal() {
917                        for fev in events.focus_events.iter() {
918                            for cb in layout_result.styled_dom.node_data.as_container()[nid]
919                                .get_callbacks()
920                                .iter()
921                            {
922                                if cb.event == EventFilter::Focus(*fev)
923                                    && cb.event != focus_received_filter
924                                    && cb.event != focus_lost_filter
925                                {
926                                    window_callbacks_this_dom.push(CallbackToCall {
927                                        event_filter: EventFilter::Focus(fev.clone()),
928                                        hit_test_item: events
929                                            .old_hit_node_ids
930                                            .get(&dom_id)
931                                            .and_then(|map| map.get(&nid))
932                                            .cloned(),
933                                        node_id: nid,
934                                    })
935                                }
936                            }
937                        }
938                    }
939                }
940            }
941
942            if !window_callbacks_this_dom.is_empty() {
943                nodes_with_callbacks.insert(dom_id, window_callbacks_this_dom);
944            }
945        }
946
947        // Final: insert Not:: event filters
948        for (dom_id, layout_result) in layout_results.iter().enumerate() {
949            let dom_id = DomId { inner: dom_id };
950
951            let not_event_filters = layout_result
952                .styled_dom
953                .nodes_with_not_callbacks
954                .iter()
955                .flat_map(|node_id| {
956                    let node_id = match node_id.into_crate_internal() {
957                        Some(s) => s,
958                        None => return Vec::new(),
959                    };
960                    layout_result.styled_dom.node_data.as_container()[node_id]
961                        .get_callbacks()
962                        .iter()
963                        .filter_map(|cb| match cb.event {
964                            EventFilter::Not(nev) => {
965                                if nodes_with_callbacks.get(&dom_id).map(|v| {
966                                    v.iter().any(|cb| {
967                                        cb.node_id == node_id
968                                            && cb.event_filter == nev.as_event_filter()
969                                    })
970                                }) != Some(true)
971                                {
972                                    Some(CallbackToCall {
973                                        event_filter: EventFilter::Not(nev.clone()),
974                                        hit_test_item: events
975                                            .old_hit_node_ids
976                                            .get(&dom_id)
977                                            .and_then(|map| map.get(&node_id))
978                                            .cloned(),
979                                        node_id,
980                                    })
981                                } else {
982                                    None
983                                }
984                            }
985                            _ => None,
986                        })
987                        .collect::<Vec<_>>()
988                })
989                .collect::<Vec<_>>();
990
991            for cb in not_event_filters {
992                nodes_with_callbacks
993                    .entry(dom_id)
994                    .or_insert_with(|| Vec::new())
995                    .push(cb);
996            }
997        }
998
999        CallbacksOfHitTest {
1000            nodes_with_callbacks,
1001        }
1002    }
1003
1004    /// The actual function that calls the callbacks in their proper hierarchy and order
1005    pub fn call(
1006        &mut self,
1007        previous_window_state: &Option<FullWindowState>,
1008        full_window_state: &FullWindowState,
1009        raw_window_handle: &RawWindowHandle,
1010        scroll_states: &BTreeMap<DomId, BTreeMap<NodeHierarchyItemId, ScrollPosition>>,
1011        gl_context: &OptionGlContextPtr,
1012        layout_results: &mut Vec<LayoutResult>,
1013        modifiable_scroll_states: &mut ScrollStates,
1014        image_cache: &mut ImageCache,
1015        system_fonts: &mut FcFontCache,
1016        system_callbacks: &ExternalSystemCallbacks,
1017        renderer_resources: &RendererResources,
1018    ) -> CallCallbacksResult {
1019        use crate::{
1020            callbacks::CallbackInfo, styled_dom::ParentWithNodeDepth, window::WindowState,
1021        };
1022
1023        let mut ret = CallCallbacksResult {
1024            should_scroll_render: false,
1025            callbacks_update_screen: Update::DoNothing,
1026            modified_window_state: None,
1027            css_properties_changed: None,
1028            words_changed: None,
1029            images_changed: None,
1030            image_masks_changed: None,
1031            nodes_scrolled_in_callbacks: None,
1032            update_focused_node: None,
1033            timers: None,
1034            threads: None,
1035            timers_removed: None,
1036            threads_removed: None,
1037            windows_created: Vec::new(),
1038            cursor_changed: false,
1039        };
1040        let mut new_focus_target = None;
1041
1042        let current_cursor = full_window_state.mouse_state.mouse_cursor_type.clone();
1043
1044        if self.nodes_with_callbacks.is_empty() {
1045            // common case
1046            return ret;
1047        }
1048
1049        let mut ret_modified_window_state: WindowState = full_window_state.clone().into();
1050        let mut ret_modified_window_state_unmodified = ret_modified_window_state.clone();
1051        let mut ret_timers = FastHashMap::new();
1052        let mut ret_timers_removed = FastBTreeSet::new();
1053        let mut ret_threads = FastHashMap::new();
1054        let mut ret_threads_removed = FastBTreeSet::new();
1055        let mut ret_words_changed = BTreeMap::new();
1056        let mut ret_images_changed = BTreeMap::new();
1057        let mut ret_image_masks_changed = BTreeMap::new();
1058        let mut ret_css_properties_changed = BTreeMap::new();
1059        let mut ret_nodes_scrolled_in_callbacks = BTreeMap::new();
1060
1061        {
1062            for (dom_id, callbacks_filter_list) in self.nodes_with_callbacks.iter() {
1063                let mut callbacks = BTreeMap::new();
1064                for cbtc in callbacks_filter_list {
1065                    callbacks
1066                        .entry(cbtc.node_id)
1067                        .or_insert_with(|| Vec::new())
1068                        .push((cbtc.hit_test_item, cbtc.event_filter));
1069                }
1070                let callbacks = callbacks;
1071                let mut empty_vec = Vec::new();
1072                let lr = match layout_results.get(dom_id.inner) {
1073                    Some(s) => s,
1074                    None => continue,
1075                };
1076
1077                let mut blacklisted_event_types = BTreeSet::new();
1078
1079                // Run all callbacks (front to back)
1080                for ParentWithNodeDepth { depth: _, node_id } in
1081                    lr.styled_dom.non_leaf_nodes.as_ref().iter().rev()
1082                {
1083                    let parent_node_id = node_id;
1084                    for child_id in parent_node_id
1085                        .into_crate_internal()
1086                        .unwrap()
1087                        .az_children(&lr.styled_dom.node_hierarchy.as_container())
1088                    {
1089                        for (hit_test_item, event_filter) in
1090                            callbacks.get(&child_id).unwrap_or(&empty_vec)
1091                        {
1092                            if blacklisted_event_types.contains(&*event_filter) {
1093                                continue;
1094                            }
1095
1096                            let mut new_focus = None;
1097                            let mut stop_propagation = false;
1098
1099                            let mut callback_info = CallbackInfo::new(
1100                                /* layout_results: */ &layout_results,
1101                                /* renderer_resources: */ renderer_resources,
1102                                /* previous_window_state: */ &previous_window_state,
1103                                /* current_window_state: */ &full_window_state,
1104                                /* modifiable_window_state: */
1105                                &mut ret_modified_window_state,
1106                                /* gl_context, */ gl_context,
1107                                /* image_cache, */ image_cache,
1108                                /* system_fonts, */ system_fonts,
1109                                /* timers: */ &mut ret_timers,
1110                                /* threads: */ &mut ret_threads,
1111                                /* timers_removed: */ &mut ret_timers_removed,
1112                                /* threads_removed: */ &mut ret_threads_removed,
1113                                /* current_window_handle: */ raw_window_handle,
1114                                /* new_windows: */ &mut ret.windows_created,
1115                                /* system_callbacks */ system_callbacks,
1116                                /* stop_propagation: */ &mut stop_propagation,
1117                                /* focus_target: */ &mut new_focus,
1118                                /* words_changed_in_callbacks: */ &mut ret_words_changed,
1119                                /* images_changed_in_callbacks: */ &mut ret_images_changed,
1120                                /* image_masks_changed_in_callbacks: */
1121                                &mut ret_image_masks_changed,
1122                                /* css_properties_changed_in_callbacks: */
1123                                &mut ret_css_properties_changed,
1124                                /* current_scroll_states: */ scroll_states,
1125                                /* nodes_scrolled_in_callback: */
1126                                &mut ret_nodes_scrolled_in_callbacks,
1127                                /* hit_dom_node: */
1128                                DomNodeId {
1129                                    dom: *dom_id,
1130                                    node: NodeHierarchyItemId::from_crate_internal(Some(child_id)),
1131                                },
1132                                /* cursor_relative_to_item: */
1133                                hit_test_item
1134                                    .as_ref()
1135                                    .map(|hi| hi.point_relative_to_item)
1136                                    .into(),
1137                                /* cursor_in_viewport: */
1138                                hit_test_item.as_ref().map(|hi| hi.point_in_viewport).into(),
1139                            );
1140
1141                            let callback_return = {
1142                                // get a MUTABLE reference to the RefAny inside of the DOM
1143                                let node_data_container = lr.styled_dom.node_data.as_container();
1144                                if let Some(callback_data) =
1145                                    node_data_container.get(child_id).and_then(|nd| {
1146                                        nd.callbacks
1147                                            .as_ref()
1148                                            .iter()
1149                                            .find(|i| i.event == *event_filter)
1150                                    })
1151                                {
1152                                    let mut callback_data_clone = callback_data.clone();
1153                                    // Invoke callback
1154                                    (callback_data_clone.callback.cb)(
1155                                        &mut callback_data_clone.data,
1156                                        &mut callback_info,
1157                                    )
1158                                } else {
1159                                    Update::DoNothing
1160                                }
1161                            };
1162
1163                            ret.callbacks_update_screen.max_self(callback_return);
1164
1165                            if let Some(new_focus) = new_focus.clone() {
1166                                new_focus_target = Some(new_focus);
1167                            }
1168
1169                            if stop_propagation {
1170                                blacklisted_event_types.insert(event_filter.clone());
1171                            }
1172                        }
1173                    }
1174                }
1175
1176                // run the callbacks for node ID 0
1177                loop {
1178                    for ((hit_test_item, event_filter), root_id) in lr
1179                        .styled_dom
1180                        .root
1181                        .into_crate_internal()
1182                        .map(|root_id| {
1183                            callbacks
1184                                .get(&root_id)
1185                                .unwrap_or(&empty_vec)
1186                                .iter()
1187                                .map(|item| (item, root_id))
1188                                .collect::<Vec<_>>()
1189                        })
1190                        .unwrap_or_default()
1191                    {
1192                        if blacklisted_event_types.contains(&event_filter) {
1193                            break; // break out of loop
1194                        }
1195
1196                        let mut new_focus = None;
1197                        let mut stop_propagation = false;
1198
1199                        let mut callback_info = CallbackInfo::new(
1200                            /* layout_results: */ &layout_results,
1201                            /* renderer_resources: */ renderer_resources,
1202                            /* previous_window_state: */ &previous_window_state,
1203                            /* current_window_state: */ &full_window_state,
1204                            /* modifiable_window_state: */ &mut ret_modified_window_state,
1205                            /* gl_context, */ gl_context,
1206                            /* image_cache, */ image_cache,
1207                            /* system_fonts, */ system_fonts,
1208                            /* timers: */ &mut ret_timers,
1209                            /* threads: */ &mut ret_threads,
1210                            /* timers_removed: */ &mut ret_timers_removed,
1211                            /* threads_removed: */ &mut ret_threads_removed,
1212                            /* current_window_handle: */ raw_window_handle,
1213                            /* new_windows: */ &mut ret.windows_created,
1214                            /* system_callbacks */ system_callbacks,
1215                            /* stop_propagation: */ &mut stop_propagation,
1216                            /* focus_target: */ &mut new_focus,
1217                            /* words_changed_in_callbacks: */ &mut ret_words_changed,
1218                            /* images_changed_in_callbacks: */ &mut ret_images_changed,
1219                            /* image_masks_changed_in_callbacks: */
1220                            &mut ret_image_masks_changed,
1221                            /* css_properties_changed_in_callbacks: */
1222                            &mut ret_css_properties_changed,
1223                            /* current_scroll_states: */ scroll_states,
1224                            /* nodes_scrolled_in_callback: */
1225                            &mut ret_nodes_scrolled_in_callbacks,
1226                            /* hit_dom_node: */
1227                            DomNodeId {
1228                                dom: *dom_id,
1229                                node: NodeHierarchyItemId::from_crate_internal(Some(root_id)),
1230                            },
1231                            /* cursor_relative_to_item: */
1232                            hit_test_item
1233                                .as_ref()
1234                                .map(|hi| hi.point_relative_to_item)
1235                                .into(),
1236                            /* cursor_in_viewport: */
1237                            hit_test_item.as_ref().map(|hi| hi.point_in_viewport).into(),
1238                        );
1239
1240                        let callback_return = {
1241                            // get a MUTABLE reference to the RefAny inside of the DOM
1242                            let node_data_container = lr.styled_dom.node_data.as_container();
1243                            if let Some(callback_data) =
1244                                node_data_container.get(root_id).and_then(|nd| {
1245                                    nd.callbacks
1246                                        .as_ref()
1247                                        .iter()
1248                                        .find(|i| i.event == *event_filter)
1249                                })
1250                            {
1251                                // Invoke callback
1252                                let mut callback_data_clone = callback_data.clone();
1253                                (callback_data_clone.callback.cb)(
1254                                    &mut callback_data_clone.data,
1255                                    &mut callback_info,
1256                                )
1257                            } else {
1258                                Update::DoNothing
1259                            }
1260                        };
1261
1262                        ret.callbacks_update_screen.max_self(callback_return);
1263
1264                        if let Some(new_focus) = new_focus.clone() {
1265                            new_focus_target = Some(new_focus);
1266                        }
1267
1268                        if stop_propagation {
1269                            blacklisted_event_types.insert(event_filter.clone());
1270                        }
1271                    }
1272
1273                    break;
1274                }
1275            }
1276        }
1277
1278        // Scroll nodes from programmatic callbacks
1279        for (dom_id, callback_scrolled_nodes) in ret_nodes_scrolled_in_callbacks.iter() {
1280            let scrollable_nodes = &layout_results[dom_id.inner].scrollable_nodes;
1281            for (scroll_node_id, scroll_position) in callback_scrolled_nodes.iter() {
1282                let scroll_node = match scrollable_nodes.overflowing_nodes.get(&scroll_node_id) {
1283                    Some(s) => s,
1284                    None => continue,
1285                };
1286
1287                modifiable_scroll_states.set_scroll_position(&scroll_node, *scroll_position);
1288                ret.should_scroll_render = true;
1289            }
1290        }
1291
1292        // Resolve the new focus target
1293        if let Some(ft) = new_focus_target {
1294            if let Ok(new_focus_node) = ft.resolve(&layout_results, full_window_state.focused_node)
1295            {
1296                ret.update_focused_node = Some(new_focus_node);
1297            }
1298        }
1299
1300        if current_cursor != ret_modified_window_state.mouse_state.mouse_cursor_type {
1301            ret.cursor_changed = true;
1302        }
1303
1304        if !ret_timers.is_empty() {
1305            ret.timers = Some(ret_timers);
1306        }
1307        if !ret_threads.is_empty() {
1308            ret.threads = Some(ret_threads);
1309        }
1310        if ret_modified_window_state != ret_modified_window_state_unmodified {
1311            ret.modified_window_state = Some(ret_modified_window_state);
1312        }
1313        if !ret_threads_removed.is_empty() {
1314            ret.threads_removed = Some(ret_threads_removed);
1315        }
1316        if !ret_timers_removed.is_empty() {
1317            ret.timers_removed = Some(ret_timers_removed);
1318        }
1319        if !ret_words_changed.is_empty() {
1320            ret.words_changed = Some(ret_words_changed);
1321        }
1322        if !ret_images_changed.is_empty() {
1323            ret.images_changed = Some(ret_images_changed);
1324        }
1325        if !ret_image_masks_changed.is_empty() {
1326            ret.image_masks_changed = Some(ret_image_masks_changed);
1327        }
1328        if !ret_css_properties_changed.is_empty() {
1329            ret.css_properties_changed = Some(ret_css_properties_changed);
1330        }
1331        if !ret_nodes_scrolled_in_callbacks.is_empty() {
1332            ret.nodes_scrolled_in_callbacks = Some(ret_nodes_scrolled_in_callbacks);
1333        }
1334
1335        ret
1336    }
1337}
1338
1339fn get_window_events(
1340    current_window_state: &FullWindowState,
1341    previous_window_state: &Option<FullWindowState>,
1342) -> Vec<WindowEventFilter> {
1343    use crate::window::{CursorPosition::*, WindowPosition};
1344
1345    let mut events = Vec::new();
1346
1347    let previous_window_state = match previous_window_state.as_ref() {
1348        Some(s) => s,
1349        None => return events,
1350    };
1351
1352    // match mouse move events first since they are the most common
1353
1354    match (
1355        previous_window_state.mouse_state.cursor_position,
1356        current_window_state.mouse_state.cursor_position,
1357    ) {
1358        (InWindow(_), OutOfWindow(_)) | (InWindow(_), Uninitialized) => {
1359            events.push(WindowEventFilter::MouseLeave);
1360        }
1361        (OutOfWindow(_), InWindow(_)) | (Uninitialized, InWindow(_)) => {
1362            events.push(WindowEventFilter::MouseEnter);
1363        }
1364        (InWindow(a), InWindow(b)) => {
1365            if a != b {
1366                events.push(WindowEventFilter::MouseOver);
1367            }
1368        }
1369        _ => {}
1370    }
1371
1372    if current_window_state.mouse_state.mouse_down()
1373        && !previous_window_state.mouse_state.mouse_down()
1374    {
1375        events.push(WindowEventFilter::MouseDown);
1376    }
1377
1378    if current_window_state.mouse_state.left_down && !previous_window_state.mouse_state.left_down {
1379        events.push(WindowEventFilter::LeftMouseDown);
1380    }
1381
1382    if current_window_state.mouse_state.right_down && !previous_window_state.mouse_state.right_down
1383    {
1384        events.push(WindowEventFilter::RightMouseDown);
1385    }
1386
1387    if current_window_state.mouse_state.middle_down
1388        && !previous_window_state.mouse_state.middle_down
1389    {
1390        events.push(WindowEventFilter::MiddleMouseDown);
1391    }
1392
1393    if previous_window_state.mouse_state.mouse_down()
1394        && !current_window_state.mouse_state.mouse_down()
1395    {
1396        events.push(WindowEventFilter::MouseUp);
1397    }
1398
1399    if previous_window_state.mouse_state.left_down && !current_window_state.mouse_state.left_down {
1400        events.push(WindowEventFilter::LeftMouseUp);
1401    }
1402
1403    if previous_window_state.mouse_state.right_down && !current_window_state.mouse_state.right_down
1404    {
1405        events.push(WindowEventFilter::RightMouseUp);
1406    }
1407
1408    if previous_window_state.mouse_state.middle_down
1409        && !current_window_state.mouse_state.middle_down
1410    {
1411        events.push(WindowEventFilter::MiddleMouseUp);
1412    }
1413
1414    // resize, move, close events
1415
1416    if current_window_state.flags.has_focus != previous_window_state.flags.has_focus {
1417        if current_window_state.flags.has_focus {
1418            events.push(WindowEventFilter::FocusReceived);
1419            events.push(WindowEventFilter::WindowFocusReceived);
1420        } else {
1421            events.push(WindowEventFilter::FocusLost);
1422            events.push(WindowEventFilter::WindowFocusLost);
1423        }
1424    }
1425
1426    if current_window_state.size.dimensions != previous_window_state.size.dimensions
1427        || current_window_state.size.dpi != previous_window_state.size.dpi
1428    {
1429        events.push(WindowEventFilter::Resized);
1430    }
1431
1432    match (
1433        current_window_state.position,
1434        previous_window_state.position,
1435    ) {
1436        (WindowPosition::Initialized(cur_pos), WindowPosition::Initialized(prev_pos)) => {
1437            if prev_pos != cur_pos {
1438                events.push(WindowEventFilter::Moved);
1439            }
1440        }
1441        (WindowPosition::Initialized(_), WindowPosition::Uninitialized) => {
1442            events.push(WindowEventFilter::Moved);
1443        }
1444        _ => {}
1445    }
1446
1447    let about_to_close_equals = current_window_state.flags.is_about_to_close
1448        == previous_window_state.flags.is_about_to_close;
1449    if current_window_state.flags.is_about_to_close && !about_to_close_equals {
1450        events.push(WindowEventFilter::CloseRequested);
1451    }
1452
1453    // scroll events
1454
1455    let is_scroll_previous = previous_window_state.mouse_state.scroll_x.is_some()
1456        || previous_window_state.mouse_state.scroll_y.is_some();
1457
1458    let is_scroll_now = current_window_state.mouse_state.scroll_x.is_some()
1459        || current_window_state.mouse_state.scroll_y.is_some();
1460
1461    if !is_scroll_previous && is_scroll_now {
1462        events.push(WindowEventFilter::ScrollStart);
1463    }
1464
1465    if is_scroll_now {
1466        events.push(WindowEventFilter::Scroll);
1467    }
1468
1469    if is_scroll_previous && !is_scroll_now {
1470        events.push(WindowEventFilter::ScrollEnd);
1471    }
1472
1473    // keyboard events
1474    let cur_vk_equal = current_window_state.keyboard_state.current_virtual_keycode
1475        == previous_window_state.keyboard_state.current_virtual_keycode;
1476    let cur_char_equal = current_window_state.keyboard_state.current_char
1477        == previous_window_state.keyboard_state.current_char;
1478
1479    if !cur_vk_equal
1480        && previous_window_state
1481            .keyboard_state
1482            .current_virtual_keycode
1483            .is_none()
1484        && current_window_state
1485            .keyboard_state
1486            .current_virtual_keycode
1487            .is_some()
1488    {
1489        events.push(WindowEventFilter::VirtualKeyDown);
1490    }
1491
1492    if !cur_char_equal && current_window_state.keyboard_state.current_char.is_some() {
1493        events.push(WindowEventFilter::TextInput);
1494    }
1495
1496    if !cur_vk_equal
1497        && previous_window_state
1498            .keyboard_state
1499            .current_virtual_keycode
1500            .is_some()
1501        && current_window_state
1502            .keyboard_state
1503            .current_virtual_keycode
1504            .is_none()
1505    {
1506        events.push(WindowEventFilter::VirtualKeyUp);
1507    }
1508
1509    // misc events
1510
1511    let hovered_file_equals =
1512        previous_window_state.hovered_file == current_window_state.hovered_file;
1513    if previous_window_state.hovered_file.is_none()
1514        && current_window_state.hovered_file.is_some()
1515        && !hovered_file_equals
1516    {
1517        events.push(WindowEventFilter::HoveredFile);
1518    }
1519
1520    if previous_window_state.hovered_file.is_some() && current_window_state.hovered_file.is_none() {
1521        if current_window_state.dropped_file.is_some() {
1522            events.push(WindowEventFilter::DroppedFile);
1523        } else {
1524            events.push(WindowEventFilter::HoveredFileCancelled);
1525        }
1526    }
1527
1528    if current_window_state.theme != previous_window_state.theme {
1529        events.push(WindowEventFilter::ThemeChanged);
1530    }
1531
1532    events
1533}
1534
1535fn get_hover_events(input: &[WindowEventFilter]) -> Vec<HoverEventFilter> {
1536    input
1537        .iter()
1538        .filter_map(|window_event| window_event.to_hover_event_filter())
1539        .collect()
1540}
1541
1542fn get_focus_events(input: &[HoverEventFilter]) -> Vec<FocusEventFilter> {
1543    input
1544        .iter()
1545        .filter_map(|hover_event| hover_event.to_focus_event_filter())
1546        .collect()
1547}