Skip to main content

oxiui_core/
dispatch.rs

1//! Event dispatch with W3C-style capture and bubble phases.
2//!
3//! [`EventDispatcher`] routes a [`DispatchEvent`] from the tree root down to a
4//! target node (the *capture* phase) and back up to the root (the *bubble*
5//! phase), invoking the handlers registered for each node along the way. A
6//! handler returns a [`Propagation`] result; once `stop_propagation` is set the
7//! dispatcher visits no further nodes.
8//!
9//! ## Handler-safe mutation during dispatch
10//!
11//! Handlers commonly want to add or remove handlers as a side effect (e.g. a
12//! "close" button that detaches its own listener). Mutating the handler list
13//! while iterating it is a classic use-after-free / skipped-element bug. The
14//! dispatcher avoids it with a **collect-then-apply** protocol: the live
15//! registry is never borrowed mutably during a dispatch. Instead, handlers push
16//! `RegistryEdit`s into a deferred queue carried by [`HandlerCtx`]; the queue
17//! is drained and applied to the registry only after the whole dispatch
18//! finishes. Adds and removes therefore take effect on the *next* event, never
19//! mid-flight.
20
21use crate::events::{KeyboardEvent, MouseEvent, Propagation, TouchEvent};
22use crate::tree::{WidgetId, WidgetTree};
23
24/// The kinds of input events the dispatcher routes.
25#[derive(Clone, Debug, PartialEq)]
26pub enum DispatchEvent {
27    /// A pointer event.
28    Mouse(MouseEvent),
29    /// A keyboard event.
30    Keyboard(KeyboardEvent),
31    /// A touch event.
32    Touch(TouchEvent),
33}
34
35/// The dispatch phase in which a handler is being invoked.
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum Phase {
38    /// Travelling root → target. Capture handlers fire here.
39    Capture,
40    /// At the target node itself.
41    Target,
42    /// Travelling target → root. Bubble handlers fire here.
43    Bubble,
44}
45
46/// A deferred edit to the handler registry, queued by a handler during dispatch
47/// and applied after the dispatch completes.
48enum RegistryEdit {
49    /// Add a handler to a node for a phase.
50    Add {
51        /// Node the handler is attached to.
52        id: WidgetId,
53        /// Phase the handler listens in.
54        phase: Phase,
55        /// The handler to install.
56        handler: Box<dyn EventHandler>,
57    },
58    /// Remove every handler registered on a node.
59    RemoveAll {
60        /// Node to clear.
61        id: WidgetId,
62    },
63}
64
65/// Context handed to a handler during dispatch.
66///
67/// A handler reads the event and the current node/phase, and may queue registry
68/// edits (which apply only after dispatch). It returns its desired
69/// [`Propagation`] from [`EventHandler::handle`].
70pub struct HandlerCtx<'a> {
71    /// The event being dispatched.
72    pub event: &'a DispatchEvent,
73    /// The node whose handler is currently running.
74    pub current: WidgetId,
75    /// The phase this invocation belongs to.
76    pub phase: Phase,
77    /// The eventual target node (deepest in the path).
78    pub target: WidgetId,
79    /// Deferred registry edits queued by handlers (applied post-dispatch).
80    pending: &'a mut Vec<RegistryEdit>,
81}
82
83impl HandlerCtx<'_> {
84    /// Queue a handler to be added to `id` for `phase` after dispatch finishes.
85    pub fn add_handler(&mut self, id: WidgetId, phase: Phase, handler: Box<dyn EventHandler>) {
86        self.pending.push(RegistryEdit::Add { id, phase, handler });
87    }
88
89    /// Queue removal of every handler on `id` after dispatch finishes.
90    ///
91    /// This is the *safe* way to "unregister during dispatch": the removal is
92    /// recorded now and applied once iteration is complete, so the handler list
93    /// is never mutated while it is being walked.
94    pub fn remove_handlers(&mut self, id: WidgetId) {
95        self.pending.push(RegistryEdit::RemoveAll { id });
96    }
97}
98
99/// A typed event handler attached to a node.
100pub trait EventHandler {
101    /// Handle `ctx.event` and return propagation control.
102    fn handle(&mut self, ctx: &mut HandlerCtx<'_>) -> Propagation;
103}
104
105/// Blanket impl so plain closures can be used as handlers.
106impl<F> EventHandler for F
107where
108    F: FnMut(&mut HandlerCtx<'_>) -> Propagation,
109{
110    fn handle(&mut self, ctx: &mut HandlerCtx<'_>) -> Propagation {
111        self(ctx)
112    }
113}
114
115/// Per-node handler lists, split by phase.
116#[derive(Default)]
117struct NodeHandlers {
118    capture: Vec<Box<dyn EventHandler>>,
119    bubble: Vec<Box<dyn EventHandler>>,
120}
121
122/// Routes events through capture/bubble phases over a [`WidgetTree`].
123///
124/// The dispatcher owns the handler registry but borrows the tree only
125/// immutably (to compute the ancestor path), so a caller can keep mutating the
126/// tree between dispatches.
127#[derive(Default)]
128pub struct EventDispatcher {
129    handlers: std::collections::HashMap<WidgetId, NodeHandlers>,
130}
131
132impl EventDispatcher {
133    /// Create an empty dispatcher.
134    pub fn new() -> Self {
135        Self::default()
136    }
137
138    /// Register a capture-phase handler on `id`.
139    pub fn on_capture(&mut self, id: WidgetId, handler: Box<dyn EventHandler>) {
140        self.handlers.entry(id).or_default().capture.push(handler);
141    }
142
143    /// Register a bubble-phase handler on `id`. Target-phase handlers are
144    /// registered here too (the target node fires its bubble handlers in the
145    /// [`Phase::Target`] step).
146    pub fn on_bubble(&mut self, id: WidgetId, handler: Box<dyn EventHandler>) {
147        self.handlers.entry(id).or_default().bubble.push(handler);
148    }
149
150    /// Remove every handler registered on `id`. Returns `true` if any existed.
151    pub fn clear_node(&mut self, id: WidgetId) -> bool {
152        self.handlers.remove(&id).is_some()
153    }
154
155    /// Total number of nodes with at least one registered handler.
156    pub fn registered_nodes(&self) -> usize {
157        self.handlers.len()
158    }
159
160    /// Compute the capture path root → target (inclusive) for `target`.
161    fn path_to(tree: &WidgetTree, target: WidgetId) -> Vec<WidgetId> {
162        let mut path = Vec::new();
163        let mut cur = tree.get(target);
164        while let Some(node) = cur {
165            path.push(node.id);
166            cur = node.parent.and_then(|p| tree.get(p));
167        }
168        path.reverse(); // root → target
169        path
170    }
171
172    /// Dispatch `event` to `target`, running the capture phase (root → target),
173    /// the target phase, then the bubble phase (target → root).
174    ///
175    /// Returns the merged [`Propagation`] of every handler that ran. Dispatch
176    /// stops early as soon as a handler sets `stop_propagation`. Registry edits
177    /// queued by handlers are applied only after this call returns.
178    pub fn dispatch(
179        &mut self,
180        tree: &WidgetTree,
181        target: WidgetId,
182        event: DispatchEvent,
183    ) -> Propagation {
184        let path = Self::path_to(tree, target);
185        if path.is_empty() {
186            return Propagation::CONTINUE;
187        }
188
189        let mut pending: Vec<RegistryEdit> = Vec::new();
190        let mut result = Propagation::CONTINUE;
191        let actual_target = path.last().copied().unwrap_or(target);
192
193        // ── Capture phase: root → target (exclusive of the target). ──────────
194        'capture: for &id in path.iter().take(path.len().saturating_sub(1)) {
195            // Take the node's capture handlers OUT of the registry for the
196            // duration of iteration, so handlers may freely queue edits that
197            // touch the same node without aliasing the list we're walking.
198            let mut taken = match self.handlers.get_mut(&id) {
199                Some(h) if !h.capture.is_empty() => std::mem::take(&mut h.capture),
200                _ => continue,
201            };
202            for handler in taken.iter_mut() {
203                let mut ctx = HandlerCtx {
204                    event: &event,
205                    current: id,
206                    phase: Phase::Capture,
207                    target: actual_target,
208                    pending: &mut pending,
209                };
210                let prop = handler.handle(&mut ctx);
211                result = result.merge(prop);
212                if prop.stop_propagation {
213                    self.restore_capture(id, taken);
214                    break 'capture;
215                }
216            }
217            self.restore_capture(id, taken);
218        }
219
220        if !result.stop_propagation {
221            // ── Target + bubble phase: target → root. ────────────────────────
222            'bubble: for (i, &id) in path.iter().rev().enumerate() {
223                let phase = if i == 0 { Phase::Target } else { Phase::Bubble };
224                let mut taken = match self.handlers.get_mut(&id) {
225                    Some(h) if !h.bubble.is_empty() => std::mem::take(&mut h.bubble),
226                    _ => continue,
227                };
228                for handler in taken.iter_mut() {
229                    let mut ctx = HandlerCtx {
230                        event: &event,
231                        current: id,
232                        phase,
233                        target: actual_target,
234                        pending: &mut pending,
235                    };
236                    let prop = handler.handle(&mut ctx);
237                    result = result.merge(prop);
238                    if prop.stop_propagation {
239                        self.restore_bubble(id, taken);
240                        break 'bubble;
241                    }
242                }
243                self.restore_bubble(id, taken);
244            }
245        }
246
247        self.apply_pending(pending);
248        result
249    }
250
251    /// Put captured capture-phase handlers back, preserving any added during
252    /// dispatch via the pending queue (those are applied separately).
253    fn restore_capture(&mut self, id: WidgetId, mut taken: Vec<Box<dyn EventHandler>>) {
254        let slot = self.handlers.entry(id).or_default();
255        // Anything pushed onto the (now-empty) live list while we iterated is
256        // impossible because handlers only queue edits; but be defensive and
257        // prepend the original handlers ahead of any concurrently-added ones.
258        taken.append(&mut slot.capture);
259        slot.capture = taken;
260    }
261
262    /// Put captured bubble-phase handlers back.
263    fn restore_bubble(&mut self, id: WidgetId, mut taken: Vec<Box<dyn EventHandler>>) {
264        let slot = self.handlers.entry(id).or_default();
265        taken.append(&mut slot.bubble);
266        slot.bubble = taken;
267    }
268
269    /// Apply deferred registry edits queued during dispatch.
270    fn apply_pending(&mut self, pending: Vec<RegistryEdit>) {
271        for edit in pending {
272            match edit {
273                RegistryEdit::Add { id, phase, handler } => match phase {
274                    Phase::Capture => self.on_capture(id, handler),
275                    Phase::Bubble | Phase::Target => self.on_bubble(id, handler),
276                },
277                RegistryEdit::RemoveAll { id } => {
278                    self.handlers.remove(&id);
279                }
280            }
281        }
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288    use crate::events::{Modifiers, MouseButton};
289    use crate::geometry::{Point, Rect};
290    use std::cell::RefCell;
291    use std::rc::Rc;
292
293    fn mouse_down() -> DispatchEvent {
294        DispatchEvent::Mouse(MouseEvent::Down {
295            pos: Point::new(5.0, 5.0),
296            button: MouseButton::Left,
297            modifiers: Modifiers::NONE,
298        })
299    }
300
301    /// root → a → target tree.
302    fn linear_tree() -> (WidgetTree, WidgetId, WidgetId) {
303        let mut t = WidgetTree::new(Rect::new(0.0, 0.0, 100.0, 100.0));
304        let a = t
305            .insert(WidgetId::ROOT, Rect::new(0.0, 0.0, 50.0, 50.0))
306            .expect("root");
307        let target = t.insert(a, Rect::new(0.0, 0.0, 20.0, 20.0)).expect("a");
308        (t, a, target)
309    }
310
311    #[test]
312    fn capture_then_bubble_ordering() {
313        let (tree, a, target) = linear_tree();
314        let log = Rc::new(RefCell::new(Vec::<String>::new()));
315        let mut d = EventDispatcher::new();
316
317        for (id, name) in [(WidgetId::ROOT, "root"), (a, "a"), (target, "target")] {
318            let log_c = Rc::clone(&log);
319            d.on_capture(
320                id,
321                Box::new(move |ctx: &mut HandlerCtx<'_>| {
322                    log_c
323                        .borrow_mut()
324                        .push(format!("cap:{name}:{:?}", ctx.phase));
325                    Propagation::CONTINUE
326                }),
327            );
328            let log_b = Rc::clone(&log);
329            d.on_bubble(
330                id,
331                Box::new(move |ctx: &mut HandlerCtx<'_>| {
332                    log_b
333                        .borrow_mut()
334                        .push(format!("bub:{name}:{:?}", ctx.phase));
335                    Propagation::CONTINUE
336                }),
337            );
338        }
339
340        d.dispatch(&tree, target, mouse_down());
341        let seen = log.borrow().clone();
342        assert_eq!(
343            seen,
344            vec![
345                // capture root → a (target excluded from capture loop)
346                "cap:root:Capture",
347                "cap:a:Capture",
348                // target + bubble target → root
349                "bub:target:Target",
350                "bub:a:Bubble",
351                "bub:root:Bubble",
352            ]
353        );
354    }
355
356    #[test]
357    fn stop_propagation_halts_bubble() {
358        let (tree, a, target) = linear_tree();
359        let log = Rc::new(RefCell::new(Vec::<String>::new()));
360        let mut d = EventDispatcher::new();
361
362        let log_t = Rc::clone(&log);
363        d.on_bubble(
364            target,
365            Box::new(move |_: &mut HandlerCtx<'_>| {
366                log_t.borrow_mut().push("target".to_string());
367                Propagation::stop() // stop here; `a` and root must NOT fire
368            }),
369        );
370        let log_a = Rc::clone(&log);
371        d.on_bubble(
372            a,
373            Box::new(move |_: &mut HandlerCtx<'_>| {
374                log_a.borrow_mut().push("a".to_string());
375                Propagation::CONTINUE
376            }),
377        );
378
379        let result = d.dispatch(&tree, target, mouse_down());
380        assert!(result.stop_propagation);
381        assert_eq!(*log.borrow(), vec!["target".to_string()]);
382    }
383
384    #[test]
385    fn prevent_default_is_reported() {
386        let (tree, _a, target) = linear_tree();
387        let mut d = EventDispatcher::new();
388        d.on_bubble(
389            target,
390            Box::new(|_: &mut HandlerCtx<'_>| Propagation::prevent()),
391        );
392        let result = d.dispatch(&tree, target, mouse_down());
393        assert!(result.prevent_default);
394        assert!(!result.stop_propagation);
395    }
396
397    #[test]
398    fn handler_removal_during_dispatch_is_deferred() {
399        let (tree, _a, target) = linear_tree();
400        let count = Rc::new(RefCell::new(0u32));
401        let mut d = EventDispatcher::new();
402
403        // Handler removes itself on first fire. Because removal is deferred, it
404        // still fires exactly once here; on the *second* dispatch it is gone.
405        let count_c = Rc::clone(&count);
406        d.on_bubble(
407            target,
408            Box::new(move |ctx: &mut HandlerCtx<'_>| {
409                *count_c.borrow_mut() += 1;
410                ctx.remove_handlers(target); // safe: applied after dispatch
411                Propagation::CONTINUE
412            }),
413        );
414
415        d.dispatch(&tree, target, mouse_down());
416        assert_eq!(*count.borrow(), 1);
417        assert_eq!(
418            d.registered_nodes(),
419            0,
420            "handler should be removed post-dispatch"
421        );
422
423        // Second dispatch: no handler remains, count unchanged.
424        d.dispatch(&tree, target, mouse_down());
425        assert_eq!(*count.borrow(), 1);
426    }
427
428    #[test]
429    fn handler_add_during_dispatch_is_deferred() {
430        let (tree, _a, target) = linear_tree();
431        let fired = Rc::new(RefCell::new(Vec::<&'static str>::new()));
432        let mut d = EventDispatcher::new();
433
434        let fired_outer = Rc::clone(&fired);
435        let fired_inner = Rc::clone(&fired);
436        d.on_bubble(
437            target,
438            Box::new(move |ctx: &mut HandlerCtx<'_>| {
439                fired_outer.borrow_mut().push("outer");
440                let f = Rc::clone(&fired_inner);
441                // Add a new handler mid-dispatch; must NOT fire this dispatch.
442                ctx.add_handler(
443                    target,
444                    Phase::Bubble,
445                    Box::new(move |_: &mut HandlerCtx<'_>| {
446                        f.borrow_mut().push("inner");
447                        Propagation::CONTINUE
448                    }),
449                );
450                Propagation::CONTINUE
451            }),
452        );
453
454        d.dispatch(&tree, target, mouse_down());
455        assert_eq!(
456            *fired.borrow(),
457            vec!["outer"],
458            "added handler must not fire same dispatch"
459        );
460        d.dispatch(&tree, target, mouse_down());
461        // Now both the original and the deferred-added handler fire.
462        assert_eq!(*fired.borrow(), vec!["outer", "outer", "inner"]);
463    }
464
465    #[test]
466    fn dispatch_to_missing_target_is_noop() {
467        let (tree, _a, _t) = linear_tree();
468        let mut d = EventDispatcher::new();
469        let prop = d.dispatch(&tree, WidgetId(9999), mouse_down());
470        assert_eq!(prop, Propagation::CONTINUE);
471    }
472}