Skip to main content

kozan_core/events/
mouse_event.rs

1//! Mouse DOM events — Chrome: `blink/core/dom/events/mouse_event.h`.
2//!
3//! These are DOM-level events dispatched through the tree (capture -> target -> bubble).
4//! NOT the platform-level input structs from `input::mouse` (which carry `Instant`,
5//! physical coords, and are produced by the OS).
6//!
7//! The `EventHandler` converts `input::MouseButtonEvent` -> `MouseDownEvent` etc.
8//!
9//! # Naming convention
10//!
11//! All DOM events use clean W3C-standard names. Where a name collides with a
12//! platform-level type in `input::` (e.g., `MouseMoveEvent`), the module path
13//! disambiguates: `events::MouseMoveEvent` vs `input::MouseMoveEvent`.
14
15use crate::input::{Modifiers, MouseButton};
16use kozan_macros::Event;
17
18/// DOM `click` event — fired after mousedown + mouseup on the same target.
19///
20/// Chrome: `MouseEvent` with type `"click"`.
21#[derive(Debug, Clone, Event)]
22#[event(bubbles, cancelable)]
23#[non_exhaustive]
24pub struct ClickEvent {
25    /// X position in CSS pixels, relative to viewport origin.
26    pub x: f32,
27    /// Y position in CSS pixels, relative to viewport origin.
28    pub y: f32,
29    /// Which button triggered this click.
30    pub button: MouseButton,
31    /// Modifier keys held during the click.
32    pub modifiers: Modifiers,
33}
34
35/// DOM `dblclick` event — fired on double-click.
36///
37/// Chrome: `MouseEvent` with type `"dblclick"`.
38#[derive(Debug, Clone, Event)]
39#[event(bubbles, cancelable)]
40#[non_exhaustive]
41pub struct DblClickEvent {
42    pub x: f32,
43    pub y: f32,
44    pub button: MouseButton,
45    pub modifiers: Modifiers,
46}
47
48/// DOM `mousedown` event — fired when a button is pressed.
49///
50/// Chrome: `MouseEvent` with type `"mousedown"`.
51#[derive(Debug, Clone, Event)]
52#[event(bubbles, cancelable)]
53#[non_exhaustive]
54pub struct MouseDownEvent {
55    pub x: f32,
56    pub y: f32,
57    pub button: MouseButton,
58    pub modifiers: Modifiers,
59}
60
61/// DOM `mouseup` event — fired when a button is released.
62///
63/// Chrome: `MouseEvent` with type `"mouseup"`.
64#[derive(Debug, Clone, Event)]
65#[event(bubbles, cancelable)]
66#[non_exhaustive]
67pub struct MouseUpEvent {
68    pub x: f32,
69    pub y: f32,
70    pub button: MouseButton,
71    pub modifiers: Modifiers,
72}
73
74/// DOM `mousemove` event — fired when the cursor moves over an element.
75///
76/// Chrome: `MouseEvent` with type `"mousemove"`.
77///
78/// Note: shares its name with `input::MouseMoveEvent` (the platform-level type).
79/// Use the module path to disambiguate when both are in scope.
80#[derive(Debug, Clone, Event)]
81#[event(bubbles, cancelable)]
82#[non_exhaustive]
83pub struct MouseMoveEvent {
84    pub x: f32,
85    pub y: f32,
86    pub modifiers: Modifiers,
87}
88
89/// DOM `mouseenter` event — fired when the cursor enters an element.
90/// Does NOT bubble (unlike `MouseOverEvent`).
91///
92/// Chrome: `MouseEvent` with type `"mouseenter"`.
93#[derive(Debug, Clone, Event)]
94#[event()]
95#[non_exhaustive]
96pub struct MouseEnterEvent {
97    pub x: f32,
98    pub y: f32,
99    pub modifiers: Modifiers,
100}
101
102/// DOM `mouseleave` event — fired when the cursor leaves an element.
103/// Does NOT bubble (unlike `MouseOutEvent`).
104///
105/// Chrome: `MouseEvent` with type `"mouseleave"`.
106/// Always carries the last known cursor position (like Chrome's `clientX`/`clientY`).
107#[derive(Debug, Clone, Event)]
108#[event()]
109#[non_exhaustive]
110pub struct MouseLeaveEvent {
111    /// Last cursor X position when leaving.
112    pub x: f32,
113    /// Last cursor Y position when leaving.
114    pub y: f32,
115    pub modifiers: Modifiers,
116}
117
118/// DOM `mouseover` event — fired when the cursor enters an element or its children.
119/// Bubbles (unlike `MouseEnterEvent`).
120///
121/// Chrome: `MouseEvent` with type `"mouseover"`.
122#[derive(Debug, Clone, Event)]
123#[event(bubbles)]
124#[non_exhaustive]
125pub struct MouseOverEvent {
126    pub x: f32,
127    pub y: f32,
128    pub modifiers: Modifiers,
129}
130
131/// DOM `mouseout` event — fired when the cursor leaves an element or enters a child.
132/// Bubbles (unlike `MouseLeaveEvent`).
133///
134/// Chrome: `MouseEvent` with type `"mouseout"`.
135#[derive(Debug, Clone, Event)]
136#[event(bubbles)]
137#[non_exhaustive]
138pub struct MouseOutEvent {
139    pub x: f32,
140    pub y: f32,
141    pub modifiers: Modifiers,
142}
143
144/// DOM `contextmenu` event — fired on right-click.
145///
146/// Chrome: `MouseEvent` with type `"contextmenu"`.
147#[derive(Debug, Clone, Event)]
148#[event(bubbles, cancelable)]
149#[non_exhaustive]
150pub struct ContextMenuEvent {
151    pub x: f32,
152    pub y: f32,
153    pub modifiers: Modifiers,
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use crate::events::{Bubbles, Cancelable, Event};
160
161    #[test]
162    fn click_event_properties() {
163        let evt = ClickEvent {
164            x: 10.0,
165            y: 20.0,
166            button: MouseButton::Left,
167            modifiers: Modifiers::EMPTY,
168        };
169        assert_eq!(evt.bubbles(), Bubbles::Yes);
170        assert_eq!(evt.cancelable(), Cancelable::Yes);
171        assert_eq!(evt.x, 10.0);
172    }
173
174    #[test]
175    fn mouseenter_does_not_bubble() {
176        let evt = MouseEnterEvent {
177            x: 0.0,
178            y: 0.0,
179            modifiers: Modifiers::EMPTY,
180        };
181        assert_eq!(evt.bubbles(), Bubbles::No);
182        assert_eq!(evt.cancelable(), Cancelable::No);
183    }
184
185    #[test]
186    fn mouseleave_carries_position() {
187        let evt = MouseLeaveEvent {
188            x: 150.0,
189            y: 200.0,
190            modifiers: Modifiers::EMPTY,
191        };
192        assert_eq!(evt.bubbles(), Bubbles::No);
193        assert_eq!(evt.x, 150.0);
194        assert_eq!(evt.y, 200.0);
195    }
196
197    #[test]
198    fn mouseover_bubbles() {
199        let evt = MouseOverEvent {
200            x: 0.0,
201            y: 0.0,
202            modifiers: Modifiers::EMPTY,
203        };
204        assert_eq!(evt.bubbles(), Bubbles::Yes);
205        assert_eq!(evt.cancelable(), Cancelable::No);
206    }
207
208    #[test]
209    fn context_menu_bubbles_and_cancelable() {
210        let evt = ContextMenuEvent {
211            x: 50.0,
212            y: 75.0,
213            modifiers: Modifiers::EMPTY.with_ctrl(),
214        };
215        assert_eq!(evt.bubbles(), Bubbles::Yes);
216        assert_eq!(evt.cancelable(), Cancelable::Yes);
217    }
218
219    #[test]
220    fn event_as_any_downcast() {
221        let evt = ClickEvent {
222            x: 1.0,
223            y: 2.0,
224            button: MouseButton::Left,
225            modifiers: Modifiers::EMPTY,
226        };
227        let any = evt.as_any();
228        let downcasted = any.downcast_ref::<ClickEvent>().unwrap();
229        assert_eq!(downcasted.x, 1.0);
230        assert_eq!(downcasted.y, 2.0);
231    }
232}