dominator2/
events.rs

1use crate::traits::StaticEvent;
2use wasm_bindgen::JsCast;
3use web_sys::{EventTarget, HtmlInputElement, HtmlTextAreaElement};
4
5#[cfg(feature = "nightly")]
6pub struct Event<const NAME: &'static str, T> {
7    event: T,
8}
9
10#[cfg(feature = "nightly")]
11impl<T, const NAME: &'static str> StaticEvent for Event<NAME, T>
12where
13    T: JsCast,
14{
15    const EVENT_TYPE: &'static str = NAME;
16
17    #[inline]
18    fn unchecked_from_event(event: web_sys::Event) -> Self {
19        Self {
20            // TODO use unchecked_into in release mode ?
21            event: event.dyn_into().unwrap(),
22        }
23    }
24}
25
26// TODO code duplication
27// TODO implement the rest of the methods
28#[cfg(feature = "nightly")]
29impl<T, const NAME: &'static str> Event<NAME, T>
30where
31    T: AsRef<web_sys::Event>,
32{
33    #[inline]
34    pub fn prevent_default(&self) {
35        self.event.as_ref().prevent_default();
36    }
37
38    #[inline]
39    pub fn target(&self) -> Option<EventTarget> {
40        self.event.as_ref().target()
41    }
42
43    #[inline]
44    pub fn dyn_target<A>(&self) -> Option<A>
45    where
46        A: JsCast,
47    {
48        self.target()?.dyn_into().ok()
49    }
50}
51
52macro_rules! make_event {
53    ($name:ident, $type:literal => $event:path) => {
54        #[derive(Debug)]
55        pub struct $name {
56            event: $event,
57        }
58
59        impl StaticEvent for $name {
60            const EVENT_TYPE: &'static str = $type;
61
62            #[inline]
63            fn unchecked_from_event(event: web_sys::Event) -> Self {
64                Self {
65                    event: event.unchecked_into(),
66                }
67            }
68        }
69
70        impl $name {
71            #[inline]
72            pub fn prevent_default(&self) {
73                self.event.prevent_default();
74            }
75
76            #[inline]
77            pub fn stop_propagation(&self) {
78                self.event.stop_propagation();
79            }
80
81            #[inline]
82            pub fn stop_immediate_propagation(&self) {
83                self.event.stop_immediate_propagation();
84            }
85
86            #[inline]
87            pub fn target(&self) -> Option<EventTarget> {
88                self.event.target()
89            }
90
91            #[inline]
92            pub fn dyn_target<A>(&self) -> Option<A>
93            where
94                A: JsCast,
95            {
96                self.target()?.dyn_into().ok()
97            }
98        }
99    };
100}
101
102#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
103pub enum MouseButton {
104    Left,
105    Middle,
106    Right,
107    Button4,
108    Button5,
109}
110
111macro_rules! make_mouse_event {
112    ($name:ident, $type:literal => $event:path) => {
113        make_event!($name, $type => $event);
114
115        impl $name {
116            #[inline] pub fn x(&self) -> i32 { self.event.client_x() }
117            #[inline] pub fn y(&self) -> i32 { self.event.client_y() }
118
119            #[inline] pub fn movement_x(&self) -> i32 { self.event.movement_x() }
120            #[inline] pub fn movement_y(&self) -> i32 { self.event.movement_y() }
121
122            #[inline] pub fn offset_x(&self) -> i32 { self.event.offset_x() }
123            #[inline] pub fn offset_y(&self) -> i32 { self.event.offset_y() }
124
125            #[inline] pub fn page_x(&self) -> i32 { self.event.page_x() }
126            #[inline] pub fn page_y(&self) -> i32 { self.event.page_y() }
127
128            #[inline] pub fn screen_x(&self) -> i32 { self.event.screen_x() }
129            #[inline] pub fn screen_y(&self) -> i32 { self.event.screen_y() }
130
131            #[inline] pub fn ctrl_key(&self) -> bool { self.event.ctrl_key() || self.event.meta_key() }
132            #[inline] pub fn shift_key(&self) -> bool { self.event.shift_key() }
133            #[inline] pub fn alt_key(&self) -> bool { self.event.alt_key() }
134
135            // TODO maybe deprecate these ?
136            #[inline] pub fn mouse_x(&self) -> i32 { self.event.client_x() }
137            #[inline] pub fn mouse_y(&self) -> i32 { self.event.client_y() }
138
139            pub fn button(&self) -> MouseButton {
140                match self.event.button() {
141                    0 => MouseButton::Left,
142                    1 => MouseButton::Middle,
143                    2 => MouseButton::Right,
144                    3 => MouseButton::Button4,
145                    4 => MouseButton::Button5,
146                    _ => unreachable!("Unexpected MouseEvent.button value"),
147                }
148            }
149        }
150    };
151}
152
153macro_rules! make_keyboard_event {
154    ($name:ident, $type:literal) => {
155        make_event!($name, $type => web_sys::KeyboardEvent);
156
157        impl $name {
158            // TODO return enum or something
159            #[inline] pub fn key(&self) -> String { self.event.key() }
160
161            #[inline] pub fn ctrl_key(&self) -> bool { self.event.ctrl_key() || self.event.meta_key() }
162            #[inline] pub fn shift_key(&self) -> bool { self.event.shift_key() }
163            #[inline] pub fn alt_key(&self) -> bool { self.event.alt_key() }
164            #[inline] pub fn repeat(&self) -> bool { self.event.repeat() }
165        }
166    };
167}
168
169macro_rules! make_focus_event {
170    ($name:ident, $type:literal) => {
171        make_event!($name, $type => web_sys::FocusEvent);
172    };
173}
174
175macro_rules! make_drag_event {
176    ($name:ident, $type:literal) => {
177        make_mouse_event!($name, $type => web_sys::DragEvent);
178
179        impl $name {
180            #[inline] pub fn data_transfer(&self) -> Option<web_sys::DataTransfer> { self.event.data_transfer() }
181        }
182    };
183}
184
185macro_rules! make_input_event {
186    ($name:ident, $type:literal) => {
187        make_event!($name, $type => web_sys::InputEvent);
188
189        impl $name {
190            #[inline] pub fn data(&self) -> Option<String> { self.event.data() }
191        }
192    };
193}
194
195make_mouse_event!(Click, "click" => web_sys::MouseEvent);
196make_mouse_event!(MouseDown, "mousedown" => web_sys::MouseEvent);
197make_mouse_event!(MouseUp, "mouseup" => web_sys::MouseEvent);
198make_mouse_event!(MouseMove, "mousemove" => web_sys::MouseEvent);
199make_mouse_event!(MouseEnter, "mouseenter" => web_sys::MouseEvent);
200make_mouse_event!(MouseLeave, "mouseleave" => web_sys::MouseEvent);
201make_mouse_event!(DoubleClick, "dblclick" => web_sys::MouseEvent);
202make_mouse_event!(ContextMenu, "contextmenu" => web_sys::MouseEvent);
203
204make_keyboard_event!(KeyDown, "keydown");
205make_keyboard_event!(KeyUp, "keyup");
206
207make_focus_event!(Focus, "focus");
208make_focus_event!(Blur, "blur");
209
210make_drag_event!(DragStart, "dragstart");
211make_drag_event!(Drag, "drag");
212make_drag_event!(DragEnd, "dragend");
213make_drag_event!(DragOver, "dragover");
214make_drag_event!(DragEnter, "dragenter");
215make_drag_event!(DragLeave, "dragleave");
216make_drag_event!(Drop, "drop");
217
218make_input_event!(Input, "input");
219make_input_event!(BeforeInput, "beforeinput");
220
221make_event!(Load, "load" => web_sys::Event);
222make_event!(Scroll, "scroll" => web_sys::Event);
223make_event!(Resize, "resize" => web_sys::UiEvent);
224
225impl Input {
226    // TODO should this work on other types as well ?
227    #[deprecated(since = "0.5.19", note = "Use with_node instead")]
228    pub fn value(&self) -> Option<String> {
229        let target = self.target()?;
230
231        if let Some(target) = target.dyn_ref::<HtmlInputElement>() {
232            // TODO check the <input> element's type ?
233            Some(target.value())
234        } else if let Some(target) = target.dyn_ref::<HtmlTextAreaElement>() {
235            Some(target.value())
236        } else {
237            None
238        }
239    }
240}
241
242make_event!(Change, "change" => web_sys::Event);
243
244// TODO add in a value method as well, the same as Input::value
245impl Change {
246    // https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement
247    pub fn checked(&self) -> Option<bool> {
248        let target = self.dyn_target::<HtmlInputElement>()?;
249
250        match target.type_().as_str() {
251            "checkbox" | "radio" => Some(target.checked()),
252            _ => None,
253        }
254    }
255}