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///
128/// ## Allocation-free fast path
129///
130/// The path buffer and the deferred-edit buffer are both held as pre-allocated
131/// `Vec`s that are cleared and reused across dispatches. This means that after
132/// the first event of each size class, dispatch is completely heap-allocation-free
133/// on the hot path (no new `Vec` allocations during capture/bubble traversal).
134pub struct EventDispatcher {
135    handlers: std::collections::HashMap<WidgetId, NodeHandlers>,
136    /// Re-used scratch buffer: the ancestor path (root → target).
137    /// Cleared and refilled on every [`dispatch`](EventDispatcher::dispatch) call.
138    path_scratch: Vec<WidgetId>,
139    /// Re-used scratch buffer: deferred registry edits queued by handlers.
140    /// Cleared and drained on every [`dispatch`](EventDispatcher::dispatch) call.
141    pending_scratch: Vec<RegistryEdit>,
142}
143
144impl Default for EventDispatcher {
145    fn default() -> Self {
146        Self {
147            handlers: std::collections::HashMap::new(),
148            // Pre-allocate for 32-node deep trees (typical UI depth is 5–15).
149            path_scratch: Vec::with_capacity(32),
150            // Pre-allocate for a handful of edits per dispatch (rarely > 2).
151            pending_scratch: Vec::with_capacity(4),
152        }
153    }
154}
155
156impl EventDispatcher {
157    /// Create an empty dispatcher.
158    pub fn new() -> Self {
159        Self::default()
160    }
161
162    /// Register a capture-phase handler on `id`.
163    pub fn on_capture(&mut self, id: WidgetId, handler: Box<dyn EventHandler>) {
164        self.handlers.entry(id).or_default().capture.push(handler);
165    }
166
167    /// Register a bubble-phase handler on `id`. Target-phase handlers are
168    /// registered here too (the target node fires its bubble handlers in the
169    /// [`Phase::Target`] step).
170    pub fn on_bubble(&mut self, id: WidgetId, handler: Box<dyn EventHandler>) {
171        self.handlers.entry(id).or_default().bubble.push(handler);
172    }
173
174    /// Remove every handler registered on `id`. Returns `true` if any existed.
175    pub fn clear_node(&mut self, id: WidgetId) -> bool {
176        self.handlers.remove(&id).is_some()
177    }
178
179    /// Total number of nodes with at least one registered handler.
180    pub fn registered_nodes(&self) -> usize {
181        self.handlers.len()
182    }
183
184    /// Compute the capture path root → target into `out` (cleared first).
185    ///
186    /// Uses the pre-allocated scratch buffer to avoid per-dispatch heap allocation.
187    fn path_to_reuse(tree: &WidgetTree, target: WidgetId, out: &mut Vec<WidgetId>) {
188        out.clear();
189        let mut cur = tree.get(target);
190        while let Some(node) = cur {
191            out.push(node.id);
192            cur = node.parent.and_then(|p| tree.get(p));
193        }
194        out.reverse(); // root → target
195    }
196
197    /// Dispatch `event` to `target`, running the capture phase (root → target),
198    /// the target phase, then the bubble phase (target → root).
199    ///
200    /// Returns the merged [`Propagation`] of every handler that ran. Dispatch
201    /// stops early as soon as a handler sets `stop_propagation`. Registry edits
202    /// queued by handlers are applied only after this call returns.
203    ///
204    /// This method is **allocation-free on the hot path** after the first call:
205    /// it reuses pre-allocated scratch buffers for both the ancestor path and
206    /// the deferred-edit queue, so no heap allocation occurs during traversal.
207    pub fn dispatch(
208        &mut self,
209        tree: &WidgetTree,
210        target: WidgetId,
211        event: DispatchEvent,
212    ) -> Propagation {
213        // Fill the path into the pre-allocated scratch buffer — no allocation.
214        // We must extract the buffer temporarily to avoid a split-borrow conflict
215        // between `self.path_scratch` (mutated) and `self.handlers` (read below).
216        let mut path = std::mem::take(&mut self.path_scratch);
217        Self::path_to_reuse(tree, target, &mut path);
218
219        if path.is_empty() {
220            self.path_scratch = path;
221            return Propagation::CONTINUE;
222        }
223
224        // Similarly extract the pending-edits buffer.
225        let mut pending = std::mem::take(&mut self.pending_scratch);
226        pending.clear();
227
228        let mut result = Propagation::CONTINUE;
229        let actual_target = path.last().copied().unwrap_or(target);
230
231        // ── Capture phase: root → target (exclusive of the target). ──────────
232        'capture: for &id in path.iter().take(path.len().saturating_sub(1)) {
233            // Take the node's capture handlers OUT of the registry for the
234            // duration of iteration, so handlers may freely queue edits that
235            // touch the same node without aliasing the list we're walking.
236            let mut taken = match self.handlers.get_mut(&id) {
237                Some(h) if !h.capture.is_empty() => std::mem::take(&mut h.capture),
238                _ => continue,
239            };
240            for handler in taken.iter_mut() {
241                let mut ctx = HandlerCtx {
242                    event: &event,
243                    current: id,
244                    phase: Phase::Capture,
245                    target: actual_target,
246                    pending: &mut pending,
247                };
248                let prop = handler.handle(&mut ctx);
249                result = result.merge(prop);
250                if prop.stop_propagation {
251                    self.restore_capture(id, taken);
252                    break 'capture;
253                }
254            }
255            self.restore_capture(id, taken);
256        }
257
258        if !result.stop_propagation {
259            // ── Target + bubble phase: target → root. ────────────────────────
260            'bubble: for (i, &id) in path.iter().rev().enumerate() {
261                let phase = if i == 0 { Phase::Target } else { Phase::Bubble };
262                let mut taken = match self.handlers.get_mut(&id) {
263                    Some(h) if !h.bubble.is_empty() => std::mem::take(&mut h.bubble),
264                    _ => continue,
265                };
266                for handler in taken.iter_mut() {
267                    let mut ctx = HandlerCtx {
268                        event: &event,
269                        current: id,
270                        phase,
271                        target: actual_target,
272                        pending: &mut pending,
273                    };
274                    let prop = handler.handle(&mut ctx);
275                    result = result.merge(prop);
276                    if prop.stop_propagation {
277                        self.restore_bubble(id, taken);
278                        break 'bubble;
279                    }
280                }
281                self.restore_bubble(id, taken);
282            }
283        }
284
285        self.apply_pending(&mut pending);
286
287        // Return the scratch buffers so the next call reuses the allocations.
288        // Clear pending (apply_pending already drained it) but keep capacity.
289        pending.clear();
290        self.pending_scratch = pending;
291        self.path_scratch = path;
292
293        result
294    }
295
296    /// Put captured capture-phase handlers back, preserving any added during
297    /// dispatch via the pending queue (those are applied separately).
298    fn restore_capture(&mut self, id: WidgetId, mut taken: Vec<Box<dyn EventHandler>>) {
299        let slot = self.handlers.entry(id).or_default();
300        // Anything pushed onto the (now-empty) live list while we iterated is
301        // impossible because handlers only queue edits; but be defensive and
302        // prepend the original handlers ahead of any concurrently-added ones.
303        taken.append(&mut slot.capture);
304        slot.capture = taken;
305    }
306
307    /// Put captured bubble-phase handlers back.
308    fn restore_bubble(&mut self, id: WidgetId, mut taken: Vec<Box<dyn EventHandler>>) {
309        let slot = self.handlers.entry(id).or_default();
310        taken.append(&mut slot.bubble);
311        slot.bubble = taken;
312    }
313
314    /// Apply deferred registry edits queued during dispatch.
315    ///
316    /// Drains the edits from `pending` in-place so the buffer's capacity is
317    /// preserved for the next dispatch call.
318    fn apply_pending(&mut self, pending: &mut Vec<RegistryEdit>) {
319        for edit in pending.drain(..) {
320            match edit {
321                RegistryEdit::Add { id, phase, handler } => match phase {
322                    Phase::Capture => self.on_capture(id, handler),
323                    Phase::Bubble | Phase::Target => self.on_bubble(id, handler),
324                },
325                RegistryEdit::RemoveAll { id } => {
326                    self.handlers.remove(&id);
327                }
328            }
329        }
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336    use crate::events::{Modifiers, MouseButton};
337    use crate::geometry::{Point, Rect};
338    use std::cell::RefCell;
339    use std::rc::Rc;
340
341    fn mouse_down() -> DispatchEvent {
342        DispatchEvent::Mouse(MouseEvent::Down {
343            pos: Point::new(5.0, 5.0),
344            button: MouseButton::Left,
345            modifiers: Modifiers::NONE,
346        })
347    }
348
349    /// root → a → target tree.
350    fn linear_tree() -> (WidgetTree, WidgetId, WidgetId) {
351        let mut t = WidgetTree::new(Rect::new(0.0, 0.0, 100.0, 100.0));
352        let a = t
353            .insert(WidgetId::ROOT, Rect::new(0.0, 0.0, 50.0, 50.0))
354            .expect("root");
355        let target = t.insert(a, Rect::new(0.0, 0.0, 20.0, 20.0)).expect("a");
356        (t, a, target)
357    }
358
359    #[test]
360    fn capture_then_bubble_ordering() {
361        let (tree, a, target) = linear_tree();
362        let log = Rc::new(RefCell::new(Vec::<String>::new()));
363        let mut d = EventDispatcher::new();
364
365        for (id, name) in [(WidgetId::ROOT, "root"), (a, "a"), (target, "target")] {
366            let log_c = Rc::clone(&log);
367            d.on_capture(
368                id,
369                Box::new(move |ctx: &mut HandlerCtx<'_>| {
370                    log_c
371                        .borrow_mut()
372                        .push(format!("cap:{name}:{:?}", ctx.phase));
373                    Propagation::CONTINUE
374                }),
375            );
376            let log_b = Rc::clone(&log);
377            d.on_bubble(
378                id,
379                Box::new(move |ctx: &mut HandlerCtx<'_>| {
380                    log_b
381                        .borrow_mut()
382                        .push(format!("bub:{name}:{:?}", ctx.phase));
383                    Propagation::CONTINUE
384                }),
385            );
386        }
387
388        d.dispatch(&tree, target, mouse_down());
389        let seen = log.borrow().clone();
390        assert_eq!(
391            seen,
392            vec![
393                // capture root → a (target excluded from capture loop)
394                "cap:root:Capture",
395                "cap:a:Capture",
396                // target + bubble target → root
397                "bub:target:Target",
398                "bub:a:Bubble",
399                "bub:root:Bubble",
400            ]
401        );
402    }
403
404    #[test]
405    fn stop_propagation_halts_bubble() {
406        let (tree, a, target) = linear_tree();
407        let log = Rc::new(RefCell::new(Vec::<String>::new()));
408        let mut d = EventDispatcher::new();
409
410        let log_t = Rc::clone(&log);
411        d.on_bubble(
412            target,
413            Box::new(move |_: &mut HandlerCtx<'_>| {
414                log_t.borrow_mut().push("target".to_string());
415                Propagation::stop() // stop here; `a` and root must NOT fire
416            }),
417        );
418        let log_a = Rc::clone(&log);
419        d.on_bubble(
420            a,
421            Box::new(move |_: &mut HandlerCtx<'_>| {
422                log_a.borrow_mut().push("a".to_string());
423                Propagation::CONTINUE
424            }),
425        );
426
427        let result = d.dispatch(&tree, target, mouse_down());
428        assert!(result.stop_propagation);
429        assert_eq!(*log.borrow(), vec!["target".to_string()]);
430    }
431
432    #[test]
433    fn prevent_default_is_reported() {
434        let (tree, _a, target) = linear_tree();
435        let mut d = EventDispatcher::new();
436        d.on_bubble(
437            target,
438            Box::new(|_: &mut HandlerCtx<'_>| Propagation::prevent()),
439        );
440        let result = d.dispatch(&tree, target, mouse_down());
441        assert!(result.prevent_default);
442        assert!(!result.stop_propagation);
443    }
444
445    #[test]
446    fn handler_removal_during_dispatch_is_deferred() {
447        let (tree, _a, target) = linear_tree();
448        let count = Rc::new(RefCell::new(0u32));
449        let mut d = EventDispatcher::new();
450
451        // Handler removes itself on first fire. Because removal is deferred, it
452        // still fires exactly once here; on the *second* dispatch it is gone.
453        let count_c = Rc::clone(&count);
454        d.on_bubble(
455            target,
456            Box::new(move |ctx: &mut HandlerCtx<'_>| {
457                *count_c.borrow_mut() += 1;
458                ctx.remove_handlers(target); // safe: applied after dispatch
459                Propagation::CONTINUE
460            }),
461        );
462
463        d.dispatch(&tree, target, mouse_down());
464        assert_eq!(*count.borrow(), 1);
465        assert_eq!(
466            d.registered_nodes(),
467            0,
468            "handler should be removed post-dispatch"
469        );
470
471        // Second dispatch: no handler remains, count unchanged.
472        d.dispatch(&tree, target, mouse_down());
473        assert_eq!(*count.borrow(), 1);
474    }
475
476    #[test]
477    fn handler_add_during_dispatch_is_deferred() {
478        let (tree, _a, target) = linear_tree();
479        let fired = Rc::new(RefCell::new(Vec::<&'static str>::new()));
480        let mut d = EventDispatcher::new();
481
482        let fired_outer = Rc::clone(&fired);
483        let fired_inner = Rc::clone(&fired);
484        d.on_bubble(
485            target,
486            Box::new(move |ctx: &mut HandlerCtx<'_>| {
487                fired_outer.borrow_mut().push("outer");
488                let f = Rc::clone(&fired_inner);
489                // Add a new handler mid-dispatch; must NOT fire this dispatch.
490                ctx.add_handler(
491                    target,
492                    Phase::Bubble,
493                    Box::new(move |_: &mut HandlerCtx<'_>| {
494                        f.borrow_mut().push("inner");
495                        Propagation::CONTINUE
496                    }),
497                );
498                Propagation::CONTINUE
499            }),
500        );
501
502        d.dispatch(&tree, target, mouse_down());
503        assert_eq!(
504            *fired.borrow(),
505            vec!["outer"],
506            "added handler must not fire same dispatch"
507        );
508        d.dispatch(&tree, target, mouse_down());
509        // Now both the original and the deferred-added handler fire.
510        assert_eq!(*fired.borrow(), vec!["outer", "outer", "inner"]);
511    }
512
513    #[test]
514    fn dispatch_to_missing_target_is_noop() {
515        let (tree, _a, _t) = linear_tree();
516        let mut d = EventDispatcher::new();
517        let prop = d.dispatch(&tree, WidgetId(9999), mouse_down());
518        assert_eq!(prop, Propagation::CONTINUE);
519    }
520}