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
74            .iter()
75            .find(|e| e.is_moved() || e.is_touch_released());
76        let mut removed_from_hovered = FxHashSet::default();
77
78        self.hovered_nodes.retain(|node_key| {
79            let Some(area) = events_measurer.try_area_of(node_key) else {
80                removed_from_hovered.insert(*node_key);
81                return false;
82            };
83
84            let cursor_still_inside = source_movement_event
85                .and_then(|e| e.try_location())
86                .is_none_or(|cursor| events_measurer.is_point_inside(node_key, cursor));
87
88            if cursor_still_inside {
89                return true;
90            }
91
92            // Cursor moved outside this node, emit leave events
93            let source_event = source_movement_event.unwrap();
94            for derived_event in Name::new_leave().get_derived_events() {
95                if events_measurer.is_listening_to(node_key, &derived_event) {
96                    collateral_emmitable_events.push(events_measurer.new_emmitable_event(
97                        *node_key,
98                        derived_event,
99                        source_event.clone(),
100                        Some(area),
101                    ));
102                }
103            }
104
105            #[cfg(debug_assertions)]
106            tracing::info!("Unmarked as hovered {:?}", node_key);
107
108            removed_from_hovered.insert(*node_key);
109
110            false
111        });
112
113        // Emit exclusive leave when the deepest node changes
114        // but the old node is still hovered (otherwise the regular leave covers it).
115        if let Some(source_event) = source_movement_event {
116            let new_deepest = emmitable_events
117                .iter()
118                .find(|e| e.name().is_exclusive_enter())
119                .map(|e| e.key());
120
121            if let Some(old_entered) = self.entered_node {
122                let deepest_changed = new_deepest != Some(old_entered);
123                let still_hovered = !removed_from_hovered.contains(&old_entered);
124
125                if deepest_changed && still_hovered {
126                    let exclusive_leave = Name::new_exclusive_leave();
127                    if events_measurer.is_listening_to(&old_entered, &exclusive_leave)
128                        && let Some(area) = events_measurer.try_area_of(&old_entered)
129                    {
130                        collateral_emmitable_events.push(events_measurer.new_emmitable_event(
131                            old_entered,
132                            exclusive_leave,
133                            source_event.clone(),
134                            Some(area),
135                        ));
136                    }
137                }
138            }
139        }
140
141        collateral_emmitable_events
142    }
143
144    pub(crate) fn filter_emmitable_events<
145        Emmitable: EmmitableEvent<Key = Key, Name = Name>,
146        Name: NameOfEvent,
147    >(
148        &mut self,
149        emmitable_events: &mut Vec<Emmitable>,
150    ) {
151        let entered_node = emmitable_events
152            .iter()
153            .rev()
154            .find(|e| e.name().is_moved() || e.name().is_exclusive_enter())
155            .map(|e| e.key());
156
157        emmitable_events.retain(|ev| match ev.name() {
158            // Deduplicate exclusive enter against `entered_node`
159            _ if ev.name().is_exclusive_enter() => {
160                entered_node.as_ref() == Some(&ev.key()) && entered_node != self.entered_node
161            }
162            // Deduplicate non-exclusive enter against `hovered_nodes`
163            _ if ev.name().is_enter() => !self.hovered_nodes.contains(&ev.key()),
164            // Only emit release events for already-pressed nodes
165            _ if ev.name().is_released() => self.pressed_nodes.contains(&ev.key()),
166            _ => true,
167        });
168
169        self.entered_node = entered_node;
170    }
171
172    /// Create the nodes states given the [PotentialEvent]s.
173    pub fn create_update<Name: NameOfEvent, Source: SourceEvent>(
174        &self,
175        events_measurer: &impl EventsMeasurer<Key = Key, Name = Name>,
176        potential_events: &PotentialEvents<Key, Name, Source>,
177    ) -> NodesStatesUpdate<Key> {
178        let mut hovered_nodes = FxHashSet::default();
179        let mut pressed_nodes = FxHashSet::default();
180
181        for events in potential_events.values() {
182            let mut child_node: Option<Key> = None;
183
184            for PotentialEvent { node_key, name, .. } in events.iter().rev() {
185                if let Some(child_node) = child_node
186                    && !events_measurer.is_node_parent_of(&child_node, *node_key)
187                {
188                    continue;
189                }
190
191                // If the background isn't transparent,
192                // we must make sure that next nodes are parent of it.
193                // This only matters for events that don't go through solids (e.g. cursor click events)
194                if !events_measurer.is_node_transparent(node_key) && !name.does_go_through_solid() {
195                    child_node = Some(*node_key);
196                }
197
198                match name {
199                    name if name.is_moved() => {
200                        hovered_nodes.insert(*node_key);
201
202                        #[cfg(debug_assertions)]
203                        tracing::info!("Marked as hovered {:?}", node_key);
204                    }
205                    name if name.is_pressed() => {
206                        pressed_nodes.insert(*node_key);
207
208                        #[cfg(debug_assertions)]
209                        tracing::info!("Marked as pressed {:?}", node_key);
210                    }
211                    _ => {}
212                }
213            }
214        }
215
216        NodesStatesUpdate {
217            pressed_nodes,
218            hovered_nodes,
219        }
220    }
221
222    /// Apply the given [NodesStatesUpdate], extending the cached hovered/pressed nodes.
223    pub fn apply_update(&mut self, update: NodesStatesUpdate<Key>) {
224        self.hovered_nodes.extend(update.hovered_nodes);
225        self.pressed_nodes.extend(update.pressed_nodes);
226    }
227
228    pub fn is_hovered(&self, key: Key) -> bool {
229        self.hovered_nodes.contains(&key)
230    }
231
232    pub fn is_pressed(&self, key: Key) -> bool {
233        self.pressed_nodes.contains(&key)
234    }
235}
236
237#[derive(Clone, Debug, PartialEq)]
238pub struct NodesStatesUpdate<Key: NodeKey> {
239    pressed_nodes: FxHashSet<Key>,
240    hovered_nodes: FxHashSet<Key>,
241}
242
243impl<Key: NodeKey> Default for NodesStatesUpdate<Key> {
244    fn default() -> Self {
245        Self {
246            pressed_nodes: FxHashSet::default(),
247            hovered_nodes: FxHashSet::default(),
248        }
249    }
250}
251
252impl<Key: NodeKey> NodesStatesUpdate<Key> {
253    /// Discard the state of a given [NodeKey] and [NameOfEvent] in this [NodesStatesUpdate].
254    pub fn discard<Name: NameOfEvent>(&mut self, name: &Name, node_key: &Key) {
255        match name {
256            _ if name.is_moved() => {
257                self.hovered_nodes.remove(node_key);
258            }
259            _ if name.is_pressed() => {
260                self.pressed_nodes.remove(node_key);
261            }
262            _ => {}
263        }
264    }
265}