Skip to main content

ragnarok/
nodes_state.rs

1use rustc_hash::{
2    FxHashMap,
3    FxHashSet,
4};
5
6use crate::{
7    EmmitableEvent,
8    EventsMeasurer,
9    NameOfEvent,
10    NodeKey,
11    PotentialEvent,
12    SourceEvent,
13};
14
15/// [`NodesState`] stores the nodes states given incoming events.
16pub struct NodesState<Key: NodeKey> {
17    pressed_nodes: FxHashSet<Key>,
18    hovered_nodes: FxHashSet<Key>,
19    entered_node: Option<Key>,
20}
21
22impl<Key: NodeKey> Default for NodesState<Key> {
23    fn default() -> Self {
24        Self {
25            pressed_nodes: FxHashSet::default(),
26            hovered_nodes: FxHashSet::default(),
27            entered_node: None,
28        }
29    }
30}
31
32pub type PotentialEvents<Key, Name, Source> =
33    FxHashMap<Name, Vec<PotentialEvent<Key, Name, Source>>>;
34
35impl<Key: NodeKey> NodesState<Key> {
36    /// Retain node states given the [EmmitableEvent]s, emitting leave events as side effects.
37    pub(crate) fn retain_states<
38        Emmitable: EmmitableEvent<Key = Key, Name = Name>,
39        Name: NameOfEvent,
40        Source: SourceEvent,
41    >(
42        &mut self,
43        events_measurer: &impl EventsMeasurer<
44            Key = Key,
45            Name = Name,
46            Emmitable = Emmitable,
47            Source = Source,
48        >,
49        emmitable_events: &[Emmitable],
50        source_events: &[Source],
51    ) -> Vec<Emmitable> {
52        let mut collateral_emmitable_events = Vec::default();
53
54        let source_press_event = source_events.iter().any(|e| e.is_pressed());
55
56        #[allow(unused_variables)]
57        self.pressed_nodes.retain(|node_key| {
58            let emmitable_press_event = emmitable_events
59                .iter()
60                .any(|event| event.name().is_pressed() && &event.key() == node_key);
61
62            // A press occurred but not on this node, so it's no longer pressed
63            if !emmitable_press_event && source_press_event {
64                #[cfg(debug_assertions)]
65                tracing::info!("Unmarked as pressed {:?}", node_key);
66
67                return false;
68            }
69
70            true
71        });
72
73        let source_movement_event = source_events.iter().find(|e| e.is_moved());
74        let mut removed_from_hovered = FxHashSet::default();
75
76        self.hovered_nodes.retain(|node_key| {
77            let Some(area) = events_measurer.try_area_of(node_key) else {
78                removed_from_hovered.insert(*node_key);
79                return false;
80            };
81
82            let cursor_still_inside = source_movement_event
83                .and_then(|e| e.try_location())
84                .is_none_or(|cursor| events_measurer.is_point_inside(node_key, cursor));
85
86            if cursor_still_inside {
87                return true;
88            }
89
90            // Cursor moved outside this node, emit leave events
91            let source_event = source_movement_event.unwrap();
92            for derived_event in Name::new_leave().get_derived_events() {
93                if events_measurer.is_listening_to(node_key, &derived_event) {
94                    collateral_emmitable_events.push(events_measurer.new_emmitable_event(
95                        *node_key,
96                        derived_event,
97                        source_event.clone(),
98                        Some(area),
99                    ));
100                }
101            }
102
103            #[cfg(debug_assertions)]
104            tracing::info!("Unmarked as hovered {:?}", node_key);
105
106            removed_from_hovered.insert(*node_key);
107
108            false
109        });
110
111        // Emit exclusive leave when the deepest node changes
112        // but the old node is still hovered (otherwise the regular leave covers it).
113        if let Some(source_event) = source_movement_event {
114            let new_deepest = emmitable_events
115                .iter()
116                .find(|e| e.name().is_exclusive_enter())
117                .map(|e| e.key());
118
119            if let Some(old_entered) = self.entered_node {
120                let deepest_changed = new_deepest != Some(old_entered);
121                let still_hovered = !removed_from_hovered.contains(&old_entered);
122
123                if deepest_changed && still_hovered {
124                    let exclusive_leave = Name::new_exclusive_leave();
125                    if events_measurer.is_listening_to(&old_entered, &exclusive_leave)
126                        && let Some(area) = events_measurer.try_area_of(&old_entered)
127                    {
128                        collateral_emmitable_events.push(events_measurer.new_emmitable_event(
129                            old_entered,
130                            exclusive_leave,
131                            source_event.clone(),
132                            Some(area),
133                        ));
134                    }
135                }
136            }
137        }
138
139        collateral_emmitable_events
140    }
141
142    pub(crate) fn filter_emmitable_events<
143        Emmitable: EmmitableEvent<Key = Key, Name = Name>,
144        Name: NameOfEvent,
145    >(
146        &mut self,
147        emmitable_events: &mut Vec<Emmitable>,
148    ) {
149        let entered_node = emmitable_events
150            .iter()
151            .rev()
152            .find(|e| e.name().is_moved() || e.name().is_exclusive_enter())
153            .map(|e| e.key());
154
155        emmitable_events.retain(|ev| match ev.name() {
156            // Deduplicate exclusive enter against `entered_node`
157            _ if ev.name().is_exclusive_enter() => {
158                entered_node.as_ref() == Some(&ev.key()) && entered_node != self.entered_node
159            }
160            // Deduplicate non-exclusive enter against `hovered_nodes`
161            _ if ev.name().is_enter() => !self.hovered_nodes.contains(&ev.key()),
162            // Only emit release events for already-pressed nodes
163            _ if ev.name().is_released() => self.pressed_nodes.contains(&ev.key()),
164            _ => true,
165        });
166
167        self.entered_node = entered_node;
168    }
169
170    /// Create the nodes states given the [PotentialEvent]s.
171    pub fn create_update<Name: NameOfEvent, Source: SourceEvent>(
172        &self,
173        events_measurer: &impl EventsMeasurer<Key = Key, Name = Name>,
174        potential_events: &PotentialEvents<Key, Name, Source>,
175    ) -> NodesStatesUpdate<Key> {
176        let mut hovered_nodes = FxHashSet::default();
177        let mut pressed_nodes = FxHashSet::default();
178
179        for events in potential_events.values() {
180            let mut child_node: Option<Key> = None;
181
182            for PotentialEvent { node_key, name, .. } in events.iter().rev() {
183                if let Some(child_node) = child_node
184                    && !events_measurer.is_node_parent_of(&child_node, *node_key)
185                {
186                    continue;
187                }
188
189                // If the background isn't transparent,
190                // we must make sure that next nodes are parent of it.
191                // This only matters for events that don't go through solids (e.g. cursor click events)
192                if !events_measurer.is_node_transparent(node_key) && !name.does_go_through_solid() {
193                    child_node = Some(*node_key);
194                }
195
196                match name {
197                    name if name.is_moved() => {
198                        hovered_nodes.insert(*node_key);
199
200                        #[cfg(debug_assertions)]
201                        tracing::info!("Marked as hovered {:?}", node_key);
202                    }
203                    name if name.is_pressed() => {
204                        pressed_nodes.insert(*node_key);
205
206                        #[cfg(debug_assertions)]
207                        tracing::info!("Marked as pressed {:?}", node_key);
208                    }
209                    _ => {}
210                }
211            }
212        }
213
214        NodesStatesUpdate {
215            pressed_nodes,
216            hovered_nodes,
217        }
218    }
219
220    /// Apply the given [NodesStatesUpdate], extending the cached hovered/pressed nodes.
221    pub fn apply_update(&mut self, update: NodesStatesUpdate<Key>) {
222        self.hovered_nodes.extend(update.hovered_nodes);
223        self.pressed_nodes.extend(update.pressed_nodes);
224    }
225
226    pub fn is_hovered(&self, key: Key) -> bool {
227        self.hovered_nodes.contains(&key)
228    }
229
230    pub fn is_pressed(&self, key: Key) -> bool {
231        self.pressed_nodes.contains(&key)
232    }
233}
234
235#[derive(Clone, Debug, PartialEq)]
236pub struct NodesStatesUpdate<Key: NodeKey> {
237    pressed_nodes: FxHashSet<Key>,
238    hovered_nodes: FxHashSet<Key>,
239}
240
241impl<Key: NodeKey> Default for NodesStatesUpdate<Key> {
242    fn default() -> Self {
243        Self {
244            pressed_nodes: FxHashSet::default(),
245            hovered_nodes: FxHashSet::default(),
246        }
247    }
248}
249
250impl<Key: NodeKey> NodesStatesUpdate<Key> {
251    /// Discard the state of a given [NodeKey] and [NameOfEvent] in this [NodesStatesUpdate].
252    pub fn discard<Name: NameOfEvent>(&mut self, name: &Name, node_key: &Key) {
253        match name {
254            _ if name.is_moved() => {
255                self.hovered_nodes.remove(node_key);
256            }
257            _ if name.is_pressed() => {
258                self.pressed_nodes.remove(node_key);
259            }
260            _ => {}
261        }
262    }
263}