freya_core/events/
events_measurer.rs

1use freya_engine::prelude::*;
2use freya_native_core::{
3    real_dom::NodeImmutable,
4    tree::TreeRef,
5    NodeId,
6};
7use itertools::sorted;
8
9use super::{
10    PlatformEventData,
11    PotentialEvent,
12};
13pub use crate::events::{
14    DomEvent,
15    NodesState,
16    PlatformEvent,
17};
18use crate::{
19    dom::{
20        DioxusDOM,
21        FreyaDOM,
22    },
23    elements::{
24        ElementUtils,
25        ElementUtilsResolver,
26    },
27    states::{
28        StyleState,
29        ViewportState,
30    },
31    types::{
32        EventEmitter,
33        EventsQueue,
34        PotentialEvents,
35    },
36    values::Fill,
37};
38
39/// Process the events and emit them to the VirtualDOM
40pub fn process_events(
41    fdom: &FreyaDOM,
42    events: &mut EventsQueue,
43    event_emitter: &EventEmitter,
44    nodes_state: &mut NodesState,
45    scale_factor: f64,
46
47    focus_id: Option<NodeId>,
48) {
49    // Get potential events that could be emitted based on the elements layout and viewports
50    let potential_events = measure_potential_event_listeners(events, fdom, scale_factor, focus_id);
51
52    // Get what events can be actually emitted based on what elements are listening
53    let mut dom_events = measure_dom_events(&potential_events, fdom, scale_factor);
54
55    // Get potential collateral events, e.g. mousemove -> mouseenter
56    let potential_collateral_events =
57        nodes_state.process_collateral(fdom, &potential_events, &mut dom_events, events);
58
59    // Get what collateral events can actually be emitted
60    let collateral_dom_events =
61        measure_dom_events(&potential_collateral_events, fdom, scale_factor);
62
63    // Get the global events
64    measure_platform_global_events(fdom, events, &mut dom_events, scale_factor);
65
66    // Join all the dom events and sort them
67    dom_events.extend(collateral_dom_events);
68    dom_events.sort_unstable();
69
70    // Send all the events
71    event_emitter.send(dom_events).unwrap();
72
73    // Clear the events queue
74    events.clear();
75}
76
77/// For every event in the queue, a global event is created
78pub fn measure_platform_global_events(
79    fdom: &FreyaDOM,
80    events: &EventsQueue,
81    dom_events: &mut Vec<DomEvent>,
82    scale_factor: f64,
83) {
84    let rdom = fdom.rdom();
85    for PlatformEvent { name, data } in events {
86        let derived_events_names = name.get_derived_events();
87
88        for derived_event_name in derived_events_names {
89            let Some(global_name) = derived_event_name.get_global_event() else {
90                continue;
91            };
92
93            let listeners = rdom.get_listeners(&global_name);
94
95            for listener in listeners {
96                let event = DomEvent::new(
97                    PotentialEvent {
98                        node_id: listener.id(),
99                        layer: None,
100                        name: global_name,
101                        data: data.clone(),
102                    },
103                    None,
104                    scale_factor,
105                );
106                dom_events.push(event)
107            }
108        }
109    }
110}
111
112/// Measure what event listeners could potentially be triggered
113pub fn measure_potential_event_listeners(
114    events: &EventsQueue,
115    fdom: &FreyaDOM,
116    scale_factor: f64,
117    focus_id: Option<NodeId>,
118) -> PotentialEvents {
119    let mut potential_events = PotentialEvents::default();
120
121    let layout = fdom.layout();
122    let rdom = fdom.rdom();
123    let layers = fdom.layers();
124
125    // Walk layer by layer from the bottom to the top
126    for (layer, layer_nodes) in sorted(layers.iter()) {
127        for node_id in layer_nodes.iter() {
128            let Some(layout_node) = layout.get(*node_id) else {
129                continue;
130            };
131            'events: for PlatformEvent { name, data } in events {
132                let cursor = match data {
133                    PlatformEventData::Mouse { cursor, .. } => cursor,
134                    PlatformEventData::Wheel { cursor, .. } => cursor,
135                    PlatformEventData::Touch { location, .. } => location,
136                    PlatformEventData::File { cursor, .. } => cursor,
137                    PlatformEventData::Keyboard { .. } if focus_id == Some(*node_id) => {
138                        let potential_event = PotentialEvent {
139                            node_id: *node_id,
140                            layer: Some(*layer),
141                            name: *name,
142                            data: data.clone(),
143                        };
144                        potential_events
145                            .entry(*name)
146                            .or_default()
147                            .push(potential_event);
148                        continue;
149                    }
150                    _ => continue,
151                };
152
153                let node = rdom.get(*node_id).unwrap();
154                let node_type = node.node_type();
155
156                let Some(element_utils) = node_type.tag().and_then(|tag| tag.utils()) else {
157                    continue;
158                };
159
160                // Make sure the cursor is inside the node area
161                if !element_utils.is_point_inside_area(
162                    cursor,
163                    &node,
164                    layout_node,
165                    scale_factor as f32,
166                ) {
167                    continue;
168                }
169
170                let node = rdom.get(*node_id).unwrap();
171                let node_viewports = node.get::<ViewportState>().unwrap();
172
173                // Make sure the cursor is inside all the inherited viewports of the node
174                for node_id in &node_viewports.viewports {
175                    let node_ref = rdom.get(*node_id).unwrap();
176                    let node_type = node_ref.node_type();
177                    let Some(element_utils) = node_type.tag().and_then(|tag| tag.utils()) else {
178                        continue;
179                    };
180                    let layout_node = layout.get(*node_id).unwrap();
181                    if !element_utils.is_point_inside_area(
182                        cursor,
183                        &node_ref,
184                        layout_node,
185                        scale_factor as f32,
186                    ) {
187                        continue 'events;
188                    }
189                }
190
191                let potential_event = PotentialEvent {
192                    node_id: *node_id,
193                    layer: Some(*layer),
194                    name: *name,
195                    data: data.clone(),
196                };
197
198                potential_events
199                    .entry(*name)
200                    .or_insert_with(Vec::new)
201                    .push(potential_event);
202            }
203        }
204    }
205
206    potential_events
207}
208
209pub fn is_node_parent_of(rdom: &DioxusDOM, node: NodeId, parent_node: NodeId) -> bool {
210    let mut head = Some(node);
211    while let Some(id) = head.take() {
212        let tree = rdom.tree_ref();
213        if let Some(parent_id) = tree.parent_id(id) {
214            if parent_id == parent_node {
215                return true;
216            }
217
218            head = Some(parent_id)
219        }
220    }
221    false
222}
223
224/// Measure what DOM events could be emitted
225fn measure_dom_events(
226    potential_events: &PotentialEvents,
227    fdom: &FreyaDOM,
228    scale_factor: f64,
229) -> Vec<DomEvent> {
230    let mut dom_events = Vec::new();
231    let rdom = fdom.rdom();
232    let layout = fdom.layout();
233
234    for (event_name, event_nodes) in potential_events {
235        // Get the derived events, but exclude globals like some file events
236        let derived_events = event_name
237            .get_derived_events()
238            .into_iter()
239            .filter(|event| !event.is_global());
240
241        // Iterate over the derived events (including the source)
242        'event: for derived_event in derived_events {
243            let mut child_node: Option<NodeId> = None;
244
245            // Iterate over the potential events in reverse so the ones in higher layers appeat first
246            for PotentialEvent {
247                node_id,
248                data,
249                name,
250                layer,
251            } in event_nodes.iter().rev()
252            {
253                let Some(node) = rdom.get(*node_id) else {
254                    continue;
255                };
256
257                if let Some(child_node) = child_node {
258                    if !is_node_parent_of(rdom, child_node, *node_id) {
259                        continue;
260                    }
261                }
262
263                if rdom.is_node_listening(node_id, &derived_event) {
264                    let potential_event = PotentialEvent {
265                        node_id: *node_id,
266                        name: derived_event,
267                        data: data.clone(),
268                        layer: *layer,
269                    };
270
271                    let layout_node = layout.get(*node_id).unwrap();
272                    let dom_event = DomEvent::new(
273                        potential_event,
274                        Some(layout_node.visible_area()),
275                        scale_factor,
276                    );
277                    dom_events.push(dom_event);
278
279                    // Events that bubble will only be emitted once
280                    // Those that don't will be stacked
281                    if name.does_bubble() {
282                        continue 'event;
283                    }
284                }
285
286                let StyleState { background, .. } = &*node.get::<StyleState>().unwrap();
287
288                if background != &Fill::Color(Color::TRANSPARENT) && !name.does_go_through_solid() {
289                    // If the background isn't transparent,
290                    // we must make sure that next nodes are parent of it
291                    // This only matters for events that bubble up (e.g. cursor click events)
292                    child_node = Some(*node_id);
293                }
294            }
295        }
296    }
297
298    dom_events
299}