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
#![allow(clippy::type_complexity)]
use dioxus_native_core::NodeId;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::events::{does_event_move_cursor, DomEvent, FreyaEvent};
/// [`ElementsState`] stores the elements states given incoming events.
#[derive(Default)]
pub struct ElementsState {
hovered_elements: FxHashSet<NodeId>,
}
impl ElementsState {
/// Update the Element states given the new events
pub fn process_events(
&mut self,
events_to_emit: &[DomEvent],
events: &[FreyaEvent],
) -> (FxHashMap<String, Vec<(NodeId, FreyaEvent)>>, Vec<DomEvent>) {
let mut new_events_to_emit = Vec::default();
let mut new_events = FxHashMap::<String, Vec<(NodeId, FreyaEvent)>>::default();
let recent_mouse_movement_event = any_recent_mouse_movement(events);
// Suggest emitting `mouseleave` in elements not being hovered anymore
self.hovered_elements.retain(|node_id| {
let no_recent_mouse_movement_on_me =
has_node_been_hovered_recently(events_to_emit, node_id);
if no_recent_mouse_movement_on_me {
if let Some(FreyaEvent::Mouse { cursor, button, .. }) = recent_mouse_movement_event
{
let events = new_events.entry("mouseleave".to_string()).or_default();
events.push((
*node_id,
FreyaEvent::Mouse {
name: "mouseleave".to_string(),
cursor,
button,
},
));
// Remove the node from the list of hovered elements
return false;
}
}
true
});
// All these events will mark the node as being hovered
// "mouseover" "mouseenter" "pointerover" "pointerenter"
// We clone this here so events emitted in the same batch that mark an element as hovered will not affect the other events
let hovered_elements = self.hovered_elements.clone();
// Emit valid events
for event in events_to_emit {
let id = &event.node_id;
let should_trigger = match event.name.as_str() {
name @ "mouseover"
| name @ "mouseenter"
| name @ "pointerover"
| name @ "pointerenter" => {
let is_hovered = hovered_elements.contains(id);
if !is_hovered {
self.hovered_elements.insert(*id);
}
if name == "mouseenter" || name == "pointerenter" {
// If the event is already being hovered then it's pointless to trigger the movement event
!is_hovered
} else {
true
}
}
_ => true,
};
if should_trigger {
new_events_to_emit.push(event.clone());
}
}
// Update the internal states of elements given the events
// e.g `mouseover` will mark the element as hovered.
for event in events_to_emit {
let id = &event.node_id;
if does_event_move_cursor(event.name.as_str()) && !self.hovered_elements.contains(id) {
self.hovered_elements.insert(*id);
}
}
(new_events, new_events_to_emit)
}
}
fn any_recent_mouse_movement(events: &[FreyaEvent]) -> Option<FreyaEvent> {
events
.iter()
.find(|event| {
if let FreyaEvent::Mouse { name, .. } = event {
does_event_move_cursor(name)
} else {
false
}
})
.cloned()
}
fn has_node_been_hovered_recently(events_to_emit: &[DomEvent], element: &NodeId) -> bool {
events_to_emit
.iter()
.find_map(|event| {
if event.does_move_cursor() && &event.node_id == element {
Some(false)
} else {
None
}
})
.unwrap_or(true)
}