⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/browser/
events.rs

1//! DOM Event System
2//!
3//! Implements W3C-style DOM events with capture/bubble propagation,
4//! hit testing against layout boxes, and event listener management.
5//! All coordinates use 26.6 fixed-point arithmetic (i32).
6
7#![allow(dead_code)]
8
9use alloc::{collections::BTreeMap, vec::Vec};
10
11// ---------------------------------------------------------------------------
12// Type aliases for Phase A interop
13// ---------------------------------------------------------------------------
14
15/// Node identifier (arena index into DOM tree)
16pub type NodeId = usize;
17
18/// 26.6 fixed-point coordinate (from Phase A layout module)
19pub type FixedPoint = i32;
20
21/// Shift amount for 26.6 fixed-point
22pub const FP_SHIFT: i32 = 6;
23
24/// Convert integer to 26.6 fixed-point
25#[inline]
26pub const fn fp_from_int(v: i32) -> FixedPoint {
27    v << FP_SHIFT
28}
29
30/// Convert 26.6 fixed-point to integer (truncate)
31#[inline]
32pub const fn fp_to_int(v: FixedPoint) -> i32 {
33    v >> FP_SHIFT
34}
35
36// ---------------------------------------------------------------------------
37// Event types and phases
38// ---------------------------------------------------------------------------
39
40/// DOM event type enumeration
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum EventType {
43    Click,
44    MouseDown,
45    MouseUp,
46    MouseMove,
47    MouseOver,
48    MouseOut,
49    KeyDown,
50    KeyUp,
51    KeyPress,
52    Focus,
53    Blur,
54    Submit,
55    Input,
56    Change,
57    Scroll,
58    Load,
59    Unload,
60    Resize,
61}
62
63impl EventType {
64    /// Whether this event type bubbles by default
65    pub fn bubbles_default(self) -> bool {
66        match self {
67            Self::Click
68            | Self::MouseDown
69            | Self::MouseUp
70            | Self::MouseMove
71            | Self::MouseOver
72            | Self::MouseOut
73            | Self::KeyDown
74            | Self::KeyUp
75            | Self::KeyPress
76            | Self::Input
77            | Self::Change
78            | Self::Scroll
79            | Self::Submit => true,
80            Self::Focus | Self::Blur | Self::Load | Self::Unload | Self::Resize => false,
81        }
82    }
83
84    /// Whether this event type is cancelable by default
85    pub fn cancelable_default(self) -> bool {
86        matches!(
87            self,
88            Self::Click
89                | Self::MouseDown
90                | Self::MouseUp
91                | Self::KeyDown
92                | Self::KeyPress
93                | Self::Submit
94        )
95    }
96}
97
98/// Event propagation phase
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
100pub enum EventPhase {
101    /// Not dispatched yet
102    #[default]
103    None,
104    /// Capture phase: root to target
105    Capture,
106    /// At the target element
107    Target,
108    /// Bubble phase: target to root
109    Bubble,
110}
111
112// ---------------------------------------------------------------------------
113// Event struct
114// ---------------------------------------------------------------------------
115
116/// A DOM event
117#[derive(Debug, Clone)]
118pub struct Event {
119    /// Type of the event
120    pub event_type: EventType,
121    /// Target node (the node the event was dispatched on)
122    pub target: NodeId,
123    /// Current target during propagation
124    pub current_target: NodeId,
125    /// Current propagation phase
126    pub phase: EventPhase,
127    /// Whether the event bubbles
128    pub bubbles: bool,
129    /// Whether the event is cancelable
130    pub cancelable: bool,
131    /// Whether preventDefault() was called
132    pub default_prevented: bool,
133    /// Whether stopPropagation() was called
134    pub propagation_stopped: bool,
135    /// Whether stopImmediatePropagation() was called
136    pub immediate_propagation_stopped: bool,
137    /// Mouse X position (pixel coordinates)
138    pub mouse_x: i32,
139    /// Mouse Y position (pixel coordinates)
140    pub mouse_y: i32,
141    /// Mouse button (0=left, 1=middle, 2=right)
142    pub button: u8,
143    /// Keyboard scancode
144    pub key_code: u32,
145    /// Character value for KeyPress
146    pub char_code: u32,
147    /// Modifier keys bitmask (1=shift, 2=ctrl, 4=alt, 8=meta)
148    pub modifiers: u8,
149}
150
151impl Event {
152    /// Create a new event with defaults from its type
153    pub fn new(event_type: EventType, target: NodeId) -> Self {
154        Self {
155            event_type,
156            target,
157            current_target: target,
158            phase: EventPhase::None,
159            bubbles: event_type.bubbles_default(),
160            cancelable: event_type.cancelable_default(),
161            default_prevented: false,
162            propagation_stopped: false,
163            immediate_propagation_stopped: false,
164            mouse_x: 0,
165            mouse_y: 0,
166            button: 0,
167            key_code: 0,
168            char_code: 0,
169            modifiers: 0,
170        }
171    }
172
173    /// Create a mouse event
174    pub fn mouse(event_type: EventType, target: NodeId, x: i32, y: i32, button: u8) -> Self {
175        let mut ev = Self::new(event_type, target);
176        ev.mouse_x = x;
177        ev.mouse_y = y;
178        ev.button = button;
179        ev
180    }
181
182    /// Create a keyboard event
183    pub fn keyboard(event_type: EventType, target: NodeId, key_code: u32, char_code: u32) -> Self {
184        let mut ev = Self::new(event_type, target);
185        ev.key_code = key_code;
186        ev.char_code = char_code;
187        ev
188    }
189
190    /// Call preventDefault()
191    pub fn prevent_default(&mut self) {
192        if self.cancelable {
193            self.default_prevented = true;
194        }
195    }
196
197    /// Call stopPropagation()
198    pub fn stop_propagation(&mut self) {
199        self.propagation_stopped = true;
200    }
201
202    /// Call stopImmediatePropagation()
203    pub fn stop_immediate_propagation(&mut self) {
204        self.propagation_stopped = true;
205        self.immediate_propagation_stopped = true;
206    }
207
208    /// Check if shift key is held
209    pub fn shift_key(&self) -> bool {
210        self.modifiers & 1 != 0
211    }
212
213    /// Check if ctrl key is held
214    pub fn ctrl_key(&self) -> bool {
215        self.modifiers & 2 != 0
216    }
217
218    /// Check if alt key is held
219    pub fn alt_key(&self) -> bool {
220        self.modifiers & 4 != 0
221    }
222
223    /// Check if meta key is held
224    pub fn meta_key(&self) -> bool {
225        self.modifiers & 8 != 0
226    }
227}
228
229// ---------------------------------------------------------------------------
230// Event Listener
231// ---------------------------------------------------------------------------
232
233/// An event listener registration
234#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct EventListener {
236    /// Type of event to listen for
237    pub event_type: EventType,
238    /// Callback identifier (index into JS function table)
239    pub callback_id: usize,
240    /// Whether to listen during capture phase
241    pub use_capture: bool,
242}
243
244impl EventListener {
245    pub fn new(event_type: EventType, callback_id: usize, use_capture: bool) -> Self {
246        Self {
247            event_type,
248            callback_id,
249            use_capture,
250        }
251    }
252}
253
254// ---------------------------------------------------------------------------
255// Layout box reference for hit testing
256// ---------------------------------------------------------------------------
257
258/// Axis-aligned bounding box for a layout element (pixel coordinates)
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
260pub struct HitRect {
261    pub x: i32,
262    pub y: i32,
263    pub width: i32,
264    pub height: i32,
265    pub node_id: NodeId,
266}
267
268impl HitRect {
269    pub fn new(x: i32, y: i32, width: i32, height: i32, node_id: NodeId) -> Self {
270        Self {
271            x,
272            y,
273            width,
274            height,
275            node_id,
276        }
277    }
278
279    /// Check if point (px, py) is inside this rect
280    pub fn contains(&self, px: i32, py: i32) -> bool {
281        px >= self.x && px < self.x + self.width && py >= self.y && py < self.y + self.height
282    }
283}
284
285// ---------------------------------------------------------------------------
286// Node ancestry (for propagation path)
287// ---------------------------------------------------------------------------
288
289/// Simple tree structure for tracking parent-child relationships
290#[derive(Debug, Clone, Default)]
291pub struct NodeTree {
292    /// Parent of each node (None for root)
293    parents: BTreeMap<NodeId, NodeId>,
294}
295
296impl NodeTree {
297    pub fn new() -> Self {
298        Self {
299            parents: BTreeMap::new(),
300        }
301    }
302
303    /// Set the parent of a node
304    pub fn set_parent(&mut self, child: NodeId, parent: NodeId) {
305        self.parents.insert(child, parent);
306    }
307
308    /// Get the parent of a node
309    pub fn parent(&self, node: NodeId) -> Option<NodeId> {
310        self.parents.get(&node).copied()
311    }
312
313    /// Get the ancestor path from root to node (inclusive)
314    pub fn ancestor_path(&self, node: NodeId) -> Vec<NodeId> {
315        let mut path = Vec::new();
316        let mut current = node;
317        path.push(current);
318        while let Some(parent) = self.parents.get(&current) {
319            path.push(*parent);
320            current = *parent;
321        }
322        path.reverse();
323        path
324    }
325
326    /// Remove a node from the tree
327    pub fn remove(&mut self, node: NodeId) {
328        self.parents.remove(&node);
329    }
330}
331
332// ---------------------------------------------------------------------------
333// Event Dispatcher
334// ---------------------------------------------------------------------------
335
336/// Dispatches DOM events with capture/bubble propagation
337pub struct EventDispatcher {
338    /// Event listeners keyed by node ID
339    listeners: BTreeMap<NodeId, Vec<EventListener>>,
340    /// Node tree for propagation paths
341    node_tree: NodeTree,
342    /// Hit-test boxes for finding event targets
343    hit_boxes: Vec<HitRect>,
344    /// Callbacks that were invoked (callback_id, event snapshot)
345    invoked: Vec<(usize, EventType)>,
346}
347
348impl Default for EventDispatcher {
349    fn default() -> Self {
350        Self::new()
351    }
352}
353
354impl EventDispatcher {
355    pub fn new() -> Self {
356        Self {
357            listeners: BTreeMap::new(),
358            node_tree: NodeTree::new(),
359            hit_boxes: Vec::new(),
360            invoked: Vec::new(),
361        }
362    }
363
364    /// Access the node tree
365    pub fn node_tree(&self) -> &NodeTree {
366        &self.node_tree
367    }
368
369    /// Access the node tree mutably
370    pub fn node_tree_mut(&mut self) -> &mut NodeTree {
371        &mut self.node_tree
372    }
373
374    // -- Listener management --
375
376    /// Add an event listener to a node
377    pub fn add_event_listener(
378        &mut self,
379        node: NodeId,
380        event_type: EventType,
381        callback_id: usize,
382        use_capture: bool,
383    ) {
384        let listener = EventListener::new(event_type, callback_id, use_capture);
385        self.listeners.entry(node).or_default().push(listener);
386    }
387
388    /// Remove an event listener from a node
389    pub fn remove_event_listener(
390        &mut self,
391        node: NodeId,
392        event_type: EventType,
393        callback_id: usize,
394        use_capture: bool,
395    ) -> bool {
396        if let Some(list) = self.listeners.get_mut(&node) {
397            let before = list.len();
398            list.retain(|l| {
399                !(l.event_type == event_type
400                    && l.callback_id == callback_id
401                    && l.use_capture == use_capture)
402            });
403            list.len() < before
404        } else {
405            false
406        }
407    }
408
409    /// Get listeners for a node
410    pub fn listeners_for(&self, node: NodeId) -> &[EventListener] {
411        self.listeners
412            .get(&node)
413            .map(|v| v.as_slice())
414            .unwrap_or(&[])
415    }
416
417    /// Clear all listeners for a node
418    pub fn clear_listeners(&mut self, node: NodeId) {
419        self.listeners.remove(&node);
420    }
421
422    // -- Hit-test boxes --
423
424    /// Set the hit-test boxes (rebuilt after layout)
425    pub fn set_hit_boxes(&mut self, boxes: Vec<HitRect>) {
426        self.hit_boxes = boxes;
427    }
428
429    /// Add a single hit-test box
430    pub fn add_hit_box(&mut self, rect: HitRect) {
431        self.hit_boxes.push(rect);
432    }
433
434    /// Clear hit-test boxes
435    pub fn clear_hit_boxes(&mut self) {
436        self.hit_boxes.clear();
437    }
438
439    /// Hit test: find the frontmost (last-drawn) node at pixel coordinates.
440    /// Returns None if no node at that position.
441    pub fn hit_test(&self, x: i32, y: i32) -> Option<NodeId> {
442        // Later boxes are drawn on top, so iterate in reverse
443        for rect in self.hit_boxes.iter().rev() {
444            if rect.contains(x, y) {
445                return Some(rect.node_id);
446            }
447        }
448        None
449    }
450
451    // -- Dispatch --
452
453    /// Get the list of invoked callbacks (callback_id, event_type) from last
454    /// dispatch
455    pub fn take_invoked(&mut self) -> Vec<(usize, EventType)> {
456        core::mem::take(&mut self.invoked)
457    }
458
459    /// Dispatch an event through the capture → target → bubble phases.
460    /// Returns true if the default action should be prevented.
461    pub fn dispatch(&mut self, event: &mut Event) -> bool {
462        // Build propagation path (root → target)
463        let path = self.node_tree.ancestor_path(event.target);
464        if path.is_empty() {
465            return event.default_prevented;
466        }
467
468        let target_idx = path.len() - 1;
469
470        // -- Capture phase (root → target, exclusive of target) --
471        event.phase = EventPhase::Capture;
472        for &node in &path[..target_idx] {
473            if event.propagation_stopped {
474                break;
475            }
476            event.current_target = node;
477            self.invoke_listeners(node, event, true);
478        }
479
480        // -- Target phase --
481        if !event.propagation_stopped {
482            event.phase = EventPhase::Target;
483            event.current_target = event.target;
484            // At target, invoke both capture and bubble listeners
485            self.invoke_listeners(event.target, event, true);
486            if !event.immediate_propagation_stopped {
487                self.invoke_listeners(event.target, event, false);
488            }
489        }
490
491        // -- Bubble phase (target → root, exclusive of target) --
492        if event.bubbles && !event.propagation_stopped {
493            event.phase = EventPhase::Bubble;
494            for &node in path[..target_idx].iter().rev() {
495                if event.propagation_stopped {
496                    break;
497                }
498                event.current_target = node;
499                self.invoke_listeners(node, event, false);
500            }
501        }
502
503        event.default_prevented
504    }
505
506    /// Invoke matching listeners on a node.
507    /// In a real browser, this would call into the JS VM. Here we record
508    /// the invocations for later processing by the ScriptEngine.
509    fn invoke_listeners(&mut self, node: NodeId, event: &Event, capture: bool) {
510        // Clone listener list to avoid borrow conflicts
511        let listeners: Vec<EventListener> = self.listeners.get(&node).cloned().unwrap_or_default();
512
513        for listener in &listeners {
514            if event.immediate_propagation_stopped {
515                break;
516            }
517            if listener.event_type != event.event_type {
518                continue;
519            }
520            // During capture phase, only invoke capture listeners
521            // During bubble phase, only invoke bubble listeners
522            // During target phase, we call this twice (once for each)
523            if event.phase != EventPhase::Target && listener.use_capture != capture {
524                continue;
525            }
526            if event.phase == EventPhase::Target && listener.use_capture != capture {
527                continue;
528            }
529            self.invoked.push((listener.callback_id, event.event_type));
530        }
531    }
532
533    /// Convenience: dispatch a mouse click at pixel coordinates.
534    /// Performs hit-test, then dispatches Click event.
535    /// Returns (target_node, default_prevented) or None if miss.
536    pub fn dispatch_click(&mut self, x: i32, y: i32, button: u8) -> Option<(NodeId, bool)> {
537        let target = self.hit_test(x, y)?;
538        let mut event = Event::mouse(EventType::Click, target, x, y, button);
539        let prevented = self.dispatch(&mut event);
540        Some((target, prevented))
541    }
542
543    /// Convenience: dispatch a mouse move event
544    pub fn dispatch_mouse_move(&mut self, x: i32, y: i32) -> Option<(NodeId, bool)> {
545        let target = self.hit_test(x, y)?;
546        let mut event = Event::mouse(EventType::MouseMove, target, x, y, 0);
547        let prevented = self.dispatch(&mut event);
548        Some((target, prevented))
549    }
550
551    /// Convenience: dispatch a keyboard event to a focused node
552    pub fn dispatch_key(
553        &mut self,
554        target: NodeId,
555        event_type: EventType,
556        key_code: u32,
557        char_code: u32,
558        modifiers: u8,
559    ) -> bool {
560        let mut event = Event::keyboard(event_type, target, key_code, char_code);
561        event.modifiers = modifiers;
562        self.dispatch(&mut event)
563    }
564}
565
566// ---------------------------------------------------------------------------
567// Tests
568// ---------------------------------------------------------------------------
569
570#[cfg(test)]
571mod tests {
572    #[allow(unused_imports)]
573    use alloc::vec;
574
575    use super::*;
576
577    fn setup_tree() -> EventDispatcher {
578        // Build a simple tree: 0 (root) -> 1 -> 2 (leaf)
579        let mut d = EventDispatcher::new();
580        d.node_tree_mut().set_parent(1, 0);
581        d.node_tree_mut().set_parent(2, 1);
582        d
583    }
584
585    #[test]
586    fn test_event_type_bubbles() {
587        assert!(EventType::Click.bubbles_default());
588        assert!(!EventType::Focus.bubbles_default());
589        assert!(!EventType::Load.bubbles_default());
590        assert!(EventType::KeyDown.bubbles_default());
591    }
592
593    #[test]
594    fn test_event_type_cancelable() {
595        assert!(EventType::Click.cancelable_default());
596        assert!(!EventType::MouseMove.cancelable_default());
597        assert!(EventType::Submit.cancelable_default());
598    }
599
600    #[test]
601    fn test_event_creation() {
602        let ev = Event::new(EventType::Click, 5);
603        assert_eq!(ev.event_type, EventType::Click);
604        assert_eq!(ev.target, 5);
605        assert!(ev.bubbles);
606        assert!(ev.cancelable);
607        assert!(!ev.default_prevented);
608    }
609
610    #[test]
611    fn test_event_mouse() {
612        let ev = Event::mouse(EventType::MouseDown, 3, 100, 200, 1);
613        assert_eq!(ev.mouse_x, 100);
614        assert_eq!(ev.mouse_y, 200);
615        assert_eq!(ev.button, 1);
616        assert_eq!(ev.target, 3);
617    }
618
619    #[test]
620    fn test_event_keyboard() {
621        let ev = Event::keyboard(EventType::KeyDown, 2, 13, 0);
622        assert_eq!(ev.key_code, 13);
623        assert_eq!(ev.char_code, 0);
624    }
625
626    #[test]
627    fn test_prevent_default() {
628        let mut ev = Event::new(EventType::Click, 0);
629        assert!(!ev.default_prevented);
630        ev.prevent_default();
631        assert!(ev.default_prevented);
632    }
633
634    #[test]
635    fn test_prevent_default_non_cancelable() {
636        let mut ev = Event::new(EventType::MouseMove, 0);
637        assert!(!ev.cancelable);
638        ev.prevent_default();
639        assert!(!ev.default_prevented);
640    }
641
642    #[test]
643    fn test_stop_propagation() {
644        let mut ev = Event::new(EventType::Click, 0);
645        ev.stop_propagation();
646        assert!(ev.propagation_stopped);
647        assert!(!ev.immediate_propagation_stopped);
648    }
649
650    #[test]
651    fn test_stop_immediate_propagation() {
652        let mut ev = Event::new(EventType::Click, 0);
653        ev.stop_immediate_propagation();
654        assert!(ev.propagation_stopped);
655        assert!(ev.immediate_propagation_stopped);
656    }
657
658    #[test]
659    fn test_modifier_keys() {
660        let mut ev = Event::new(EventType::KeyDown, 0);
661        ev.modifiers = 0b1111;
662        assert!(ev.shift_key());
663        assert!(ev.ctrl_key());
664        assert!(ev.alt_key());
665        assert!(ev.meta_key());
666
667        ev.modifiers = 0;
668        assert!(!ev.shift_key());
669        assert!(!ev.ctrl_key());
670    }
671
672    #[test]
673    fn test_hit_rect_contains() {
674        let r = HitRect::new(10, 20, 100, 50, 0);
675        assert!(r.contains(10, 20));
676        assert!(r.contains(50, 40));
677        assert!(r.contains(109, 69));
678        assert!(!r.contains(110, 20));
679        assert!(!r.contains(10, 70));
680        assert!(!r.contains(9, 20));
681    }
682
683    #[test]
684    fn test_node_tree_ancestor_path() {
685        let mut tree = NodeTree::new();
686        tree.set_parent(1, 0);
687        tree.set_parent(2, 1);
688        tree.set_parent(3, 1);
689
690        let path = tree.ancestor_path(2);
691        assert_eq!(path, vec![0, 1, 2]);
692
693        let path = tree.ancestor_path(0);
694        assert_eq!(path, vec![0]);
695    }
696
697    #[test]
698    fn test_add_remove_listener() {
699        let mut d = EventDispatcher::new();
700        d.add_event_listener(1, EventType::Click, 42, false);
701        assert_eq!(d.listeners_for(1).len(), 1);
702
703        let removed = d.remove_event_listener(1, EventType::Click, 42, false);
704        assert!(removed);
705        assert_eq!(d.listeners_for(1).len(), 0);
706    }
707
708    #[test]
709    fn test_remove_nonexistent_listener() {
710        let mut d = EventDispatcher::new();
711        let removed = d.remove_event_listener(1, EventType::Click, 99, false);
712        assert!(!removed);
713    }
714
715    #[test]
716    fn test_hit_test() {
717        let mut d = EventDispatcher::new();
718        d.add_hit_box(HitRect::new(0, 0, 800, 600, 0));
719        d.add_hit_box(HitRect::new(10, 10, 100, 50, 1));
720        d.add_hit_box(HitRect::new(20, 20, 30, 30, 2));
721
722        // Frontmost box at (25, 25) is node 2
723        assert_eq!(d.hit_test(25, 25), Some(2));
724        // At (5, 5) only root box
725        assert_eq!(d.hit_test(5, 5), Some(0));
726        // Outside all boxes
727        assert_eq!(d.hit_test(900, 900), None);
728    }
729
730    #[test]
731    fn test_dispatch_capture_bubble() {
732        let mut d = setup_tree();
733        // Listeners: capture on root(0), bubble on middle(1), bubble on leaf(2)
734        d.add_event_listener(0, EventType::Click, 100, true);
735        d.add_event_listener(1, EventType::Click, 101, false);
736        d.add_event_listener(2, EventType::Click, 102, false);
737
738        let mut ev = Event::new(EventType::Click, 2);
739        d.dispatch(&mut ev);
740
741        let invoked = d.take_invoked();
742        // Capture on 0, then target on 2, then bubble on 1
743        assert_eq!(invoked.len(), 3);
744        assert_eq!(invoked[0].0, 100); // capture on root
745        assert_eq!(invoked[1].0, 102); // target on leaf
746        assert_eq!(invoked[2].0, 101); // bubble on middle
747    }
748
749    #[test]
750    fn test_dispatch_stop_propagation() {
751        let mut d = setup_tree();
752        d.add_event_listener(0, EventType::Click, 100, true);
753        d.add_event_listener(1, EventType::Click, 101, true);
754        d.add_event_listener(2, EventType::Click, 102, false);
755
756        // Stop at root capture
757        let mut ev = Event::new(EventType::Click, 2);
758        ev.propagation_stopped = false;
759        d.dispatch(&mut ev);
760        let invoked = d.take_invoked();
761        // Root capture fires, then middle capture, then target
762        assert_eq!(invoked.len(), 3);
763    }
764
765    #[test]
766    fn test_dispatch_no_bubble() {
767        let mut d = setup_tree();
768        d.add_event_listener(0, EventType::Focus, 100, false);
769        d.add_event_listener(2, EventType::Focus, 102, false);
770
771        let mut ev = Event::new(EventType::Focus, 2);
772        assert!(!ev.bubbles);
773        d.dispatch(&mut ev);
774
775        let invoked = d.take_invoked();
776        // Only target fires (no bubble to root)
777        assert_eq!(invoked.len(), 1);
778        assert_eq!(invoked[0].0, 102);
779    }
780
781    #[test]
782    fn test_dispatch_click_convenience() {
783        let mut d = EventDispatcher::new();
784        d.node_tree_mut().set_parent(1, 0);
785        d.add_hit_box(HitRect::new(0, 0, 100, 100, 1));
786        d.add_event_listener(1, EventType::Click, 50, false);
787
788        let result = d.dispatch_click(50, 50, 0);
789        assert!(result.is_some());
790        let (target, prevented) = result.unwrap();
791        assert_eq!(target, 1);
792        assert!(!prevented);
793    }
794
795    #[test]
796    fn test_dispatch_click_miss() {
797        let d = &mut EventDispatcher::new();
798        d.add_hit_box(HitRect::new(0, 0, 10, 10, 0));
799        let result = d.dispatch_click(100, 100, 0);
800        assert!(result.is_none());
801    }
802
803    #[test]
804    fn test_dispatch_key() {
805        let mut d = EventDispatcher::new();
806        d.add_event_listener(5, EventType::KeyDown, 77, false);
807
808        let prevented = d.dispatch_key(5, EventType::KeyDown, 65, 0, 0);
809        assert!(!prevented);
810        let invoked = d.take_invoked();
811        assert_eq!(invoked.len(), 1);
812        assert_eq!(invoked[0].0, 77);
813    }
814
815    #[test]
816    fn test_clear_listeners() {
817        let mut d = EventDispatcher::new();
818        d.add_event_listener(1, EventType::Click, 10, false);
819        d.add_event_listener(1, EventType::KeyDown, 11, false);
820        assert_eq!(d.listeners_for(1).len(), 2);
821        d.clear_listeners(1);
822        assert_eq!(d.listeners_for(1).len(), 0);
823    }
824
825    #[test]
826    fn test_multiple_listeners_same_node() {
827        let mut d = EventDispatcher::new();
828        d.add_event_listener(1, EventType::Click, 10, false);
829        d.add_event_listener(1, EventType::Click, 11, false);
830        d.add_event_listener(1, EventType::KeyDown, 12, false);
831
832        let mut ev = Event::new(EventType::Click, 1);
833        d.dispatch(&mut ev);
834        let invoked = d.take_invoked();
835        // Two Click listeners, not the KeyDown one
836        assert_eq!(invoked.len(), 2);
837    }
838
839    #[test]
840    fn test_fp_conversion() {
841        assert_eq!(fp_from_int(10), 640);
842        assert_eq!(fp_to_int(640), 10);
843        assert_eq!(fp_to_int(fp_from_int(42)), 42);
844    }
845
846    #[test]
847    fn test_event_phase_default() {
848        let phase = EventPhase::default();
849        assert_eq!(phase, EventPhase::None);
850    }
851
852    #[test]
853    fn test_node_tree_remove() {
854        let mut tree = NodeTree::new();
855        tree.set_parent(1, 0);
856        assert!(tree.parent(1).is_some());
857        tree.remove(1);
858        assert!(tree.parent(1).is_none());
859    }
860}