Skip to main content

wingy_core/
event.rs

1//! The event enums and assorted supporting types.
2use std::cell::LazyCell;
3use std::cmp::Ordering;
4use std::f64;
5use std::sync::{Arc, Mutex, Weak};
6
7use dpi::{PhysicalPosition, PhysicalSize};
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10use smol_str::SmolStr;
11
12use crate::Instant;
13use crate::data_transfer::{DataTransferId, TypedData};
14use crate::error::InternalError;
15use crate::event_loop::{AsyncRequestSerial, DndAction};
16use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
17#[cfg(doc)]
18use crate::window::Window;
19use crate::window::{ActivationToken, Theme};
20
21/// Describes the reason the event loop is resuming.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[non_exhaustive]
24pub enum StartCause {
25    /// Sent if the time specified by [`ControlFlow::WaitUntil`] has been reached. Contains the
26    /// moment the timeout was requested and the requested resume time. The actual resume time is
27    /// guaranteed to be equal to or after the requested resume time.
28    ///
29    /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
30    ResumeTimeReached { start: Instant, requested_resume: Instant },
31
32    /// Sent if the OS has new events to send to the window, after a wait was requested. Contains
33    /// the moment the wait was requested and the resume time, if requested.
34    WaitCancelled { start: Instant, requested_resume: Option<Instant> },
35
36    /// Sent if the event loop is being resumed after the loop's control flow was set to
37    /// [`ControlFlow::Poll`].
38    ///
39    /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
40    Poll,
41
42    /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized.
43    Init,
44}
45
46/// Describes an event from a [`Window`].
47#[derive(Debug, Clone, PartialEq)]
48#[non_exhaustive]
49pub enum WindowEvent {
50    /// The activation token was delivered back and now could be used.
51    ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken },
52
53    /// The size of the window's surface has changed.
54    ///
55    /// Contains the new dimensions of the surface (can also be retrieved with
56    /// [`Window::surface_size`]).
57    ///
58    /// This event will not necessarily be emitted upon window creation, query
59    /// [`Window::surface_size`] if you need to determine the surface's initial size.
60    ///
61    /// [`Window::surface_size`]: crate::window::Window::surface_size
62    SurfaceResized(PhysicalSize<u32>),
63
64    /// The position of the window has changed.
65    ///
66    /// Contains the window's new position in desktop coordinates (can also be retrieved with
67    /// [`Window::outer_position`]).
68    ///
69    /// ## Platform-specific
70    ///
71    /// - **iOS / Android / Web / Wayland:** Unsupported.
72    Moved(PhysicalPosition<i32>),
73
74    /// The window has been requested to close.
75    CloseRequested,
76
77    /// The window has been destroyed.
78    Destroyed,
79
80    /// A drag operation has entered the window.
81    ///
82    /// The user can use the `id` to read information about the incoming dragged data, and report
83    /// whether the operation is accepted or rejected back to the operating system (see
84    /// [`crate::event_loop::ActiveEventLoop::set_valid_dnd_actions`](`crate::event_loop::ActiveEventLoop::set_valid_dnd_actions`)).
85    ///
86    /// To read the data being dragged, see
87    /// [`crate::event_loop::ActiveEventLoop::fetch_data_transfer`](`crate::event_loop::ActiveEventLoop::fetch_data_transfer`).
88    DragEntered {
89        /// ID of the data transfer object, see
90        /// [`crate::event_loop::ActiveEventLoop::data_transfer`](`crate::event_loop::ActiveEventLoop::data_transfer`).
91        id: DataTransferId,
92        /// (x,y) coordinates in pixels relative to the top-left corner of the window.
93        ///
94        /// May be negative on some platforms if something is dragged over a window's decorations
95        /// (title bar, frame, etc).
96        ///
97        /// Some platforms will provide this on enter, others do not. If
98        /// [`crate::event_loop::ActiveEventLoop::set_valid_dnd_actions`](`crate::event_loop::ActiveEventLoop::set_valid_dnd_actions`)
99        /// is never called, the default state is for the drag operation to be rejected. The
100        /// position is provided here when available to allow the application to accept a drag
101        /// operation as soon as possible, preventing the cursor from flickering from rejected to
102        /// accepted.
103        position: Option<PhysicalPosition<f64>>,
104    },
105    /// The position of an ongoing drag operation has changed.
106    DragPosition {
107        /// ID of the data transfer object, see
108        /// [`crate::event_loop::ActiveEventLoop::data_transfer`](`crate::event_loop::ActiveEventLoop::data_transfer`).
109        id: DataTransferId,
110        /// (x,y) coordinates in pixels relative to the top-left corner of the window.
111        ///
112        /// May be negative on some platforms if something is dragged over a window's decorations
113        /// (title bar, frame, etc).
114        position: PhysicalPosition<f64>,
115        /// The drag action proposed by the OS, based on the actions supplied in
116        /// [`crate::event_loop::ActiveEventLoop::set_valid_dnd_actions`], the actions available on
117        /// the source, and the held modifier keys.
118        ///
119        /// This may be `None` if the backend has not supplied a valid action. On some platforms
120        /// (in particular, X11), the application is only informed of the proposed action once
121        /// the operation completes.
122        proposed_action: Option<DndAction>,
123    },
124    /// A drag operation has dropped file(s) on the window.
125    DragDropped {
126        /// ID of the data transfer object, see
127        /// [`crate::event_loop::ActiveEventLoop::data_transfer`].
128        id: DataTransferId,
129        /// The drag action proposed by the OS, based on the actions supplied in
130        /// [`crate::event_loop::ActiveEventLoop::set_valid_dnd_actions`], the actions available on
131        /// the source, and the held modifier keys.
132        ///
133        /// This may be `None` if the backend has not supplied a valid action. This is different
134        /// from the drag being canceled: the drag completed successfully, we just don't know
135        /// what action was selected.
136        proposed_action: Option<DndAction>,
137    },
138    /// A drag operation has been canceled or left the window.
139    DragLeft {
140        /// ID of the data transfer object, see
141        /// [`crate::event_loop::ActiveEventLoop::data_transfer`].
142        id: DataTransferId,
143    },
144    /// Data is available for a specific fetch request, see
145    /// [`fetch_data_transfer`](crate::event_loop::ActiveEventLoop::data_transfer).
146    ///
147    /// While wingy makes a best effort to only send this event precisely once, on some platforms it
148    /// may not be possible to uniquely determine the window that should receive it. In these
149    /// cases, wingy may dispatch the event to all  windows that have access to the data
150    /// transfer. If your application should only process this event once per data transfer, the
151    /// `serial` field can be used to deduplicate it.
152    DataTransferReceived {
153        /// ID of the data transfer object, see
154        /// [`crate::event_loop::ActiveEventLoop::data_transfer`].
155        id: DataTransferId,
156        /// Serial returned from `fetch_data_transfer`.
157        serial: AsyncRequestSerial,
158        /// The data for the transfer, with a specific type.
159        value: Arc<dyn TypedData>,
160    },
161
162    /// A drag operation started with `start_drag` has been dropped.
163    OutgoingDragDropped {
164        /// The ID returned from `start_drag`
165        id: DataTransferId,
166        /// The operation selected by the drop destination.
167        ///
168        /// This may be `None` if the backend has not supplied a valid action. This is different
169        /// from the drag being canceled: the drag completed successfully, we just don't know
170        /// what action was selected.
171        action: Option<DndAction>,
172    },
173    /// A drag operation started with `start_drag` has been canceled.
174    OutgoingDragCanceled {
175        /// The ID returned from `start_drag`
176        id: DataTransferId,
177    },
178
179    /// The window gained or lost focus.
180    ///
181    /// The parameter is true if the window has gained focus, and false if it has lost focus.
182    ///
183    /// Windows are unfocused upon creation, but will usually be focused by the system soon
184    /// afterwards.
185    Focused(bool),
186
187    /// An event from the keyboard has been received.
188    ///
189    /// ## Platform-specific
190    /// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down,
191    ///   numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key
192    ///   events which are not marked as `is_synthetic`.
193    /// - **iOS:** Unsupported.
194    KeyboardInput {
195        device_id: Option<DeviceId>,
196        event: KeyEvent,
197
198        /// If `true`, the event was generated synthetically by wingy
199        /// in one of the following circumstances:
200        ///
201        /// * Synthetic key press events are generated for all keys pressed when a window gains
202        ///   focus. Likewise, synthetic key release events are generated for all keys pressed when
203        ///   a window goes out of focus. ***Currently, this is only functional on X11 and
204        ///   Windows***
205        ///
206        /// Otherwise, this value is always `false`.
207        is_synthetic: bool,
208    },
209
210    /// The keyboard modifiers have changed.
211    ModifiersChanged(Modifiers),
212
213    /// An event from an input method.
214    ///
215    /// **Note:** You have to explicitly enable this event using [`Window::set_ime_allowed`].
216    ///
217    /// ## Platform-specific
218    ///
219    /// - **iOS / Android / Web / Orbital:** Unsupported.
220    Ime(Ime),
221
222    /// The pointer has moved on the window.
223    ///
224    /// Should be emitted regardless of window focus.
225    PointerMoved {
226        device_id: Option<DeviceId>,
227
228        /// (x,y) coordinates in pixels relative to the top-left corner of the window. Because the
229        /// range of this data is limited by the display area and it may have been
230        /// transformed by the OS to implement effects such as pointer acceleration, it
231        /// should not be used to implement non-pointer-like interactions such as 3D camera
232        /// control. For that, consider [`DeviceEvent::PointerMotion`].
233        ///
234        /// ## Platform-specific
235        ///
236        /// **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
237        ///
238        /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
239        /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
240        /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
241        position: PhysicalPosition<f64>,
242
243        /// Indicates whether the event is created by a primary pointer.
244        ///
245        /// A pointer is considered primary when it's a mouse, the first finger in a multi-touch
246        /// interaction, or an unknown pointer source.
247        primary: bool,
248
249        source: PointerSource,
250    },
251
252    /// The pointer has entered the window.
253    ///
254    /// Should be emitted regardless of window focus.
255    PointerEntered {
256        device_id: Option<DeviceId>,
257
258        /// The position of the pointer when it entered the window.
259        ///
260        /// ## Platform-specific
261        ///
262        /// - **Orbital: Always emits `(0., 0.)`.
263        /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
264        ///
265        /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
266        /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
267        /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
268        position: PhysicalPosition<f64>,
269
270        /// Indicates whether the event is created by a primary pointer.
271        ///
272        /// A pointer is considered primary when it's a mouse, the first finger in a multi-touch
273        /// interaction, or an unknown pointer source.
274        primary: bool,
275
276        kind: PointerKind,
277    },
278
279    /// The pointer has left the window.
280    ///
281    /// Should be emitted regardless of window focus.
282    PointerLeft {
283        device_id: Option<DeviceId>,
284
285        /// The position of the pointer when it left the window. The position reported can be
286        /// outside the bounds of the window.
287        ///
288        /// ## Platform-specific
289        ///
290        /// - **Orbital/Windows:** Always emits [`None`].
291        /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
292        ///
293        /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
294        /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
295        /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
296        position: Option<PhysicalPosition<f64>>,
297
298        /// Indicates whether the event is created by a primary pointer.
299        ///
300        /// A pointer is considered primary when it's a mouse, the first finger in a multi-touch
301        /// interaction, or an unknown pointer source.
302        primary: bool,
303
304        kind: PointerKind,
305    },
306
307    /// A mouse wheel movement or touchpad scroll occurred.
308    MouseWheel {
309        device_id: Option<DeviceId>,
310        delta: MouseScrollDelta,
311        phase: TouchPhase,
312
313        /// What is driving the scroll: a wheel, fingers on a touchpad, or the system
314        /// continuing a finger scroll with momentum.
315        ///
316        /// ## Platform-specific
317        ///
318        /// - **macOS / Wayland:** Reported.
319        /// - **Windows / X11 / Web / Orbital / iOS / Android:** Always [`ScrollSource::Unknown`].
320        source: ScrollSource,
321    },
322
323    /// An mouse button press has been received.
324    PointerButton {
325        device_id: Option<DeviceId>,
326        state: ElementState,
327
328        /// The position of the pointer when the button was pressed.
329        ///
330        /// ## Platform-specific
331        ///
332        /// - **Orbital: Always emits `(0., 0.)`.
333        /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
334        ///
335        /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
336        /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
337        /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
338        position: PhysicalPosition<f64>,
339
340        /// Indicates whether the event is created by a primary pointer.
341        ///
342        /// A pointer is considered primary when it's a mouse, the first finger in a multi-touch
343        /// interaction, or an unknown pointer source.
344        primary: bool,
345
346        button: ButtonSource,
347
348        /// Whether this event is part of the click that activated an otherwise inactive window.
349        ///
350        /// On macOS, AppKit normally consumes the click that brings a window forward without
351        /// delivering it as a regular mouse event (controlled by [`acceptsFirstMouse:`]). Wingy
352        /// always delivers it, but tags both the activating press *and* its matching release
353        /// with this flag so applications can short-circuit the whole gesture with a single
354        /// check — e.g. ignore activation clicks for destructive or button-like targets while
355        /// accepting them for low-risk actions (selection, scrolling).
356        ///
357        /// ## Platform-specific
358        ///
359        /// - **Only available on macOS.** Always `false` on every other platform.
360        /// - Only ever `true` for the left mouse button. Intervening drag motion (delivered as
361        ///   [`WindowEvent::PointerMoved`]) is not tagged; applications that care about drags
362        ///   during the activation gesture must track that state themselves.
363        ///
364        /// [`acceptsFirstMouse:`]: https://developer.apple.com/documentation/appkit/nsview/acceptsfirstmouse(_:)
365        is_macos_activation_click: bool,
366    },
367
368    /// Multi-finger hold gesture on the touchpad or touchscreen without movement.
369    ///
370    /// The `phase` field indicates the lifecycle of the hold gesture:
371    /// - `Started`: One or more fingers are in contact with the touchpad/touchscreen.
372    /// - `Ended`: All fingers have been lifted from the touchpad/touchscreen.
373    /// - `Cancelled`: The hold gesture was interrupted, for example when another finger touches the
374    ///   touchpad (causing a new `Started` event with more fingers), or when movement begins and
375    ///   transitions to other gestures like pinch, pan, or rotation.
376    ///
377    /// ## Platform-specific
378    ///
379    /// - Only available on **Wayland**.
380    HoldGesture { device_id: Option<DeviceId>, phase: TouchPhase },
381
382    /// Two-finger pinch gesture, often used for magnification.
383    ///
384    /// ## Platform-specific
385    ///
386    /// - Only available on **macOS**, **iOS**, and **Wayland**.
387    /// - On iOS, not recognized by default. It must be enabled when needed.
388    PinchGesture {
389        device_id: Option<DeviceId>,
390        /// Positive values indicate magnification (zooming in) and  negative
391        /// values indicate shrinking (zooming out).
392        ///
393        /// This value may be NaN.
394        delta: f64,
395        phase: TouchPhase,
396    },
397
398    /// N-finger pan gesture
399    ///
400    /// ## Platform-specific
401    ///
402    /// - Only available on **iOS** and **Wayland**.
403    /// - On iOS, not recognized by default. It must be enabled when needed.
404    PanGesture {
405        device_id: Option<DeviceId>,
406        /// Change in pixels of pan gesture from last update.
407        delta: PhysicalPosition<f32>,
408        phase: TouchPhase,
409    },
410
411    /// Double tap gesture.
412    ///
413    /// On a Mac, smart magnification is triggered by a double tap with two fingers
414    /// on the trackpad and is commonly used to zoom on a certain object
415    /// (e.g. a paragraph of a PDF) or (sort of like a toggle) to reset any zoom.
416    /// The gesture is also supported in Safari, Pages, etc.
417    ///
418    /// The event is general enough that its generating gesture is allowed to vary
419    /// across platforms. It could also be generated by another device.
420    ///
421    /// Unfortunately, neither [Windows](https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741)
422    /// nor [Wayland](https://wayland.freedesktop.org/libinput/doc/latest/gestures.html)
423    /// support this gesture or any other gesture with the same effect.
424    ///
425    /// ## Platform-specific
426    ///
427    /// - Only available on **macOS 10.8** and later, and **iOS**.
428    /// - On iOS, not recognized by default. It must be enabled when needed.
429    DoubleTapGesture { device_id: Option<DeviceId> },
430
431    /// Two-finger rotation gesture.
432    ///
433    /// Positive delta values indicate rotation counterclockwise and
434    /// negative delta values indicate rotation clockwise.
435    ///
436    /// ## Platform-specific
437    ///
438    /// - Only available on **macOS**, **iOS**, and **Wayland**.
439    /// - On iOS, not recognized by default. It must be enabled when needed.
440    RotationGesture {
441        device_id: Option<DeviceId>,
442        /// change in rotation in degrees
443        delta: f32,
444        phase: TouchPhase,
445    },
446
447    /// Touchpad pressure event.
448    ///
449    /// ## Platform-specific
450    ///
451    /// - **macOS**: Only supported on Apple forcetouch-capable macbooks.
452    /// - **Android / iOS / Wayland / X11 / Windows / Orbital / Web:** Unsupported.
453    TouchpadPressure {
454        device_id: Option<DeviceId>,
455        /// Value between 0 and 1 representing how hard the touchpad is being
456        /// pressed.
457        pressure: f32,
458        /// Represents the click level.
459        stage: i64,
460    },
461
462    /// The window's scale factor has changed.
463    ///
464    /// The following user actions can cause DPI changes:
465    ///
466    /// * Changing the display's resolution.
467    /// * Changing the display's scale factor (e.g. in Control Panel on Windows).
468    /// * Moving the window to a display with a different scale factor.
469    ///
470    /// To update the window size, use the provided [`SurfaceSizeWriter`] handle. By default, the
471    /// window is resized to the value suggested by the OS, but it can be changed to any value.
472    ///
473    /// This event will not necessarily be emitted upon window creation, query
474    /// [`Window::scale_factor`] if you need to determine the window's initial scale factor.
475    ///
476    /// For more information about DPI in general, see the [`dpi`] crate.
477    ///
478    /// [`Window::scale_factor`]: crate::window::Window::scale_factor
479    ScaleFactorChanged {
480        scale_factor: f64,
481        /// Handle to update surface size during scale changes.
482        ///
483        /// See [`SurfaceSizeWriter`] docs for more details.
484        surface_size_writer: SurfaceSizeWriter,
485    },
486
487    /// The system window theme has changed.
488    ///
489    /// Applications might wish to react to this to change the theme of the content of the window
490    /// when the system changes the window theme.
491    ///
492    /// This only reports a change if the window theme was not overridden by [`Window::set_theme`].
493    ///
494    /// ## Platform-specific
495    ///
496    /// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported.
497    ThemeChanged(Theme),
498
499    /// The window has been occluded (completely hidden from view).
500    ///
501    /// This is different to window visibility as it depends on whether the window is closed,
502    /// minimised, set invisible, or fully occluded by another window.
503    ///
504    /// ## Platform-specific
505    ///
506    /// ### iOS
507    ///
508    /// On iOS, the `Occluded(false)` event is emitted in response to an
509    /// [`applicationWillEnterForeground`] callback which means the application should start
510    /// preparing its data. The `Occluded(true)` event is emitted in response to an
511    /// [`applicationDidEnterBackground`] callback which means the application should free
512    /// resources (according to the [iOS application lifecycle]).
513    ///
514    /// [`applicationWillEnterForeground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623076-applicationwillenterforeground
515    /// [`applicationDidEnterBackground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground
516    /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
517    ///
518    /// ### Others
519    ///
520    /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
521    /// - **Android / Wayland / Windows / Orbital:** Unsupported.
522    ///
523    /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
524    /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
525    /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
526    Occluded(bool),
527
528    /// Emitted when a window should be redrawn.
529    ///
530    /// This gets triggered in a few scenarios:
531    /// - The OS has performed an operation that's invalidated the window's contents (such as
532    ///   resizing the window, or changing [the safe area]).
533    /// - The application has explicitly requested a redraw via [`Window::request_redraw`].
534    ///
535    /// Wingy will aggregate duplicate redraw requests into a single event, to
536    /// help avoid duplicating rendering work.
537    ///
538    /// [the safe area]: crate::window::Window::safe_area
539    RedrawRequested,
540}
541
542/// Represents the kind type of a pointer event.
543///
544/// ## Platform-specific
545///
546/// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the
547/// system.
548#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
549#[non_exhaustive]
550pub enum PointerKind {
551    Mouse,
552    /// See [`PointerSource::Touch`] for more details.
553    ///
554    /// ## Platform-specific
555    ///
556    /// **macOS:** Unsupported.
557    Touch(FingerId),
558    TabletTool(TabletToolKind),
559    Unknown,
560}
561
562/// Represents the pointer type and its data for a pointer event.
563///
564/// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the
565/// system.
566#[derive(Clone, Debug, PartialEq)]
567#[non_exhaustive]
568pub enum PointerSource {
569    Mouse,
570    /// Represents a touch event.
571    ///
572    /// Every time the user touches the screen, a [`WindowEvent::PointerEntered`] and a
573    /// [`WindowEvent::PointerButton`] with [`ElementState::Pressed`] event with an unique
574    /// identifier for the finger is emitted. When a finger is lifted, a
575    /// [`WindowEvent::PointerButton`] with [`ElementState::Released`] and a
576    /// [`WindowEvent::PointerLeft`] event is generated with the same [`FingerId`].
577    ///
578    /// After a [`WindowEvent::PointerEntered`] event has been emitted, there may be zero or more
579    /// [`WindowEvent::PointerMoved`] events when the finger is moved or the touch pressure
580    /// changes.
581    ///
582    /// A [`WindowEvent::PointerLeft`] without a [`WindowEvent::PointerButton`] with
583    /// [`ElementState::Released`] event is emitted when the system has canceled tracking this
584    /// touch, such as when the window loses focus, or on mobile devices if the user moves the
585    /// device against their face.
586    ///
587    /// The [`FingerId`] may be reused by the system after a [`WindowEvent::PointerLeft`] event.
588    /// The user should assume that a new [`WindowEvent::PointerEntered`] event received with the
589    /// same ID has nothing to do with the old finger and is a new finger.
590    ///
591    /// ## Platform-specific
592    ///
593    /// **macOS:** Unsupported.
594    Touch {
595        finger_id: FingerId,
596
597        /// Describes how hard the screen was pressed. May be [`None`] if the hardware does not
598        /// support pressure sensitivity.
599        ///
600        /// ## Platform-specific
601        ///
602        /// - **MacOS / Orbital / Wayland / X11:** Always emits [`None`].
603        /// - **Android:** Will never be [`None`]. If the device doesn't support pressure
604        ///   sensitivity, force will either be 0.0 or 1.0. Also see the
605        ///   [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
606        /// - **Web:** Will never be [`None`]. If the device doesn't support pressure sensitivity,
607        ///   force will be 0.5 when a button is pressed or 0.0 otherwise.
608        force: Option<Force>,
609    },
610    TabletTool {
611        /// Describes as which tool kind the interaction happened.
612        kind: TabletToolKind,
613
614        /// Describes how the tool was held and used.
615        data: TabletToolData,
616    },
617    Unknown,
618}
619
620impl From<PointerSource> for PointerKind {
621    fn from(source: PointerSource) -> Self {
622        match source {
623            PointerSource::Mouse => Self::Mouse,
624            PointerSource::Touch { finger_id, .. } => Self::Touch(finger_id),
625            PointerSource::TabletTool { kind, .. } => Self::TabletTool(kind),
626            PointerSource::Unknown => Self::Unknown,
627        }
628    }
629}
630
631/// Represents the pointer type of a [`WindowEvent::PointerButton`].
632///
633/// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the
634/// system.
635#[derive(Clone, Debug, PartialEq)]
636#[non_exhaustive]
637pub enum ButtonSource {
638    /// ## Platform-specific
639    ///
640    /// ### macOS
641    ///
642    /// Users may expect holding [<kbd>CTRL</kbd>](ModifiersState::CONTROL) while
643    /// clicking [`MouseButton::Left`] to result in a "secondary" click, but the way these
644    /// clicks behave natively is slightly different from how a physical secondary
645    /// button press would, depending on the content under the cursor when clicked. If
646    /// applications want this behavior they should implement it themselves by interpreting
647    /// [`Left`](MouseButton::Left) clicks as secondary when
648    /// [<kbd>CTRL</kbd>](ModifiersState::CONTROL) is held and their internal logic deems it
649    /// appropriate for the content under the pointer.
650    /// See also <https://github.com/rust-windowing/winit/issues/4469>.
651    Mouse(MouseButton),
652    /// See [`PointerSource::Touch`] for more details.
653    ///
654    /// ## Platform-specific
655    ///
656    /// **macOS:** Unsupported.
657    Touch {
658        finger_id: FingerId,
659        force: Option<Force>,
660    },
661    TabletTool {
662        kind: TabletToolKind,
663        button: TabletToolButton,
664        data: TabletToolData,
665    },
666    /// A pointer button of unknown source.
667    ///
668    /// Codes are undefined and may not be reproducible across platforms or wingy versions.
669    Unknown(u16),
670}
671
672impl ButtonSource {
673    /// Try to convert a [`ButtonSource`] to an equivalent [`MouseButton`]. If a pointer type has no
674    /// special handling in an application, this method can be used to handle it like any generic
675    /// mouse input.
676    pub fn mouse_button(self) -> Option<MouseButton> {
677        match self {
678            ButtonSource::Mouse(mouse) => Some(mouse),
679            ButtonSource::Touch { .. } => Some(MouseButton::Left),
680            ButtonSource::TabletTool { button, .. } => button.into(),
681            ButtonSource::Unknown(_) => None,
682        }
683    }
684}
685
686impl From<MouseButton> for ButtonSource {
687    fn from(mouse: MouseButton) -> Self {
688        Self::Mouse(mouse)
689    }
690}
691
692/// Identifier of an input device.
693///
694/// Whenever you receive an event arising from a particular input device, this event contains a
695/// `DeviceId` which identifies its origin. Note that devices may be virtual (representing an
696/// on-screen cursor and keyboard focus) or physical. Virtual devices typically aggregate inputs
697/// from multiple physical devices.
698#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
699pub struct DeviceId(i64);
700
701impl DeviceId {
702    /// Convert the [`DeviceId`] into the underlying integer.
703    ///
704    /// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
705    pub const fn into_raw(self) -> i64 {
706        self.0
707    }
708
709    /// Construct a [`DeviceId`] from the underlying integer.
710    ///
711    /// This should only be called with integers returned from [`DeviceId::into_raw`].
712    pub const fn from_raw(id: i64) -> Self {
713        Self(id)
714    }
715}
716
717/// Identifier of a finger in a touch event.
718///
719/// Whenever a touch event is received it contains a `FingerId` which uniquely identifies the finger
720/// used for the current interaction.
721#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
722pub struct FingerId(pub(crate) usize);
723
724impl FingerId {
725    /// Convert the [`FingerId`] into the underlying integer.
726    ///
727    /// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
728    pub const fn into_raw(self) -> usize {
729        self.0
730    }
731
732    /// Construct a [`FingerId`] from the underlying integer.
733    ///
734    /// This should only be called with integers returned from [`FingerId::into_raw`].
735    pub const fn from_raw(id: usize) -> Self {
736        Self(id)
737    }
738}
739
740/// Represents raw hardware events that are not associated with any particular window.
741///
742/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera
743/// or first-person game controls. Many physical actions, such as mouse movement, can produce both
744/// device and [window events]. Because window events typically arise from virtual devices
745/// (corresponding to GUI pointers and keyboard focus) the device IDs may not match.
746///
747/// Note that these events are delivered regardless of input focus.
748///
749/// [window events]: WindowEvent
750#[derive(Clone, Copy, Debug, PartialEq)]
751#[non_exhaustive]
752pub enum DeviceEvent {
753    /// Change in physical position of a pointing device.
754    ///
755    /// This represents raw, unfiltered physical motion. Not to be confused with
756    /// [`WindowEvent::PointerMoved`].
757    ///
758    /// ## Platform-specific
759    ///
760    /// **Web:** Only returns raw data, not OS accelerated, if [`CursorGrabMode::Locked`] is used
761    /// and browser support is available.
762    ///
763    /// [`CursorGrabMode::Locked`]: crate::window::CursorGrabMode::Locked
764    PointerMotion {
765        /// (x, y) change in position in unspecified units.
766        ///
767        /// Different devices may use different units.
768        delta: (f64, f64),
769    },
770
771    /// Physical scroll event
772    MouseWheel {
773        delta: MouseScrollDelta,
774    },
775
776    Button {
777        button: ButtonId,
778        state: ElementState,
779    },
780
781    Key(RawKeyEvent),
782}
783
784/// Describes a keyboard input as a raw device event.
785///
786/// Note that holding down a key may produce repeated `RawKeyEvent`s. The
787/// operating system doesn't provide information whether such an event is a
788/// repeat or the initial keypress. An application may emulate this by, for
789/// example keeping a Map/Set of pressed keys and determining whether a keypress
790/// corresponds to an already pressed key.
791#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
792#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
793pub struct RawKeyEvent {
794    pub physical_key: keyboard::PhysicalKey,
795    pub state: ElementState,
796}
797
798/// Describes a keyboard input targeting a window.
799#[derive(Debug, Clone, Eq, PartialEq, Hash)]
800pub struct KeyEvent {
801    /// Represents the position of a key independent of the currently active layout.
802    ///
803    /// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode).
804    /// The most prevalent use case for this is games. For example the default keys for the player
805    /// to move around might be the W, A, S, and D keys on a US layout. The position of these keys
806    /// is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY"
807    /// layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.)
808    ///
809    /// ## Caveats
810    ///
811    /// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that
812    ///   implements DVORAK in hardware (or firmware)
813    /// - Your application will likely have to handle keyboards which are missing keys that your
814    ///   own keyboard has.
815    /// - Certain `KeyCode`s will move between a couple of different positions depending on what
816    ///   layout the keyboard was manufactured to support.
817    ///
818    ///  **Because of these caveats, it is important that you provide users with a way to configure
819    ///  most (if not all) keybinds in your application.**
820    ///
821    /// ## `Fn` and `FnLock`
822    ///
823    /// `Fn` and `FnLock` key events are *exceedingly unlikely* to be emitted by Wingy. These keys
824    /// are usually handled at the hardware or OS level, and aren't surfaced to applications. If
825    /// you somehow see this in the wild, we'd like to know :)
826    pub physical_key: keyboard::PhysicalKey,
827
828    /// This value is affected by all modifiers except <kbd>Ctrl</kbd>.
829    ///
830    /// This has two use cases:
831    /// - Allows querying whether the current input is a Dead key.
832    /// - Allows handling key-bindings on platforms which don't support [`key_without_modifiers`].
833    ///
834    /// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard
835    /// shortcuts, **it is important that you provide users with a way to configure your
836    /// application's shortcuts so you don't render your application unusable for users with an
837    /// incompatible keyboard layout.**
838    ///
839    /// ## Platform-specific
840    /// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the
841    ///   browser/OS.
842    ///
843    /// [`key_without_modifiers`]: Self::key_without_modifiers
844    pub logical_key: keyboard::Key,
845
846    /// Contains the text produced by this keypress.
847    ///
848    /// In most cases this is identical to the content
849    /// of the `Character` variant of `logical_key`.
850    /// However, on Windows when a dead key was pressed earlier
851    /// but cannot be combined with the character from this
852    /// keypress, the produced text will consist of two characters:
853    /// the dead-key-character followed by the character resulting
854    /// from this keypress.
855    ///
856    /// An additional difference from `logical_key` is that
857    /// this field stores the text representation of any key
858    /// that has such a representation. For example when
859    /// `logical_key` is `Key::Named(NamedKey::Enter)`, this field is `Some("\r")`.
860    ///
861    /// This is `None` if the current keypress cannot
862    /// be interpreted as text.
863    ///
864    /// See also [`text_with_all_modifiers`][Self::text_with_all_modifiers].
865    pub text: Option<SmolStr>,
866
867    /// Contains the location of this key on the keyboard.
868    ///
869    /// Certain keys on the keyboard may appear in more than once place. For example, the "Shift"
870    /// key appears on the left side of the QWERTY keyboard as well as the right side. However,
871    /// both keys have the same symbolic value. Another example of this phenomenon is the "1"
872    /// key, which appears both above the "Q" key and as the "Keypad 1" key.
873    ///
874    /// This field allows the user to differentiate between keys like this that have the same
875    /// symbolic value but different locations on the keyboard.
876    ///
877    /// See the [`KeyLocation`] type for more details.
878    ///
879    /// [`KeyLocation`]: crate::keyboard::KeyLocation
880    pub location: keyboard::KeyLocation,
881
882    /// Whether the key is being pressed or released.
883    ///
884    /// See the [`ElementState`] type for more details.
885    pub state: ElementState,
886
887    /// Whether or not this key is a key repeat event.
888    ///
889    /// On some systems, holding down a key for some period of time causes that key to be repeated
890    /// as though it were being pressed and released repeatedly. This field is `true` if and only
891    /// if this event is the result of one of those repeats.
892    ///
893    /// # Example
894    ///
895    /// In games, you often want to ignore repeated key events - this can be
896    /// done by ignoring events where this property is set.
897    ///
898    /// ```no_run
899    /// use wingy_core::event::{ElementState, KeyEvent, WindowEvent};
900    /// use wingy_core::keyboard::{KeyCode, PhysicalKey};
901    /// # let window_event = WindowEvent::RedrawRequested; // To make the example compile
902    /// match window_event {
903    ///     WindowEvent::KeyboardInput {
904    ///         event:
905    ///             KeyEvent {
906    ///                 physical_key: PhysicalKey::Code(KeyCode::KeyW),
907    ///                 state: ElementState::Pressed,
908    ///                 repeat: false,
909    ///                 ..
910    ///             },
911    ///         ..
912    ///     } => {
913    ///         // The physical key `W` was pressed, and it was not a repeat
914    ///     },
915    ///     _ => {}, // Handle other events
916    /// }
917    /// ```
918    pub repeat: bool,
919
920    /// Similar to [`text`][Self::text], except that this is affected by <kbd>Ctrl</kbd> and may
921    /// produce ASCII control characters.
922    ///
923    /// For example, pressing <kbd>Ctrl</kbd>+<kbd>space</kbd> produces `Some("\x00")`.
924    ///
925    /// ## Platform-specific
926    ///
927    /// - **Android:** This field is always the same value as `text`.
928    /// - **iOS:** Unimplemented, this field is always the same value as `text`.
929    /// - **Web:** Unsupported, this field is always the same value as `text`.
930    pub text_with_all_modifiers: Option<SmolStr>,
931
932    /// This value ignores all modifiers including, but not limited to <kbd>Shift</kbd>,
933    /// <kbd>Caps Lock</kbd>, and <kbd>Ctrl</kbd>. In most cases this means that the
934    /// unicode character in the resulting string is lowercase.
935    ///
936    /// This is useful for key-bindings / shortcut key combinations.
937    ///
938    /// In case [`logical_key`][Self::logical_key] reports [`Dead`][keyboard::Key::Dead],
939    /// this will still report the key as `Character` according to the current keyboard
940    /// layout. This value cannot be `Dead`.
941    ///
942    /// ## Platform-specific
943    ///
944    /// - **Android:** Unimplemented, this field is always the same value as `logical_key`.
945    /// - **iOS:** Unimplemented, this field is always the same value as `logical_key`.
946    /// - **Web:** Unsupported, this field is always the same value as `logical_key`.
947    pub key_without_modifiers: keyboard::Key,
948}
949
950/// Describes keyboard modifiers event.
951#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
952#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
953pub struct Modifiers {
954    pub(crate) state: ModifiersState,
955
956    // NOTE: Currently active modifiers keys (logically, but not necessarily physically, pressed).
957    //
958    // The field providing a metadata, it shouldn't be used as a source of truth.
959    pub(crate) pressed_mods: ModifiersKeys,
960}
961
962impl Modifiers {
963    /// Create a new modifiers from state and pressed mods.
964    pub fn new(state: ModifiersState, pressed_mods: ModifiersKeys) -> Self {
965        Self { state, pressed_mods }
966    }
967
968    /// The logical state of the modifiers.
969    pub fn state(&self) -> ModifiersState {
970        self.state
971    }
972
973    /// The logical state of the left shift key.
974    pub fn lshift_state(&self) -> ModifiersKeyState {
975        self.mod_state(ModifiersKeys::LSHIFT)
976    }
977
978    /// The logical state of the right shift key.
979    pub fn rshift_state(&self) -> ModifiersKeyState {
980        self.mod_state(ModifiersKeys::RSHIFT)
981    }
982
983    /// The logical state of the left alt key.
984    pub fn lalt_state(&self) -> ModifiersKeyState {
985        self.mod_state(ModifiersKeys::LALT)
986    }
987
988    /// The logical state of the right alt key.
989    pub fn ralt_state(&self) -> ModifiersKeyState {
990        self.mod_state(ModifiersKeys::RALT)
991    }
992
993    /// The logical state of the left control key.
994    pub fn lcontrol_state(&self) -> ModifiersKeyState {
995        self.mod_state(ModifiersKeys::LCONTROL)
996    }
997
998    /// The logical state of the right control key.
999    pub fn rcontrol_state(&self) -> ModifiersKeyState {
1000        self.mod_state(ModifiersKeys::RCONTROL)
1001    }
1002
1003    /// The logical state of the left super key.
1004    pub fn lsuper_state(&self) -> ModifiersKeyState {
1005        self.mod_state(ModifiersKeys::LMETA)
1006    }
1007
1008    /// The logical state of the right super key.
1009    pub fn rsuper_state(&self) -> ModifiersKeyState {
1010        self.mod_state(ModifiersKeys::RMETA)
1011    }
1012
1013    fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState {
1014        if self.pressed_mods.contains(modifier) {
1015            ModifiersKeyState::Pressed
1016        } else {
1017            ModifiersKeyState::Unknown
1018        }
1019    }
1020}
1021
1022impl From<ModifiersState> for Modifiers {
1023    fn from(value: ModifiersState) -> Self {
1024        Self { state: value, pressed_mods: Default::default() }
1025    }
1026}
1027
1028/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
1029///
1030/// The `Ime` events must be applied in the order they arrive.
1031///
1032/// This is also called a "composition event".
1033///
1034/// Most keypresses using a latin-like keyboard layout simply generate a
1035/// [`WindowEvent::KeyboardInput`]. However, one couldn't possibly have a key for every single
1036/// unicode character that the user might want to type
1037/// - so the solution operating systems employ is to allow the user to type these using _a sequence
1038///   of keypresses_ instead.
1039///
1040/// A prominent example of this is accents - many keyboard layouts allow you to first click the
1041/// "accent key", and then the character you want to apply the accent to. In this case, some
1042/// platforms will generate the following event sequence:
1043///
1044/// ```ignore
1045/// // Press "`" key
1046/// Ime::Preedit("`", Some((0, 0)))
1047/// // Press "E" key
1048/// Ime::Commit("é") // The commit clears the preedit.
1049/// ```
1050///
1051/// Additionally, certain input devices are configured to display a candidate box that allow the
1052/// user to select the desired character interactively. (To properly position this box, you must use
1053/// [`Window::set_ime_cursor_area`].)
1054///
1055/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the
1056/// following event sequence could be obtained:
1057///
1058/// ```ignore
1059/// // Press "A" key
1060/// Ime::Preedit("a", Some((1, 1)))
1061/// // Press "B" key
1062/// Ime::Preedit("a b", Some((3, 3)))
1063/// // Press left arrow key
1064/// Ime::Preedit("a b", Some((1, 1)))
1065/// // Press space key
1066/// Ime::Preedit("啊b", Some((3, 3)))
1067/// // Press space key
1068/// Ime::Commit("啊不") // The commit clears the preedit.
1069/// ```
1070#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1071#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1072#[allow(clippy::exhaustive_enums)]
1073pub enum Ime {
1074    /// Notifies when the IME was enabled.
1075    ///
1076    /// After getting this event you could receive [`Preedit`][Self::Preedit] and
1077    /// [`Commit`][Self::Commit] events. You should also start performing IME related requests
1078    /// like [`Window::set_ime_cursor_area`].
1079    Enabled,
1080
1081    /// Notifies when a new composing text should be set at the cursor position.
1082    ///
1083    /// The value represents a pair of the preedit string and the cursor begin position and end
1084    /// position. When it's `None`, the cursor should be hidden. When `String` is an empty string
1085    /// this indicates that preedit was cleared.
1086    ///
1087    /// The cursor position is byte-wise indexed, assuming UTF-8.
1088    Preedit(String, Option<(usize, usize)>),
1089
1090    /// Notifies when text should be inserted into the editor widget.
1091    ///
1092    /// This event clears the preedit.
1093    Commit(String),
1094
1095    /// Delete text surrounding the cursor or selection.
1096    ///
1097    /// This event does not affect either the pre-edit string.
1098    /// This means that the application must first remove the pre-edit,
1099    /// then execute the deletion, then insert the removed text back.
1100    ///
1101    /// This event assumes text is stored in UTF-8.
1102    DeleteSurrounding {
1103        /// Bytes to remove before the selection
1104        before_bytes: usize,
1105        /// Bytes to remove after the selection
1106        after_bytes: usize,
1107    },
1108
1109    /// Notifies when the IME was disabled.
1110    ///
1111    /// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or
1112    /// [`Commit`][Self::Commit] events until the next [`Enabled`][Self::Enabled] event. You should
1113    /// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear
1114    /// pending preedit text.
1115    Disabled,
1116}
1117
1118/// Describes touch-screen input state.
1119#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
1120#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1121#[allow(clippy::exhaustive_enums)]
1122pub enum TouchPhase {
1123    /// Initial touch contact or gesture start, for example when one or more fingers touch the
1124    /// screen or touchpad.
1125    Started,
1126    /// The touch contact point changed, for example without lifting the finger.
1127    Moved,
1128    /// All touch contact points have been lifted from the touchscreen or touchpad.
1129    ///
1130    /// This event is important as it should clear any state or event in flight that was
1131    /// generated by the preceding `Started` and `Moved` events.
1132    Ended,
1133    /// The event was cancelled and should cancel any event in flight and clear state.
1134    Cancelled,
1135}
1136
1137/// Describes the force of a touch event
1138#[derive(Debug, Clone, Copy, PartialEq)]
1139#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1140#[doc(alias = "Pressure")]
1141#[allow(clippy::exhaustive_enums)]
1142pub enum Force {
1143    /// On iOS, the force is calibrated so that the same number corresponds to
1144    /// roughly the same amount of pressure on the screen regardless of the
1145    /// device.
1146    Calibrated {
1147        /// The force of the touch, where a value of 1.0 represents the force of
1148        /// an average touch (predetermined by the system, not user-specific).
1149        ///
1150        /// The force reported by Apple Pencil is measured along the axis of the
1151        /// pencil. If you want a force perpendicular to the device, you need to
1152        /// calculate this value using the [`TabletToolAngle::altitude`] value.
1153        force: f64,
1154        /// The maximum possible force for a touch.
1155        ///
1156        /// The value of this field is sufficiently high to provide a wide
1157        /// dynamic range for values of the `force` field.
1158        max_possible_force: f64,
1159    },
1160    /// If the platform reports the force as normalized, we have no way of
1161    /// knowing how much pressure 1.0 corresponds to – we know it's the maximum
1162    /// amount of force, but as to how much force, you might either have to
1163    /// press really really hard, or not hard at all, depending on the device.
1164    Normalized(f64),
1165}
1166
1167impl Force {
1168    /// Returns the force normalized to the range between 0.0 and 1.0 inclusive.
1169    ///
1170    /// Instead of normalizing the force, you should prefer to handle
1171    /// [`Force::Calibrated`] so that the amount of force the user has to apply is
1172    /// consistent across devices.
1173    ///
1174    /// Passing in a [`TabletToolAngle`], returns the perpendicular force.
1175    pub fn normalized(&self, angle: Option<TabletToolAngle>) -> f64 {
1176        match self {
1177            Force::Calibrated { force, max_possible_force } => {
1178                let force = match angle {
1179                    Some(TabletToolAngle { altitude, .. }) => force / altitude.sin(),
1180                    None => *force,
1181                };
1182                force / max_possible_force
1183            },
1184            Force::Normalized(force) => *force,
1185        }
1186    }
1187}
1188
1189/// Identifier for a specific analog axis on some device.
1190pub type AxisId = u32;
1191
1192/// Identifier for a specific button on some device.
1193pub type ButtonId = u32;
1194
1195/// Tablet of the tablet tool.
1196#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
1197#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
1198#[non_exhaustive]
1199pub enum TabletToolKind {
1200    #[default]
1201    Pen,
1202    Eraser,
1203    Brush,
1204    Pencil,
1205    Airbrush,
1206    Finger,
1207    Mouse,
1208    Lens,
1209}
1210
1211#[derive(Default, Clone, Debug, PartialEq)]
1212#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
1213pub struct TabletToolData {
1214    /// The force applied to the tool against the surface.
1215    ///
1216    /// When the force information is not available, [`None`] is returned.
1217    ///
1218    /// ## Platform-specific
1219    ///
1220    /// **Web:** Has no mechanism to detect support, so this will always be [`Some`].
1221    pub force: Option<Force>,
1222    /// Represents normalized tangential pressure, also known as barrel pressure. In the range of
1223    /// -1 to 1. 0 means no tangential pressure is applied. [`None`] means backend or device has no
1224    /// support.
1225    ///
1226    /// ## Platform-specific
1227    ///
1228    /// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with a value
1229    /// of 0.
1230    pub tangential_force: Option<f32>,
1231    /// The clockwise rotation in degrees of a tool around its own major axis. E.g. twisting a pen
1232    /// around its length. In the range of 0 to 359. [`None`] means backend or device has no
1233    /// support.
1234    ///
1235    /// ## Platform-specific
1236    ///
1237    /// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with a value
1238    /// of 0.
1239    pub twist: Option<u16>,
1240    /// The plane angle in degrees. [`None`] means backend or device has no support.
1241    ///
1242    /// ## Platform-specific
1243    ///
1244    /// **Web:** Has no mechanism to detect support, so this will always be [`Some`] with default
1245    /// values.
1246    pub tilt: Option<TabletToolTilt>,
1247    /// The angular position in radians. [`None`] means backend or device has no support.
1248    ///
1249    /// ## Platform-specific
1250    ///
1251    /// **Web:** Has no mechanism to detect device support, so this will always be [`Some`] with
1252    /// default values unless browser support is lacking.
1253    pub angle: Option<TabletToolAngle>,
1254}
1255
1256impl TabletToolData {
1257    /// Returns [`TabletToolTilt`] if present or calculates it from [`TabletToolAngle`].
1258    pub fn tilt(self) -> Option<TabletToolTilt> {
1259        if let Some(tilt) = self.tilt { Some(tilt) } else { self.angle.map(TabletToolAngle::tilt) }
1260    }
1261
1262    /// Returns [`TabletToolAngle`] if present or calculates it from [`TabletToolTilt`].
1263    pub fn angle(self) -> Option<TabletToolAngle> {
1264        if let Some(angle) = self.angle {
1265            Some(angle)
1266        } else {
1267            self.tilt.map(TabletToolTilt::angle)
1268        }
1269    }
1270}
1271
1272/// The plane angle in degrees of a tool.
1273#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
1274#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
1275pub struct TabletToolTilt {
1276    /// The plane angle in degrees between the surface Y-Z plane and the plane containing the tool
1277    /// and the surface Y axis. Positive values are to the right. In the range of -90 to 90. 0
1278    /// means the tool is perpendicular to the surface and is the default.
1279    ///
1280    /// ![Tilt X](https://raw.githubusercontent.com/rust-windowing/wingy/master/wingy/docs/res/tool_tilt_x.webp)
1281    ///
1282    /// <sub>
1283    ///   For image attribution, see the
1284    ///   <a href="https://github.com/rust-windowing/winit/blob/master/winit/docs/ATTRIBUTION.md">
1285    ///     ATTRIBUTION.md
1286    ///   </a>
1287    ///   file.
1288    /// </sub>
1289    pub x: i8,
1290    /// The plane angle in degrees between the surface X-Z plane and the plane containing the tool
1291    /// and the surface X axis. Positive values are towards the user. In the range of -90 to
1292    /// 90. 0 means the tool is perpendicular to the surface and is the default.
1293    ///
1294    /// ![Tilt Y](https://raw.githubusercontent.com/rust-windowing/wingy/master/wingy/docs/res/tool_tilt_y.webp)
1295    ///
1296    /// <sub>
1297    ///   For image attribution, see the
1298    ///   <a href="https://github.com/rust-windowing/winit/blob/master/winit/docs/ATTRIBUTION.md">
1299    ///     ATTRIBUTION.md
1300    ///   </a>
1301    ///   file.
1302    /// </sub>
1303    pub y: i8,
1304}
1305
1306impl TabletToolTilt {
1307    pub fn angle(self) -> TabletToolAngle {
1308        // See <https://www.w3.org/TR/2024/WD-pointerevents3-20240326/#converting-between-tiltx-tilty-and-altitudeangle-azimuthangle>.
1309
1310        use std::f64::consts::*;
1311
1312        const PI_0_5: f64 = FRAC_PI_2;
1313        const PI_1_5: f64 = 3. * FRAC_PI_2;
1314        const PI_2: f64 = 2. * PI;
1315
1316        let x = LazyCell::new(|| f64::from(self.x).to_radians());
1317        let y = LazyCell::new(|| f64::from(self.y).to_radians());
1318
1319        let mut azimuth = 0.;
1320
1321        if self.x == 0 {
1322            match self.y.cmp(&0) {
1323                Ordering::Greater => azimuth = PI_0_5,
1324                Ordering::Less => azimuth = PI_1_5,
1325                Ordering::Equal => (),
1326            }
1327        } else if self.y == 0 {
1328            if self.x < 0 {
1329                azimuth = PI;
1330            }
1331        } else if self.x.abs() == 90 || self.y.abs() == 90 {
1332            // not enough information to calculate azimuth
1333            azimuth = 0.;
1334        } else {
1335            // Non-boundary case: neither tiltX nor tiltY is equal to 0 or +-90
1336            azimuth = f64::atan2(y.tan(), x.tan());
1337
1338            if azimuth < 0. {
1339                azimuth += PI_2;
1340            }
1341        }
1342
1343        let altitude = if self.x.abs() == 90 || self.y.abs() == 90 {
1344            0.
1345        } else if self.x == 0 {
1346            PI_0_5 - y.abs()
1347        } else if self.y == 0 {
1348            PI_0_5 - x.abs()
1349        } else {
1350            // Non-boundary case: neither tiltX nor tiltY is equal to 0 or +-90
1351            f64::atan(1. / f64::sqrt(x.tan().powi(2) + y.tan().powi(2)))
1352        };
1353
1354        TabletToolAngle { altitude, azimuth }
1355    }
1356}
1357
1358/// The angular position in radians of a tool.
1359#[derive(Clone, Copy, Debug, PartialEq)]
1360#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
1361pub struct TabletToolAngle {
1362    /// The altitude angle in radians between the tools perpendicular position to the surface and
1363    /// the surface X-Y plane. In the range of 0, parallel to the surface, to π/2, perpendicular to
1364    /// the surface. π/2 means the tool is perpendicular to the surface and is the default.
1365    ///
1366    /// ![Altitude angle](https://raw.githubusercontent.com/rust-windowing/wingy/master/docs/res/tool_altitude.webp)
1367    ///
1368    /// <sub>
1369    ///   For image attribution, see the
1370    ///   <a href="https://github.com/rust-windowing/winit/blob/master/docs/res/ATTRIBUTION.md">
1371    ///     ATTRIBUTION.md
1372    ///   </a>
1373    ///   file.
1374    /// </sub>
1375    pub altitude: f64,
1376    /// The azimuth angle in radiants representing the rotation between the major axis of the tool
1377    /// and the surface X-Y plane. In the range of 0, 3 o'clock, progressively increasing clockwise
1378    /// to 2π. 0 means the tool is at 3 o'clock or is perpendicular to the surface (`altitude` of
1379    /// π/2) and is the default.
1380    ///
1381    /// ![Azimuth angle](https://raw.githubusercontent.com/rust-windowing/wingy/master/docs/res/tool_azimuth.webp)
1382    ///
1383    /// <sub>
1384    ///   For image attribution, see the
1385    ///   <a href="https://github.com/rust-windowing/winit/blob/master/docs/res/ATTRIBUTION.md">
1386    ///     ATTRIBUTION.md
1387    ///   </a>
1388    ///   file.
1389    /// </sub>
1390    pub azimuth: f64,
1391}
1392
1393impl Default for TabletToolAngle {
1394    fn default() -> Self {
1395        Self { altitude: f64::consts::FRAC_2_PI, azimuth: 0. }
1396    }
1397}
1398
1399impl TabletToolAngle {
1400    pub fn tilt(self) -> TabletToolTilt {
1401        // See <https://www.w3.org/TR/2024/WD-pointerevents3-20240326/#converting-between-tiltx-tilty-and-altitudeangle-azimuthangle>.
1402
1403        use std::f64::consts::*;
1404
1405        const PI_0_5: f64 = FRAC_PI_2;
1406        const PI_1_5: f64 = 3. * FRAC_PI_2;
1407        const PI_2: f64 = 2. * PI;
1408
1409        let mut x = 0.;
1410        let mut y = 0.;
1411
1412        if self.altitude == 0. {
1413            if self.azimuth == 0. || self.azimuth == PI_2 {
1414                x = FRAC_PI_2;
1415            } else if self.azimuth == PI_0_5 {
1416                y = FRAC_PI_2;
1417            } else if self.azimuth == PI {
1418                x = -FRAC_PI_2;
1419            } else if self.azimuth == PI_1_5 {
1420                y = -FRAC_PI_2;
1421            } else if self.azimuth > 0. && self.azimuth < PI_0_5 {
1422                x = FRAC_PI_2;
1423                y = FRAC_PI_2;
1424            } else if self.azimuth > PI_0_5 && self.azimuth < PI {
1425                x = -FRAC_PI_2;
1426                y = FRAC_PI_2;
1427            } else if self.azimuth > PI && self.azimuth < PI_1_5 {
1428                x = -FRAC_PI_2;
1429                y = -FRAC_PI_2;
1430            } else if self.azimuth > PI_1_5 && self.azimuth < PI_2 {
1431                x = FRAC_PI_2;
1432                y = -FRAC_PI_2;
1433            }
1434        }
1435
1436        if self.altitude != 0. {
1437            let altitude = self.altitude.tan();
1438
1439            x = f64::atan(f64::cos(self.azimuth) / altitude);
1440            y = f64::atan(f64::sin(self.azimuth) / altitude);
1441        }
1442
1443        TabletToolTilt { x: x.to_degrees().round() as i8, y: y.to_degrees().round() as i8 }
1444    }
1445}
1446
1447/// Describes the input state of a key.
1448#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
1449#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1450#[allow(clippy::exhaustive_enums)]
1451pub enum ElementState {
1452    Pressed,
1453    Released,
1454}
1455
1456impl ElementState {
1457    /// True if `self == Pressed`.
1458    pub fn is_pressed(self) -> bool {
1459        self == ElementState::Pressed
1460    }
1461}
1462
1463/// Identifies a button of a mouse controller.
1464///
1465/// ## Platform-specific
1466///
1467/// The first three buttons should be supported on all platforms.
1468/// [`Self::Back`] and [`Self::Forward`] are supported on most platforms
1469/// (when using a compatible mouse).
1470///
1471/// - **Android, iOS:** Currently not supported.
1472/// - **Orbital:** Only left/right/middle buttons are supported at this time.
1473/// - **Web, Windows:** Supports left/right/middle/back/forward buttons.
1474/// - **Wayland:** Supports buttons 0..=15.
1475/// - **macOS:** Supports all button variants.
1476/// - **X11:** Technically supports further buttons than this (0..=250), these are emitted in
1477///   `ButtonSource::Unknown`.
1478#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1479#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1480#[repr(u8)]
1481#[allow(clippy::exhaustive_enums)]
1482pub enum MouseButton {
1483    /// The primary (usually left) button
1484    Left = 0,
1485    /// The secondary (usually right) button
1486    Right = 1,
1487    /// The tertiary (usually middle) button
1488    Middle = 2,
1489    /// The first side button, frequently assigned a back function
1490    Back = 3,
1491    /// The second side button, frequently assigned a forward function
1492    Forward = 4,
1493    /// The sixth button
1494    Button6 = 5,
1495    /// The seventh button
1496    Button7 = 6,
1497    /// The eighth button
1498    Button8 = 7,
1499    /// The ninth button
1500    Button9 = 8,
1501    /// The tenth button
1502    Button10 = 9,
1503    /// The eleventh button
1504    Button11 = 10,
1505    /// The twelfth button
1506    Button12 = 11,
1507    /// The thirteenth button
1508    Button13 = 12,
1509    /// The fourteenth button
1510    Button14 = 13,
1511    /// The fifteenth button
1512    Button15 = 14,
1513    /// The sixteenth button
1514    Button16 = 15,
1515    Button17 = 16,
1516    Button18 = 17,
1517    Button19 = 18,
1518    Button20 = 19,
1519    Button21 = 20,
1520    Button22 = 21,
1521    Button23 = 22,
1522    Button24 = 23,
1523    Button25 = 24,
1524    Button26 = 25,
1525    Button27 = 26,
1526    Button28 = 27,
1527    Button29 = 28,
1528    Button30 = 29,
1529    Button31 = 30,
1530    Button32 = 31,
1531}
1532
1533impl MouseButton {
1534    /// Construct from a `u8` if within the range `0..=31`
1535    pub fn try_from_u8(b: u8) -> Option<MouseButton> {
1536        Some(match b {
1537            0 => MouseButton::Left,
1538            1 => MouseButton::Right,
1539            2 => MouseButton::Middle,
1540            3 => MouseButton::Back,
1541            4 => MouseButton::Forward,
1542            5 => MouseButton::Button6,
1543            6 => MouseButton::Button7,
1544            7 => MouseButton::Button8,
1545            8 => MouseButton::Button9,
1546            9 => MouseButton::Button10,
1547            10 => MouseButton::Button11,
1548            11 => MouseButton::Button12,
1549            12 => MouseButton::Button13,
1550            13 => MouseButton::Button14,
1551            14 => MouseButton::Button15,
1552            15 => MouseButton::Button16,
1553            16 => MouseButton::Button17,
1554            17 => MouseButton::Button18,
1555            18 => MouseButton::Button19,
1556            19 => MouseButton::Button20,
1557            20 => MouseButton::Button21,
1558            21 => MouseButton::Button22,
1559            22 => MouseButton::Button23,
1560            23 => MouseButton::Button24,
1561            24 => MouseButton::Button25,
1562            25 => MouseButton::Button26,
1563            26 => MouseButton::Button27,
1564            27 => MouseButton::Button28,
1565            28 => MouseButton::Button29,
1566            29 => MouseButton::Button30,
1567            30 => MouseButton::Button31,
1568            31 => MouseButton::Button32,
1569            _ => return None,
1570        })
1571    }
1572}
1573
1574/// Describes a button of a tool, e.g. a pen.
1575#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1576#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
1577#[allow(clippy::exhaustive_enums)]
1578pub enum TabletToolButton {
1579    Contact,
1580    Barrel,
1581    Other(u16),
1582}
1583
1584impl From<TabletToolButton> for Option<MouseButton> {
1585    fn from(tool: TabletToolButton) -> Self {
1586        Some(match tool {
1587            TabletToolButton::Contact => MouseButton::Left,
1588            TabletToolButton::Barrel => MouseButton::Right,
1589            TabletToolButton::Other(1) => MouseButton::Middle,
1590            TabletToolButton::Other(3) => MouseButton::Back,
1591            TabletToolButton::Other(4) => MouseButton::Forward,
1592            TabletToolButton::Other(_) => return None,
1593        })
1594    }
1595}
1596
1597/// Describes a difference in the mouse scroll wheel state.
1598#[derive(Debug, Clone, Copy, PartialEq)]
1599#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1600#[non_exhaustive]
1601pub enum MouseScrollDelta {
1602    /// Amount in lines or rows to scroll in the horizontal
1603    /// and vertical directions.
1604    ///
1605    /// Positive values indicate that the content that is being scrolled should move
1606    /// right and down (revealing more content left and up).
1607    LineDelta(f32, f32),
1608
1609    /// Amount in pixels to scroll in the horizontal and
1610    /// vertical direction.
1611    ///
1612    /// Scroll events are expressed as a `PixelDelta` if
1613    /// supported by the device (eg. a touchpad) and
1614    /// platform.
1615    ///
1616    /// Positive values indicate that the content being scrolled should
1617    /// move right/down.
1618    ///
1619    /// For a 'natural scrolling' touch pad (that acts like a touch screen)
1620    /// this means moving your fingers right and down should give positive values,
1621    /// and move the content right and down (to reveal more things left and up).
1622    PixelDelta(PhysicalPosition<f64>),
1623}
1624
1625/// What is driving a [`WindowEvent::MouseWheel`] event.
1626///
1627/// This lets an application treat the different kinds of scrolling differently, for example
1628/// to not scroll a slider during system-driven momentum scrolling, or to implement its own
1629/// momentum scrolling only on platforms where the system doesn't.
1630#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1631#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1632#[non_exhaustive]
1633pub enum ScrollSource {
1634    /// The platform doesn't say.
1635    Unknown,
1636
1637    /// A physical wheel, usually on a mouse (including tilting it sideways).
1638    Wheel,
1639
1640    /// Fingers moving on a touchpad.
1641    ///
1642    /// On platforms that report a [`TouchPhase`], the fingers are on the touchpad from
1643    /// [`TouchPhase::Started`] until [`TouchPhase::Ended`].
1644    Finger,
1645
1646    /// The system is continuing a finger scroll after the fingers were lifted, with
1647    /// decaying speed, such as the "momentum phase" on macOS.
1648    ///
1649    /// On platforms that report a [`TouchPhase`], this gets its own [`TouchPhase::Started`]
1650    /// … [`TouchPhase::Ended`] cycle after the [`ScrollSource::Finger`] one.
1651    Momentum,
1652}
1653
1654/// Handle to synchronously change the size of the window from the [`WindowEvent`].
1655#[derive(Debug, Clone)]
1656pub struct SurfaceSizeWriter {
1657    pub(crate) new_surface_size: Weak<Mutex<PhysicalSize<u32>>>,
1658}
1659
1660impl SurfaceSizeWriter {
1661    pub fn new(new_surface_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
1662        Self { new_surface_size }
1663    }
1664
1665    /// Try to request surface size which will be set synchronously on the window.
1666    pub fn request_surface_size(
1667        &mut self,
1668        new_surface_size: PhysicalSize<u32>,
1669    ) -> Result<(), InternalError> {
1670        if let Some(inner) = self.new_surface_size.upgrade() {
1671            *inner.lock().unwrap() = new_surface_size;
1672            Ok(())
1673        } else {
1674            Err(InternalError::new("SurfaceSizeWriter connection failed"))
1675        }
1676    }
1677
1678    /// Get the currently stashed surface size.
1679    pub fn surface_size(&self) -> Result<PhysicalSize<u32>, InternalError> {
1680        if let Some(inner) = self.new_surface_size.upgrade() {
1681            Ok(*inner.lock().unwrap())
1682        } else {
1683            Err(InternalError::new("SurfaceSizeWriter connection failed"))
1684        }
1685    }
1686}
1687
1688impl PartialEq for SurfaceSizeWriter {
1689    fn eq(&self, other: &Self) -> bool {
1690        self.new_surface_size.as_ptr() == other.new_surface_size.as_ptr()
1691    }
1692}
1693
1694impl Eq for SurfaceSizeWriter {}
1695
1696#[cfg(test)]
1697mod tests {
1698    use std::collections::{BTreeSet, HashSet};
1699
1700    use dpi::PhysicalPosition;
1701
1702    use crate::event;
1703
1704    macro_rules! foreach_event {
1705        ($closure:expr) => {{
1706            foreach_event!(window: $closure);
1707            foreach_event!(device: $closure);
1708        }};
1709        (window: $closure:expr) => {{
1710            #[allow(unused_mut)]
1711            let mut with_window_event: &mut dyn FnMut(event::WindowEvent) = &mut $closure;
1712            let fid = event::FingerId::from_raw(0);
1713
1714            use crate::event::Ime::Enabled;
1715            use crate::event::WindowEvent::*;
1716            use crate::event::{PointerKind, PointerSource};
1717            use crate::event_loop::DndAction;
1718            use crate::data_transfer::DataTransferId;
1719
1720            let dnd_data = DataTransferId::from_raw(123);
1721
1722            with_window_event(CloseRequested);
1723            with_window_event(Destroyed);
1724            with_window_event(Focused(true));
1725            with_window_event(Moved((0, 0).into()));
1726            with_window_event(SurfaceResized((0, 0).into()));
1727            with_window_event(DragEntered { id: dnd_data, position: None });
1728            with_window_event(DragPosition { id: dnd_data, position: (0, 0).into(), proposed_action: Some(DndAction::Copy) });
1729            with_window_event(DragDropped { id: dnd_data, proposed_action: Some(DndAction::Copy) });
1730            with_window_event(DragLeft { id: dnd_data });
1731            with_window_event(Ime(Enabled));
1732            with_window_event(PointerMoved {
1733                device_id: None,
1734                primary: true,
1735                position: (0, 0).into(),
1736                source: PointerSource::Mouse,
1737            });
1738            with_window_event(ModifiersChanged(event::Modifiers::default()));
1739            with_window_event(PointerEntered {
1740                device_id: None,
1741                primary: true,
1742                position: (0, 0).into(),
1743                kind: PointerKind::Mouse,
1744            });
1745            with_window_event(PointerLeft {
1746                primary: true,
1747                device_id: None,
1748                position: Some((0, 0).into()),
1749                kind: PointerKind::Mouse,
1750            });
1751            with_window_event(MouseWheel {
1752                device_id: None,
1753                delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
1754                phase: event::TouchPhase::Started,
1755                source: event::ScrollSource::Unknown,
1756            });
1757            with_window_event(PointerButton {
1758                device_id: None,
1759                primary: true,
1760                state: event::ElementState::Pressed,
1761                position: (0, 0).into(),
1762                button: event::ButtonSource::Unknown(0),
1763                is_macos_activation_click: false,
1764            });
1765            with_window_event(PointerButton {
1766                device_id: None,
1767                primary: true,
1768                state: event::ElementState::Released,
1769                position: (0, 0).into(),
1770                button: event::ButtonSource::Touch {
1771                    finger_id: fid,
1772                    force: Some(event::Force::Normalized(0.0)),
1773                },
1774                is_macos_activation_click: false,
1775            });
1776            with_window_event(PinchGesture {
1777                device_id: None,
1778                delta: 0.0,
1779                phase: event::TouchPhase::Started,
1780            });
1781            with_window_event(DoubleTapGesture { device_id: None });
1782            with_window_event(RotationGesture {
1783                device_id: None,
1784                delta: 0.0,
1785                phase: event::TouchPhase::Started,
1786            });
1787            with_window_event(PanGesture {
1788                device_id: None,
1789                delta: PhysicalPosition::<f32>::new(0.0, 0.0),
1790                phase: event::TouchPhase::Started,
1791            });
1792            with_window_event(TouchpadPressure { device_id: None, pressure: 0.0, stage: 0 });
1793            with_window_event(ThemeChanged(crate::window::Theme::Light));
1794            with_window_event(Occluded(true));
1795        }};
1796        (device: $closure:expr) => {{
1797            use event::DeviceEvent::*;
1798
1799            #[allow(unused_mut)]
1800            let mut with_device_event: &mut dyn FnMut(event::DeviceEvent) = &mut $closure;
1801
1802            with_device_event(PointerMotion { delta: (0.0, 0.0).into() });
1803            with_device_event(MouseWheel { delta: event::MouseScrollDelta::LineDelta(0.0, 0.0) });
1804            with_device_event(Button { button: 0, state: event::ElementState::Pressed });
1805        }};
1806    }
1807
1808    #[allow(clippy::clone_on_copy)]
1809    #[test]
1810    fn test_event_clone() {
1811        foreach_event!(|event| {
1812            let event2 = event.clone();
1813            assert_eq!(event, event2);
1814        });
1815    }
1816
1817    #[test]
1818    fn test_tilt_angle_conversions() {
1819        use std::f64::consts::*;
1820
1821        use event::{TabletToolAngle, TabletToolTilt};
1822
1823        // See <https://github.com/web-platform-tests/wpt/blob/5af3e9c2a2aba76ade00f0dbc3486e50a74a4506/pointerevents/pointerevent_tiltX_tiltY_to_azimuth_altitude.html#L11-L23>.
1824        const TILT_TO_ANGLE: &[(TabletToolTilt, TabletToolAngle)] = &[
1825            (TabletToolTilt { x: 0, y: 0 }, TabletToolAngle { altitude: FRAC_PI_2, azimuth: 0. }),
1826            (TabletToolTilt { x: 0, y: 90 }, TabletToolAngle { altitude: 0., azimuth: FRAC_PI_2 }),
1827            (
1828                TabletToolTilt { x: 0, y: -90 },
1829                TabletToolAngle { altitude: 0., azimuth: 3. * FRAC_PI_2 },
1830            ),
1831            (TabletToolTilt { x: 90, y: 0 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
1832            (TabletToolTilt { x: 90, y: 90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
1833            (TabletToolTilt { x: 90, y: -90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
1834            (TabletToolTilt { x: -90, y: 0 }, TabletToolAngle { altitude: 0., azimuth: PI }),
1835            (TabletToolTilt { x: -90, y: 90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
1836            (TabletToolTilt { x: -90, y: -90 }, TabletToolAngle { altitude: 0., azimuth: 0. }),
1837            (
1838                TabletToolTilt { x: 0, y: 45 },
1839                TabletToolAngle { altitude: FRAC_PI_4, azimuth: FRAC_PI_2 },
1840            ),
1841            (
1842                TabletToolTilt { x: 0, y: -45 },
1843                TabletToolAngle { altitude: FRAC_PI_4, azimuth: 3. * FRAC_PI_2 },
1844            ),
1845            (TabletToolTilt { x: 45, y: 0 }, TabletToolAngle { altitude: FRAC_PI_4, azimuth: 0. }),
1846            (TabletToolTilt { x: -45, y: 0 }, TabletToolAngle { altitude: FRAC_PI_4, azimuth: PI }),
1847        ];
1848
1849        for (tilt, angle) in TILT_TO_ANGLE {
1850            assert_eq!(tilt.angle(), *angle, "{tilt:?}");
1851        }
1852
1853        // See <https://github.com/web-platform-tests/wpt/blob/5af3e9c2a2aba76ade00f0dbc3486e50a74a4506/pointerevents/pointerevent_tiltX_tiltY_to_azimuth_altitude.html#L38-L46>.
1854        const ANGLE_TO_TILT: &[(TabletToolAngle, TabletToolTilt)] = &[
1855            (TabletToolAngle { altitude: 0., azimuth: 0. }, TabletToolTilt { x: 90, y: 0 }),
1856            (TabletToolAngle { altitude: FRAC_PI_4, azimuth: 0. }, TabletToolTilt { x: 45, y: 0 }),
1857            (TabletToolAngle { altitude: FRAC_PI_2, azimuth: 0. }, TabletToolTilt { x: 0, y: 0 }),
1858            (TabletToolAngle { altitude: 0., azimuth: FRAC_PI_2 }, TabletToolTilt { x: 0, y: 90 }),
1859            (
1860                TabletToolAngle { altitude: FRAC_PI_4, azimuth: FRAC_PI_2 },
1861                TabletToolTilt { x: 0, y: 45 },
1862            ),
1863            (TabletToolAngle { altitude: 0., azimuth: PI }, TabletToolTilt { x: -90, y: 0 }),
1864            (TabletToolAngle { altitude: FRAC_PI_4, azimuth: PI }, TabletToolTilt { x: -45, y: 0 }),
1865            (
1866                TabletToolAngle { altitude: 0., azimuth: 3. * FRAC_PI_2 },
1867                TabletToolTilt { x: 0, y: -90 },
1868            ),
1869            (
1870                TabletToolAngle { altitude: FRAC_PI_4, azimuth: 3. * FRAC_PI_2 },
1871                TabletToolTilt { x: 0, y: -45 },
1872            ),
1873        ];
1874
1875        for (angle, tilt) in ANGLE_TO_TILT {
1876            assert_eq!(angle.tilt(), *tilt, "{angle:?}");
1877        }
1878    }
1879
1880    #[test]
1881    fn test_force_normalize() {
1882        let force = event::Force::Normalized(0.0);
1883        assert_eq!(force.normalized(None), 0.0);
1884
1885        let force2 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 };
1886        assert_eq!(force2.normalized(None), 2.0);
1887
1888        let force3 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 };
1889        assert_eq!(
1890            force3.normalized(Some(event::TabletToolAngle {
1891                altitude: std::f64::consts::PI / 2.0,
1892                azimuth: 0.
1893            })),
1894            2.0
1895        );
1896    }
1897
1898    #[allow(clippy::clone_on_copy)]
1899    #[test]
1900    fn ensure_attrs_do_not_panic() {
1901        foreach_event!(|event| {
1902            let _ = format!("{event:?}");
1903        });
1904        let _ = event::StartCause::Init.clone();
1905
1906        let fid = crate::event::FingerId::from_raw(0).clone();
1907        HashSet::new().insert(fid);
1908        let mut set = [fid, fid, fid];
1909        set.sort_unstable();
1910        let mut set2 = BTreeSet::new();
1911        set2.insert(fid);
1912        set2.insert(fid);
1913
1914        HashSet::new().insert(event::TouchPhase::Started.clone());
1915        HashSet::new().insert(event::MouseButton::Left.clone());
1916        HashSet::new().insert(event::Ime::Enabled);
1917
1918        let _ = event::Force::Calibrated { force: 0.0, max_possible_force: 0.0 }.clone();
1919    }
1920}