Skip to main content

ragnarok/
nodes_state.rs

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