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 /// 
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 /// 
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 /// 
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 /// 
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}