use crate::events::{KeyboardEvent, MouseEvent, Propagation, TouchEvent};
use crate::tree::{WidgetId, WidgetTree};
#[derive(Clone, Debug, PartialEq)]
pub enum DispatchEvent {
Mouse(MouseEvent),
Keyboard(KeyboardEvent),
Touch(TouchEvent),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Phase {
Capture,
Target,
Bubble,
}
enum RegistryEdit {
Add {
id: WidgetId,
phase: Phase,
handler: Box<dyn EventHandler>,
},
RemoveAll {
id: WidgetId,
},
}
pub struct HandlerCtx<'a> {
pub event: &'a DispatchEvent,
pub current: WidgetId,
pub phase: Phase,
pub target: WidgetId,
pending: &'a mut Vec<RegistryEdit>,
}
impl HandlerCtx<'_> {
pub fn add_handler(&mut self, id: WidgetId, phase: Phase, handler: Box<dyn EventHandler>) {
self.pending.push(RegistryEdit::Add { id, phase, handler });
}
pub fn remove_handlers(&mut self, id: WidgetId) {
self.pending.push(RegistryEdit::RemoveAll { id });
}
}
pub trait EventHandler {
fn handle(&mut self, ctx: &mut HandlerCtx<'_>) -> Propagation;
}
impl<F> EventHandler for F
where
F: FnMut(&mut HandlerCtx<'_>) -> Propagation,
{
fn handle(&mut self, ctx: &mut HandlerCtx<'_>) -> Propagation {
self(ctx)
}
}
#[derive(Default)]
struct NodeHandlers {
capture: Vec<Box<dyn EventHandler>>,
bubble: Vec<Box<dyn EventHandler>>,
}
#[derive(Default)]
pub struct EventDispatcher {
handlers: std::collections::HashMap<WidgetId, NodeHandlers>,
}
impl EventDispatcher {
pub fn new() -> Self {
Self::default()
}
pub fn on_capture(&mut self, id: WidgetId, handler: Box<dyn EventHandler>) {
self.handlers.entry(id).or_default().capture.push(handler);
}
pub fn on_bubble(&mut self, id: WidgetId, handler: Box<dyn EventHandler>) {
self.handlers.entry(id).or_default().bubble.push(handler);
}
pub fn clear_node(&mut self, id: WidgetId) -> bool {
self.handlers.remove(&id).is_some()
}
pub fn registered_nodes(&self) -> usize {
self.handlers.len()
}
fn path_to(tree: &WidgetTree, target: WidgetId) -> Vec<WidgetId> {
let mut path = Vec::new();
let mut cur = tree.get(target);
while let Some(node) = cur {
path.push(node.id);
cur = node.parent.and_then(|p| tree.get(p));
}
path.reverse(); path
}
pub fn dispatch(
&mut self,
tree: &WidgetTree,
target: WidgetId,
event: DispatchEvent,
) -> Propagation {
let path = Self::path_to(tree, target);
if path.is_empty() {
return Propagation::CONTINUE;
}
let mut pending: Vec<RegistryEdit> = Vec::new();
let mut result = Propagation::CONTINUE;
let actual_target = path.last().copied().unwrap_or(target);
'capture: for &id in path.iter().take(path.len().saturating_sub(1)) {
let mut taken = match self.handlers.get_mut(&id) {
Some(h) if !h.capture.is_empty() => std::mem::take(&mut h.capture),
_ => continue,
};
for handler in taken.iter_mut() {
let mut ctx = HandlerCtx {
event: &event,
current: id,
phase: Phase::Capture,
target: actual_target,
pending: &mut pending,
};
let prop = handler.handle(&mut ctx);
result = result.merge(prop);
if prop.stop_propagation {
self.restore_capture(id, taken);
break 'capture;
}
}
self.restore_capture(id, taken);
}
if !result.stop_propagation {
'bubble: for (i, &id) in path.iter().rev().enumerate() {
let phase = if i == 0 { Phase::Target } else { Phase::Bubble };
let mut taken = match self.handlers.get_mut(&id) {
Some(h) if !h.bubble.is_empty() => std::mem::take(&mut h.bubble),
_ => continue,
};
for handler in taken.iter_mut() {
let mut ctx = HandlerCtx {
event: &event,
current: id,
phase,
target: actual_target,
pending: &mut pending,
};
let prop = handler.handle(&mut ctx);
result = result.merge(prop);
if prop.stop_propagation {
self.restore_bubble(id, taken);
break 'bubble;
}
}
self.restore_bubble(id, taken);
}
}
self.apply_pending(pending);
result
}
fn restore_capture(&mut self, id: WidgetId, mut taken: Vec<Box<dyn EventHandler>>) {
let slot = self.handlers.entry(id).or_default();
taken.append(&mut slot.capture);
slot.capture = taken;
}
fn restore_bubble(&mut self, id: WidgetId, mut taken: Vec<Box<dyn EventHandler>>) {
let slot = self.handlers.entry(id).or_default();
taken.append(&mut slot.bubble);
slot.bubble = taken;
}
fn apply_pending(&mut self, pending: Vec<RegistryEdit>) {
for edit in pending {
match edit {
RegistryEdit::Add { id, phase, handler } => match phase {
Phase::Capture => self.on_capture(id, handler),
Phase::Bubble | Phase::Target => self.on_bubble(id, handler),
},
RegistryEdit::RemoveAll { id } => {
self.handlers.remove(&id);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::events::{Modifiers, MouseButton};
use crate::geometry::{Point, Rect};
use std::cell::RefCell;
use std::rc::Rc;
fn mouse_down() -> DispatchEvent {
DispatchEvent::Mouse(MouseEvent::Down {
pos: Point::new(5.0, 5.0),
button: MouseButton::Left,
modifiers: Modifiers::NONE,
})
}
fn linear_tree() -> (WidgetTree, WidgetId, WidgetId) {
let mut t = WidgetTree::new(Rect::new(0.0, 0.0, 100.0, 100.0));
let a = t
.insert(WidgetId::ROOT, Rect::new(0.0, 0.0, 50.0, 50.0))
.expect("root");
let target = t.insert(a, Rect::new(0.0, 0.0, 20.0, 20.0)).expect("a");
(t, a, target)
}
#[test]
fn capture_then_bubble_ordering() {
let (tree, a, target) = linear_tree();
let log = Rc::new(RefCell::new(Vec::<String>::new()));
let mut d = EventDispatcher::new();
for (id, name) in [(WidgetId::ROOT, "root"), (a, "a"), (target, "target")] {
let log_c = Rc::clone(&log);
d.on_capture(
id,
Box::new(move |ctx: &mut HandlerCtx<'_>| {
log_c
.borrow_mut()
.push(format!("cap:{name}:{:?}", ctx.phase));
Propagation::CONTINUE
}),
);
let log_b = Rc::clone(&log);
d.on_bubble(
id,
Box::new(move |ctx: &mut HandlerCtx<'_>| {
log_b
.borrow_mut()
.push(format!("bub:{name}:{:?}", ctx.phase));
Propagation::CONTINUE
}),
);
}
d.dispatch(&tree, target, mouse_down());
let seen = log.borrow().clone();
assert_eq!(
seen,
vec![
"cap:root:Capture",
"cap:a:Capture",
"bub:target:Target",
"bub:a:Bubble",
"bub:root:Bubble",
]
);
}
#[test]
fn stop_propagation_halts_bubble() {
let (tree, a, target) = linear_tree();
let log = Rc::new(RefCell::new(Vec::<String>::new()));
let mut d = EventDispatcher::new();
let log_t = Rc::clone(&log);
d.on_bubble(
target,
Box::new(move |_: &mut HandlerCtx<'_>| {
log_t.borrow_mut().push("target".to_string());
Propagation::stop() }),
);
let log_a = Rc::clone(&log);
d.on_bubble(
a,
Box::new(move |_: &mut HandlerCtx<'_>| {
log_a.borrow_mut().push("a".to_string());
Propagation::CONTINUE
}),
);
let result = d.dispatch(&tree, target, mouse_down());
assert!(result.stop_propagation);
assert_eq!(*log.borrow(), vec!["target".to_string()]);
}
#[test]
fn prevent_default_is_reported() {
let (tree, _a, target) = linear_tree();
let mut d = EventDispatcher::new();
d.on_bubble(
target,
Box::new(|_: &mut HandlerCtx<'_>| Propagation::prevent()),
);
let result = d.dispatch(&tree, target, mouse_down());
assert!(result.prevent_default);
assert!(!result.stop_propagation);
}
#[test]
fn handler_removal_during_dispatch_is_deferred() {
let (tree, _a, target) = linear_tree();
let count = Rc::new(RefCell::new(0u32));
let mut d = EventDispatcher::new();
let count_c = Rc::clone(&count);
d.on_bubble(
target,
Box::new(move |ctx: &mut HandlerCtx<'_>| {
*count_c.borrow_mut() += 1;
ctx.remove_handlers(target); Propagation::CONTINUE
}),
);
d.dispatch(&tree, target, mouse_down());
assert_eq!(*count.borrow(), 1);
assert_eq!(
d.registered_nodes(),
0,
"handler should be removed post-dispatch"
);
d.dispatch(&tree, target, mouse_down());
assert_eq!(*count.borrow(), 1);
}
#[test]
fn handler_add_during_dispatch_is_deferred() {
let (tree, _a, target) = linear_tree();
let fired = Rc::new(RefCell::new(Vec::<&'static str>::new()));
let mut d = EventDispatcher::new();
let fired_outer = Rc::clone(&fired);
let fired_inner = Rc::clone(&fired);
d.on_bubble(
target,
Box::new(move |ctx: &mut HandlerCtx<'_>| {
fired_outer.borrow_mut().push("outer");
let f = Rc::clone(&fired_inner);
ctx.add_handler(
target,
Phase::Bubble,
Box::new(move |_: &mut HandlerCtx<'_>| {
f.borrow_mut().push("inner");
Propagation::CONTINUE
}),
);
Propagation::CONTINUE
}),
);
d.dispatch(&tree, target, mouse_down());
assert_eq!(
*fired.borrow(),
vec!["outer"],
"added handler must not fire same dispatch"
);
d.dispatch(&tree, target, mouse_down());
assert_eq!(*fired.borrow(), vec!["outer", "outer", "inner"]);
}
#[test]
fn dispatch_to_missing_target_is_noop() {
let (tree, _a, _t) = linear_tree();
let mut d = EventDispatcher::new();
let prop = d.dispatch(&tree, WidgetId(9999), mouse_down());
assert_eq!(prop, Propagation::CONTINUE);
}
}