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