rio_window/event.rs
1//! The [`Event`] enum and assorted supporting types.
2//!
3//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get
4//! processed and used to modify the program state. For more details, see the root-level
5//! documentation.
6//!
7//! Some of these events represent different "parts" of a traditional event-handling loop. You could
8//! approximate the basic ordering loop of [`EventLoop::run_app(...)`] like this:
9//!
10//! ```rust,ignore
11//! let mut start_cause = StartCause::Init;
12//!
13//! while !elwt.exiting() {
14//! app.new_events(event_loop, start_cause);
15//!
16//! for event in (window events, user events, device events) {
17//! // This will pick the right method on the application based on the event.
18//! app.handle_event(event_loop, event);
19//! }
20//!
21//! for window_id in (redraw windows) {
22//! app.window_event(event_loop, window_id, RedrawRequested);
23//! }
24//!
25//! app.about_to_wait(event_loop);
26//! start_cause = wait_if_necessary();
27//! }
28//!
29//! app.exiting(event_loop);
30//! ```
31//!
32//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
33//! describes what happens in what order.
34//!
35//! [`EventLoop::run_app(...)`]: crate::event_loop::EventLoop::run_app
36//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
37use std::path::PathBuf;
38use std::sync::{Mutex, Weak};
39#[cfg(not(web_platform))]
40use std::time::Instant;
41
42use smol_str::SmolStr;
43#[cfg(web_platform)]
44use web_time::Instant;
45
46use crate::dpi::{PhysicalPosition, PhysicalSize};
47use crate::error::ExternalError;
48use crate::event_loop::AsyncRequestSerial;
49use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
50use crate::platform_impl;
51#[cfg(doc)]
52use crate::window::Window;
53use crate::window::{ActivationToken, Theme, WindowId};
54
55/// Describes a generic event.
56///
57/// See the module-level docs for more information on the event loop manages each event.
58#[derive(Debug, Clone, PartialEq)]
59pub enum Event<T: 'static> {
60 /// See [`ApplicationHandler::new_events`] for details.
61 ///
62 /// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events
63 NewEvents(StartCause),
64
65 /// See [`ApplicationHandler::window_event`] for details.
66 ///
67 /// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event
68 WindowEvent {
69 window_id: WindowId,
70 event: WindowEvent,
71 },
72
73 /// See [`ApplicationHandler::device_event`] for details.
74 ///
75 /// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event
76 DeviceEvent {
77 device_id: DeviceId,
78 event: DeviceEvent,
79 },
80
81 /// See [`ApplicationHandler::user_event`] for details.
82 ///
83 /// [`ApplicationHandler::user_event`]: crate::application::ApplicationHandler::user_event
84 UserEvent(T),
85
86 /// See [`ApplicationHandler::suspended`] for details.
87 ///
88 /// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended
89 Suspended,
90
91 /// See [`ApplicationHandler::resumed`] for details.
92 ///
93 /// [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
94 Resumed,
95
96 /// See [`ApplicationHandler::about_to_wait`] for details.
97 ///
98 /// [`ApplicationHandler::about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
99 AboutToWait,
100
101 /// See [`ApplicationHandler::exiting`] for details.
102 ///
103 /// [`ApplicationHandler::exiting`]: crate::application::ApplicationHandler::exiting
104 LoopExiting,
105
106 /// See [`ApplicationHandler::memory_warning`] for details.
107 ///
108 /// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning
109 MemoryWarning,
110
111 Opened {
112 urls: Vec<String>,
113 },
114
115 OpenConfig,
116 HookEvent(Hook),
117}
118
119impl<T> Event<T> {
120 #[allow(clippy::result_large_err)]
121 pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
122 use self::Event::*;
123 match self {
124 UserEvent(_) => Err(self),
125 WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
126 DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
127 NewEvents(cause) => Ok(NewEvents(cause)),
128 AboutToWait => Ok(AboutToWait),
129 LoopExiting => Ok(LoopExiting),
130 Suspended => Ok(Suspended),
131 Resumed => Ok(Resumed),
132 MemoryWarning => Ok(MemoryWarning),
133 Opened { urls } => Ok(Opened { urls }),
134 OpenConfig => Ok(OpenConfig),
135 HookEvent(hook) => Ok(HookEvent(hook)),
136 }
137 }
138}
139
140#[derive(Debug, Clone, PartialEq)]
141pub enum Hook {
142 CreateTab,
143 Close,
144 Copy,
145 Paste,
146 SplitDown,
147 SplitRight,
148}
149
150/// Describes the reason the event loop is resuming.
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum StartCause {
153 /// Sent if the time specified by [`ControlFlow::WaitUntil`] has been reached. Contains the
154 /// moment the timeout was requested and the requested resume time. The actual resume time is
155 /// guaranteed to be equal to or after the requested resume time.
156 ///
157 /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
158 ResumeTimeReached {
159 start: Instant,
160 requested_resume: Instant,
161 },
162
163 /// Sent if the OS has new events to send to the window, after a wait was requested. Contains
164 /// the moment the wait was requested and the resume time, if requested.
165 WaitCancelled {
166 start: Instant,
167 requested_resume: Option<Instant>,
168 },
169
170 /// Sent if the event loop is being resumed after the loop's control flow was set to
171 /// [`ControlFlow::Poll`].
172 ///
173 /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
174 Poll,
175
176 /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized.
177 Init,
178
179 /// Menu or dock can trigger it
180 CreateWindow,
181
182 /// Macos only, executed once app is requesting a reopen
183 MacOSReopen,
184}
185
186/// Describes an event from a [`Window`].
187#[derive(Debug, Clone, PartialEq)]
188pub enum WindowEvent {
189 /// The activation token was delivered back and now could be used.
190 #[cfg_attr(
191 not(any(x11_platform, wayland_platform)),
192 allow(rustdoc::broken_intra_doc_links)
193 )]
194 /// Delivered in response to [`request_activation_token`].
195 ///
196 /// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token
197 ActivationTokenDone {
198 serial: AsyncRequestSerial,
199 token: ActivationToken,
200 },
201
202 /// The size of the window has changed. Contains the client area's new dimensions.
203 Resized(PhysicalSize<u32>),
204
205 /// The position of the window has changed. Contains the window's new position.
206 ///
207 /// ## Platform-specific
208 ///
209 /// - **iOS / Android / Web / Wayland:** Unsupported.
210 Moved(PhysicalPosition<i32>),
211
212 /// The window has been requested to close.
213 CloseRequested,
214
215 /// The window has been destroyed.
216 Destroyed,
217
218 /// A file has been dropped into the window.
219 ///
220 /// When the user drops multiple files at once, this event will be emitted for each file
221 /// separately.
222 DroppedFile(PathBuf),
223
224 /// A file is being hovered over the window.
225 ///
226 /// When the user hovers multiple files at once, this event will be emitted for each file
227 /// separately.
228 HoveredFile(PathBuf),
229
230 /// A file was hovered, but has exited the window.
231 ///
232 /// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
233 /// hovered.
234 HoveredFileCancelled,
235
236 /// The window gained or lost focus.
237 ///
238 /// The parameter is true if the window has gained focus, and false if it has lost focus.
239 Focused(bool),
240
241 /// An event from the keyboard has been received.
242 ///
243 /// ## Platform-specific
244 /// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down,
245 /// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key
246 /// events which are not marked as `is_synthetic`.
247 KeyboardInput {
248 device_id: DeviceId,
249 event: KeyEvent,
250
251 /// If `true`, the event was generated synthetically by winit
252 /// in one of the following circumstances:
253 ///
254 /// * Synthetic key press events are generated for all keys pressed when a window gains
255 /// focus. Likewise, synthetic key release events are generated for all keys pressed when
256 /// a window goes out of focus. ***Currently, this is only functional on X11 and
257 /// Windows***
258 ///
259 /// Otherwise, this value is always `false`.
260 is_synthetic: bool,
261 },
262
263 /// The keyboard modifiers have changed.
264 ModifiersChanged(Modifiers),
265
266 /// An event from an input method.
267 ///
268 /// **Note:** You have to explicitly enable this event using [`Window::set_ime_allowed`].
269 ///
270 /// ## Platform-specific
271 ///
272 /// - **iOS / Android / Web / Orbital:** Unsupported.
273 Ime(Ime),
274
275 /// The cursor has moved on the window.
276 ///
277 /// ## Platform-specific
278 ///
279 /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
280 ///
281 /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
282 /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
283 /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
284 CursorMoved {
285 device_id: DeviceId,
286
287 /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range
288 /// of this data is limited by the display area and it may have been transformed by
289 /// the OS to implement effects such as cursor acceleration, it should not be used
290 /// to implement non-cursor-like interactions such as 3D camera control.
291 position: PhysicalPosition<f64>,
292 },
293
294 /// The cursor has entered the window.
295 ///
296 /// ## Platform-specific
297 ///
298 /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
299 ///
300 /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
301 /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
302 /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
303 CursorEntered { device_id: DeviceId },
304
305 /// The cursor has left the window.
306 ///
307 /// ## Platform-specific
308 ///
309 /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
310 ///
311 /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
312 /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
313 /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
314 CursorLeft { device_id: DeviceId },
315
316 /// A mouse wheel movement or touchpad scroll occurred.
317 MouseWheel {
318 device_id: DeviceId,
319 delta: MouseScrollDelta,
320 phase: TouchPhase,
321 },
322
323 /// An mouse button press has been received.
324 MouseInput {
325 device_id: DeviceId,
326 state: ElementState,
327 button: MouseButton,
328 },
329
330 /// Two-finger pinch gesture, often used for magnification.
331 ///
332 /// ## Platform-specific
333 ///
334 /// - Only available on **macOS** and **iOS**.
335 /// - On iOS, not recognized by default. It must be enabled when needed.
336 PinchGesture {
337 device_id: DeviceId,
338 /// Positive values indicate magnification (zooming in) and negative
339 /// values indicate shrinking (zooming out).
340 ///
341 /// This value may be NaN.
342 delta: f64,
343 phase: TouchPhase,
344 },
345
346 /// N-finger pan gesture
347 ///
348 /// ## Platform-specific
349 ///
350 /// - Only available on **iOS**.
351 /// - On iOS, not recognized by default. It must be enabled when needed.
352 PanGesture {
353 device_id: DeviceId,
354 /// Change in pixels of pan gesture from last update.
355 delta: PhysicalPosition<f32>,
356 phase: TouchPhase,
357 },
358
359 /// Double tap gesture.
360 ///
361 /// On a Mac, smart magnification is triggered by a double tap with two fingers
362 /// on the trackpad and is commonly used to zoom on a certain object
363 /// (e.g. a paragraph of a PDF) or (sort of like a toggle) to reset any zoom.
364 /// The gesture is also supported in Safari, Pages, etc.
365 ///
366 /// The event is general enough that its generating gesture is allowed to vary
367 /// across platforms. It could also be generated by another device.
368 ///
369 /// Unfortunately, neither [Windows](https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741)
370 /// nor [Wayland](https://wayland.freedesktop.org/libinput/doc/latest/gestures.html)
371 /// support this gesture or any other gesture with the same effect.
372 ///
373 /// ## Platform-specific
374 ///
375 /// - Only available on **macOS 10.8** and later, and **iOS**.
376 /// - On iOS, not recognized by default. It must be enabled when needed.
377 DoubleTapGesture { device_id: DeviceId },
378
379 /// Two-finger rotation gesture.
380 ///
381 /// Positive delta values indicate rotation counterclockwise and
382 /// negative delta values indicate rotation clockwise.
383 ///
384 /// ## Platform-specific
385 ///
386 /// - Only available on **macOS** and **iOS**.
387 /// - On iOS, not recognized by default. It must be enabled when needed.
388 RotationGesture {
389 device_id: DeviceId,
390 /// change in rotation in degrees
391 delta: f32,
392 phase: TouchPhase,
393 },
394
395 /// Touchpad pressure event.
396 ///
397 /// At the moment, only supported on Apple forcetouch-capable macbooks.
398 /// The parameters are: pressure level (value between 0 and 1 representing how hard the
399 /// touchpad is being pressed) and stage (integer representing the click level).
400 TouchpadPressure {
401 device_id: DeviceId,
402 pressure: f32,
403 stage: i64,
404 },
405
406 /// Motion on some analog axis. May report data redundant to other, more specific events.
407 AxisMotion {
408 device_id: DeviceId,
409 axis: AxisId,
410 value: f64,
411 },
412
413 /// Touch event has been received
414 ///
415 /// ## Platform-specific
416 ///
417 /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
418 /// - **macOS:** Unsupported.
419 ///
420 /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
421 /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
422 /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
423 Touch(Touch),
424
425 /// The window's scale factor has changed.
426 ///
427 /// The following user actions can cause DPI changes:
428 ///
429 /// * Changing the display's resolution.
430 /// * Changing the display's scale factor (e.g. in Control Panel on Windows).
431 /// * Moving the window to a display with a different scale factor.
432 ///
433 /// To update the window size, use the provided [`InnerSizeWriter`] handle. By default, the
434 /// window is resized to the value suggested by the OS, but it can be changed to any value.
435 ///
436 /// For more information about DPI in general, see the [`dpi`] crate.
437 ScaleFactorChanged {
438 scale_factor: f64,
439 /// Handle to update inner size during scale changes.
440 ///
441 /// See [`InnerSizeWriter`] docs for more details.
442 inner_size_writer: InnerSizeWriter,
443 },
444
445 /// The system window theme has changed.
446 ///
447 /// Applications might wish to react to this to change the theme of the content of the window
448 /// when the system changes the window theme.
449 ///
450 /// ## Platform-specific
451 ///
452 /// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported.
453 ThemeChanged(Theme),
454
455 /// The window has been occluded (completely hidden from view).
456 ///
457 /// This is different to window visibility as it depends on whether the window is closed,
458 /// minimised, set invisible, or fully occluded by another window.
459 ///
460 /// ## Platform-specific
461 ///
462 /// ### iOS
463 ///
464 /// On iOS, the `Occluded(false)` event is emitted in response to an
465 /// [`applicationWillEnterForeground`] callback which means the application should start
466 /// preparing its data. The `Occluded(true)` event is emitted in response to an
467 /// [`applicationDidEnterBackground`] callback which means the application should free
468 /// resources (according to the [iOS application lifecycle]).
469 ///
470 /// [`applicationWillEnterForeground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623076-applicationwillenterforeground
471 /// [`applicationDidEnterBackground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground
472 /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
473 ///
474 /// ### Others
475 ///
476 /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
477 /// - **Android / Wayland / Windows / Orbital:** Unsupported.
478 ///
479 /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
480 /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
481 /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
482 Occluded(bool),
483
484 /// Emitted when a window should be redrawn.
485 ///
486 /// This gets triggered in two scenarios:
487 /// - The OS has performed an operation that's invalidated the window's contents (such as
488 /// resizing the window).
489 /// - The application has explicitly requested a redraw via [`Window::request_redraw`].
490 ///
491 /// Winit will aggregate duplicate redraw requests into a single event, to
492 /// help avoid duplicating rendering work.
493 RedrawRequested,
494}
495
496/// Identifier of an input device.
497///
498/// Whenever you receive an event arising from a particular input device, this event contains a
499/// `DeviceId` which identifies its origin. Note that devices may be virtual (representing an
500/// on-screen cursor and keyboard focus) or physical. Virtual devices typically aggregate inputs
501/// from multiple physical devices.
502#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
503pub struct DeviceId(pub(crate) platform_impl::DeviceId);
504
505impl DeviceId {
506 /// Returns a dummy id, useful for unit testing.
507 ///
508 /// # Safety
509 ///
510 /// The only guarantee made about the return value of this function is that
511 /// it will always be equal to itself and to future values returned by this function.
512 /// No other guarantees are made. This may be equal to a real `DeviceId`.
513 ///
514 /// **Passing this into a winit function will result in undefined behavior.**
515 pub const unsafe fn dummy() -> Self {
516 #[allow(unused_unsafe)]
517 DeviceId(unsafe { platform_impl::DeviceId::dummy() })
518 }
519}
520
521/// Represents raw hardware events that are not associated with any particular window.
522///
523/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera
524/// or first-person game controls. Many physical actions, such as mouse movement, can produce both
525/// device and window events. Because window events typically arise from virtual devices
526/// (corresponding to GUI cursors and keyboard focus) the device IDs may not match.
527///
528/// Note that these events are delivered regardless of input focus.
529#[derive(Clone, Debug, PartialEq)]
530pub enum DeviceEvent {
531 Added,
532 Removed,
533
534 /// Change in physical position of a pointing device.
535 ///
536 /// This represents raw, unfiltered physical motion. Not to be confused with
537 /// [`WindowEvent::CursorMoved`].
538 MouseMotion {
539 /// (x, y) change in position in unspecified units.
540 ///
541 /// Different devices may use different units.
542 delta: (f64, f64),
543 },
544
545 /// Physical scroll event
546 MouseWheel {
547 delta: MouseScrollDelta,
548 },
549
550 /// Motion on some analog axis. This event will be reported for all arbitrary input devices
551 /// that winit supports on this platform, including mouse devices. If the device is a mouse
552 /// device then this will be reported alongside the MouseMotion event.
553 Motion {
554 axis: AxisId,
555 value: f64,
556 },
557
558 Button {
559 button: ButtonId,
560 state: ElementState,
561 },
562
563 Key(RawKeyEvent),
564}
565
566/// Describes a keyboard input as a raw device event.
567///
568/// Note that holding down a key may produce repeated `RawKeyEvent`s. The
569/// operating system doesn't provide information whether such an event is a
570/// repeat or the initial keypress. An application may emulate this by, for
571/// example keeping a Map/Set of pressed keys and determining whether a keypress
572/// corresponds to an already pressed key.
573#[derive(Debug, Clone, Eq, PartialEq, Hash)]
574pub struct RawKeyEvent {
575 pub physical_key: keyboard::PhysicalKey,
576 pub state: ElementState,
577}
578
579/// Describes a keyboard input targeting a window.
580#[derive(Debug, Clone, Eq, PartialEq, Hash)]
581pub struct KeyEvent {
582 /// Represents the position of a key independent of the currently active layout.
583 ///
584 /// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode).
585 /// The most prevalent use case for this is games. For example the default keys for the player
586 /// to move around might be the W, A, S, and D keys on a US layout. The position of these keys
587 /// is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY"
588 /// layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.)
589 ///
590 /// ## Caveats
591 ///
592 /// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that
593 /// implements DVORAK in hardware (or firmware)
594 /// - Your application will likely have to handle keyboards which are missing keys that your
595 /// own keyboard has.
596 /// - Certain `KeyCode`s will move between a couple of different positions depending on what
597 /// layout the keyboard was manufactured to support.
598 ///
599 /// **Because of these caveats, it is important that you provide users with a way to configure
600 /// most (if not all) keybinds in your application.**
601 ///
602 /// ## `Fn` and `FnLock`
603 ///
604 /// `Fn` and `FnLock` key events are *exceedingly unlikely* to be emitted by Winit. These keys
605 /// are usually handled at the hardware or OS level, and aren't surfaced to applications. If
606 /// you somehow see this in the wild, we'd like to know :)
607 pub physical_key: keyboard::PhysicalKey,
608
609 // Allowing `broken_intra_doc_links` for `logical_key`, because
610 // `key_without_modifiers` is not available on all platforms
611 #[cfg_attr(
612 not(any(windows_platform, macos_platform, x11_platform, wayland_platform)),
613 allow(rustdoc::broken_intra_doc_links)
614 )]
615 /// This value is affected by all modifiers except <kbd>Ctrl</kbd>.
616 ///
617 /// This has two use cases:
618 /// - Allows querying whether the current input is a Dead key.
619 /// - Allows handling key-bindings on platforms which don't
620 /// support [`key_without_modifiers`].
621 ///
622 /// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard
623 /// shortcuts, **it is important that you provide users with a way to configure your
624 /// application's shortcuts so you don't render your application unusable for users with an
625 /// incompatible keyboard layout.**
626 ///
627 /// ## Platform-specific
628 /// - **Web:** Dead keys might be reported as the real key instead
629 /// of `Dead` depending on the browser/OS.
630 ///
631 /// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
632 pub logical_key: keyboard::Key,
633
634 /// Contains the text produced by this keypress.
635 ///
636 /// In most cases this is identical to the content
637 /// of the `Character` variant of `logical_key`.
638 /// However, on Windows when a dead key was pressed earlier
639 /// but cannot be combined with the character from this
640 /// keypress, the produced text will consist of two characters:
641 /// the dead-key-character followed by the character resulting
642 /// from this keypress.
643 ///
644 /// An additional difference from `logical_key` is that
645 /// this field stores the text representation of any key
646 /// that has such a representation. For example when
647 /// `logical_key` is `Key::Named(NamedKey::Enter)`, this field is `Some("\r")`.
648 ///
649 /// This is `None` if the current keypress cannot
650 /// be interpreted as text.
651 ///
652 /// See also: `text_with_all_modifiers()`
653 pub text: Option<SmolStr>,
654
655 /// Contains the location of this key on the keyboard.
656 ///
657 /// Certain keys on the keyboard may appear in more than once place. For example, the "Shift"
658 /// key appears on the left side of the QWERTY keyboard as well as the right side. However,
659 /// both keys have the same symbolic value. Another example of this phenomenon is the "1"
660 /// key, which appears both above the "Q" key and as the "Keypad 1" key.
661 ///
662 /// This field allows the user to differentiate between keys like this that have the same
663 /// symbolic value but different locations on the keyboard.
664 ///
665 /// See the [`KeyLocation`] type for more details.
666 ///
667 /// [`KeyLocation`]: crate::keyboard::KeyLocation
668 pub location: keyboard::KeyLocation,
669
670 /// Whether the key is being pressed or released.
671 ///
672 /// See the [`ElementState`] type for more details.
673 pub state: ElementState,
674
675 /// Whether or not this key is a key repeat event.
676 ///
677 /// On some systems, holding down a key for some period of time causes that key to be repeated
678 /// as though it were being pressed and released repeatedly. This field is `true` if and only
679 /// if this event is the result of one of those repeats.
680 ///
681 /// # Example
682 ///
683 /// In games, you often want to ignore repated key events - this can be
684 /// done by ignoring events where this property is set.
685 ///
686 /// ```
687 /// use rio_window::event::{ElementState, KeyEvent, WindowEvent};
688 /// use rio_window::keyboard::{KeyCode, PhysicalKey};
689 /// # let window_event = WindowEvent::RedrawRequested; // To make the example compile
690 /// match window_event {
691 /// WindowEvent::KeyboardInput {
692 /// event:
693 /// KeyEvent {
694 /// physical_key: PhysicalKey::Code(KeyCode::KeyW),
695 /// state: ElementState::Pressed,
696 /// repeat: false,
697 /// ..
698 /// },
699 /// ..
700 /// } => {
701 /// // The physical key `W` was pressed, and it was not a repeat
702 /// },
703 /// _ => {}, // Handle other events
704 /// }
705 /// ```
706 pub repeat: bool,
707
708 /// Platform-specific key event information.
709 ///
710 /// On Windows, Linux and macOS, this type contains the key without modifiers and the text with
711 /// all modifiers applied.
712 ///
713 /// On Android, iOS, Redox and Web, this type is a no-op.
714 pub(crate) platform_specific: platform_impl::KeyEventExtra,
715}
716
717/// Describes keyboard modifiers event.
718#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
719pub struct Modifiers {
720 pub(crate) state: ModifiersState,
721
722 // NOTE: Currently pressed modifiers keys.
723 //
724 // The field providing a metadata, it shouldn't be used as a source of truth.
725 pub(crate) pressed_mods: ModifiersKeys,
726}
727
728impl Modifiers {
729 /// The state of the modifiers.
730 pub fn state(&self) -> ModifiersState {
731 self.state
732 }
733
734 /// The state of the left shift key.
735 pub fn lshift_state(&self) -> ModifiersKeyState {
736 self.mod_state(ModifiersKeys::LSHIFT)
737 }
738
739 /// The state of the right shift key.
740 pub fn rshift_state(&self) -> ModifiersKeyState {
741 self.mod_state(ModifiersKeys::RSHIFT)
742 }
743
744 /// The state of the left alt key.
745 pub fn lalt_state(&self) -> ModifiersKeyState {
746 self.mod_state(ModifiersKeys::LALT)
747 }
748
749 /// The state of the right alt key.
750 pub fn ralt_state(&self) -> ModifiersKeyState {
751 self.mod_state(ModifiersKeys::RALT)
752 }
753
754 /// The state of the left control key.
755 pub fn lcontrol_state(&self) -> ModifiersKeyState {
756 self.mod_state(ModifiersKeys::LCONTROL)
757 }
758
759 /// The state of the right control key.
760 pub fn rcontrol_state(&self) -> ModifiersKeyState {
761 self.mod_state(ModifiersKeys::RCONTROL)
762 }
763
764 /// The state of the left super key.
765 pub fn lsuper_state(&self) -> ModifiersKeyState {
766 self.mod_state(ModifiersKeys::LSUPER)
767 }
768
769 /// The state of the right super key.
770 pub fn rsuper_state(&self) -> ModifiersKeyState {
771 self.mod_state(ModifiersKeys::RSUPER)
772 }
773
774 fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState {
775 if self.pressed_mods.contains(modifier) {
776 ModifiersKeyState::Pressed
777 } else {
778 ModifiersKeyState::Unknown
779 }
780 }
781}
782
783impl From<ModifiersState> for Modifiers {
784 fn from(value: ModifiersState) -> Self {
785 Self {
786 state: value,
787 pressed_mods: Default::default(),
788 }
789 }
790}
791
792/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
793///
794/// This is also called a "composition event".
795///
796/// Most keypresses using a latin-like keyboard layout simply generate a
797/// [`WindowEvent::KeyboardInput`]. However, one couldn't possibly have a key for every single
798/// unicode character that the user might want to type
799/// - so the solution operating systems employ is to allow the user to type these using _a sequence
800/// of keypresses_ instead.
801///
802/// A prominent example of this is accents - many keyboard layouts allow you to first click the
803/// "accent key", and then the character you want to apply the accent to. In this case, some
804/// platforms will generate the following event sequence:
805///
806/// ```ignore
807/// // Press "`" key
808/// Ime::Preedit("`", Some((0, 0)))
809/// // Press "E" key
810/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
811/// Ime::Commit("é")
812/// ```
813///
814/// Additionally, certain input devices are configured to display a candidate box that allow the
815/// user to select the desired character interactively. (To properly position this box, you must use
816/// [`Window::set_ime_cursor_area`].)
817///
818/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the
819/// following event sequence could be obtained:
820///
821/// ```ignore
822/// // Press "A" key
823/// Ime::Preedit("a", Some((1, 1)))
824/// // Press "B" key
825/// Ime::Preedit("a b", Some((3, 3)))
826/// // Press left arrow key
827/// Ime::Preedit("a b", Some((1, 1)))
828/// // Press space key
829/// Ime::Preedit("啊b", Some((3, 3)))
830/// // Press space key
831/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
832/// Ime::Commit("啊不")
833/// ```
834#[derive(Debug, Clone, PartialEq, Eq, Hash)]
835pub enum Ime {
836 /// Notifies when the IME was enabled.
837 ///
838 /// After getting this event you could receive [`Preedit`][Self::Preedit] and
839 /// [`Commit`][Self::Commit] events. You should also start performing IME related requests
840 /// like [`Window::set_ime_cursor_area`].
841 Enabled,
842
843 /// Notifies when a new composing text should be set at the cursor position.
844 ///
845 /// The value represents a pair of the preedit string and the cursor begin position and end
846 /// position. When it's `None`, the cursor should be hidden. When `String` is an empty string
847 /// this indicates that preedit was cleared.
848 ///
849 /// The cursor position is byte-wise indexed.
850 Preedit(String, Option<(usize, usize)>),
851
852 /// Notifies when text should be inserted into the editor widget.
853 ///
854 /// Right before this event winit will send empty [`Self::Preedit`] event.
855 Commit(String),
856
857 /// Notifies when the IME was disabled.
858 ///
859 /// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or
860 /// [`Commit`][Self::Commit] events until the next [`Enabled`][Self::Enabled] event. You should
861 /// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear
862 /// pending preedit text.
863 Disabled,
864}
865
866/// Describes touch-screen input state.
867#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
868pub enum TouchPhase {
869 Started,
870 Moved,
871 Ended,
872 Cancelled,
873}
874
875/// Represents a touch event
876///
877/// Every time the user touches the screen, a new [`TouchPhase::Started`] event with an unique
878/// identifier for the finger is generated. When the finger is lifted, an [`TouchPhase::Ended`]
879/// event is generated with the same finger id.
880///
881/// After a `Started` event has been emitted, there may be zero or more `Move`
882/// events when the finger is moved or the touch pressure changes.
883///
884/// The finger id may be reused by the system after an `Ended` event. The user
885/// should assume that a new `Started` event received with the same id has nothing
886/// to do with the old finger and is a new finger.
887///
888/// A [`TouchPhase::Cancelled`] event is emitted when the system has canceled tracking this
889/// touch, such as when the window loses focus, or on iOS if the user moves the
890/// device against their face.
891///
892/// ## Platform-specific
893///
894/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
895/// - **macOS:** Unsupported.
896///
897/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
898/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
899/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
900#[derive(Debug, Clone, Copy, PartialEq)]
901pub struct Touch {
902 pub device_id: DeviceId,
903 pub phase: TouchPhase,
904 pub location: PhysicalPosition<f64>,
905 /// Describes how hard the screen was pressed. May be `None` if the platform
906 /// does not support pressure sensitivity.
907 ///
908 /// ## Platform-specific
909 ///
910 /// - Only available on **iOS** 9.0+, **Windows** 8+, **Web**, and **Android**.
911 pub force: Option<Force>,
912 /// Unique identifier of a finger.
913 pub id: u64,
914}
915
916/// Describes the force of a touch event
917#[derive(Debug, Clone, Copy, PartialEq)]
918pub enum Force {
919 /// On iOS, the force is calibrated so that the same number corresponds to
920 /// roughly the same amount of pressure on the screen regardless of the
921 /// device.
922 Calibrated {
923 /// The force of the touch, where a value of 1.0 represents the force of
924 /// an average touch (predetermined by the system, not user-specific).
925 ///
926 /// The force reported by Apple Pencil is measured along the axis of the
927 /// pencil. If you want a force perpendicular to the device, you need to
928 /// calculate this value using the `altitude_angle` value.
929 force: f64,
930 /// The maximum possible force for a touch.
931 ///
932 /// The value of this field is sufficiently high to provide a wide
933 /// dynamic range for values of the `force` field.
934 max_possible_force: f64,
935 /// The altitude (in radians) of the stylus.
936 ///
937 /// A value of 0 radians indicates that the stylus is parallel to the
938 /// surface. The value of this property is Pi/2 when the stylus is
939 /// perpendicular to the surface.
940 altitude_angle: Option<f64>,
941 },
942 /// If the platform reports the force as normalized, we have no way of
943 /// knowing how much pressure 1.0 corresponds to – we know it's the maximum
944 /// amount of force, but as to how much force, you might either have to
945 /// press really really hard, or not hard at all, depending on the device.
946 Normalized(f64),
947}
948
949impl Force {
950 /// Returns the force normalized to the range between 0.0 and 1.0 inclusive.
951 ///
952 /// Instead of normalizing the force, you should prefer to handle
953 /// [`Force::Calibrated`] so that the amount of force the user has to apply is
954 /// consistent across devices.
955 pub fn normalized(&self) -> f64 {
956 match self {
957 Force::Calibrated {
958 force,
959 max_possible_force,
960 altitude_angle,
961 } => {
962 let force = match altitude_angle {
963 Some(altitude_angle) => force / altitude_angle.sin(),
964 None => *force,
965 };
966 force / max_possible_force
967 }
968 Force::Normalized(force) => *force,
969 }
970 }
971}
972
973/// Identifier for a specific analog axis on some device.
974pub type AxisId = u32;
975
976/// Identifier for a specific button on some device.
977pub type ButtonId = u32;
978
979/// Describes the input state of a key.
980#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
981pub enum ElementState {
982 Pressed,
983 Released,
984}
985
986impl ElementState {
987 /// True if `self == Pressed`.
988 pub fn is_pressed(self) -> bool {
989 self == ElementState::Pressed
990 }
991}
992
993/// Describes a button of a mouse controller.
994///
995/// ## Platform-specific
996///
997/// **macOS:** `Back` and `Forward` might not work with all hardware.
998/// **Orbital:** `Back` and `Forward` are unsupported due to orbital not supporting them.
999#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1000pub enum MouseButton {
1001 Left,
1002 Right,
1003 Middle,
1004 Back,
1005 Forward,
1006 Other(u16),
1007}
1008
1009/// Describes a difference in the mouse scroll wheel state.
1010#[derive(Debug, Clone, Copy, PartialEq)]
1011pub enum MouseScrollDelta {
1012 /// Amount in lines or rows to scroll in the horizontal
1013 /// and vertical directions.
1014 ///
1015 /// Positive values indicate that the content that is being scrolled should move
1016 /// right and down (revealing more content left and up).
1017 LineDelta(f32, f32),
1018
1019 /// Amount in pixels to scroll in the horizontal and
1020 /// vertical direction.
1021 ///
1022 /// Scroll events are expressed as a `PixelDelta` if
1023 /// supported by the device (eg. a touchpad) and
1024 /// platform.
1025 ///
1026 /// Positive values indicate that the content being scrolled should
1027 /// move right/down.
1028 ///
1029 /// For a 'natural scrolling' touch pad (that acts like a touch screen)
1030 /// this means moving your fingers right and down should give positive values,
1031 /// and move the content right and down (to reveal more things left and up).
1032 PixelDelta(PhysicalPosition<f64>),
1033}
1034
1035/// Handle to synchronously change the size of the window from the
1036/// [`WindowEvent`].
1037#[derive(Debug, Clone)]
1038pub struct InnerSizeWriter {
1039 pub(crate) new_inner_size: Weak<Mutex<PhysicalSize<u32>>>,
1040}
1041
1042impl InnerSizeWriter {
1043 #[cfg(not(orbital_platform))]
1044 pub(crate) fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
1045 Self { new_inner_size }
1046 }
1047
1048 /// Try to request inner size which will be set synchronously on the window.
1049 pub fn request_inner_size(
1050 &mut self,
1051 new_inner_size: PhysicalSize<u32>,
1052 ) -> Result<(), ExternalError> {
1053 if let Some(inner) = self.new_inner_size.upgrade() {
1054 *inner.lock().unwrap() = new_inner_size;
1055 Ok(())
1056 } else {
1057 Err(ExternalError::Ignored)
1058 }
1059 }
1060}
1061
1062impl PartialEq for InnerSizeWriter {
1063 fn eq(&self, other: &Self) -> bool {
1064 self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr()
1065 }
1066}
1067
1068#[cfg(test)]
1069mod tests {
1070 use crate::dpi::PhysicalPosition;
1071 use crate::event;
1072 use std::collections::{BTreeSet, HashSet};
1073
1074 macro_rules! foreach_event {
1075 ($closure:expr) => {{
1076 #[allow(unused_mut)]
1077 let mut x = $closure;
1078 let did = unsafe { event::DeviceId::dummy() };
1079
1080 #[allow(deprecated)]
1081 {
1082 use crate::event::Event::*;
1083 use crate::event::Ime::Enabled;
1084 use crate::event::WindowEvent::*;
1085 use crate::window::WindowId;
1086
1087 // Mainline events.
1088 let wid = unsafe { WindowId::dummy() };
1089 x(UserEvent(()));
1090 x(NewEvents(event::StartCause::Init));
1091 x(AboutToWait);
1092 x(LoopExiting);
1093 x(Suspended);
1094 x(Resumed);
1095
1096 // Window events.
1097 let with_window_event = |wev| {
1098 x(WindowEvent {
1099 window_id: wid,
1100 event: wev,
1101 })
1102 };
1103
1104 with_window_event(CloseRequested);
1105 with_window_event(Destroyed);
1106 with_window_event(Focused(true));
1107 with_window_event(Moved((0, 0).into()));
1108 with_window_event(Resized((0, 0).into()));
1109 with_window_event(DroppedFile("x.txt".into()));
1110 with_window_event(HoveredFile("x.txt".into()));
1111 with_window_event(HoveredFileCancelled);
1112 with_window_event(Ime(Enabled));
1113 with_window_event(CursorMoved {
1114 device_id: did,
1115 position: (0, 0).into(),
1116 });
1117 with_window_event(ModifiersChanged(event::Modifiers::default()));
1118 with_window_event(CursorEntered { device_id: did });
1119 with_window_event(CursorLeft { device_id: did });
1120 with_window_event(MouseWheel {
1121 device_id: did,
1122 delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
1123 phase: event::TouchPhase::Started,
1124 });
1125 with_window_event(MouseInput {
1126 device_id: did,
1127 state: event::ElementState::Pressed,
1128 button: event::MouseButton::Other(0),
1129 });
1130 with_window_event(PinchGesture {
1131 device_id: did,
1132 delta: 0.0,
1133 phase: event::TouchPhase::Started,
1134 });
1135 with_window_event(DoubleTapGesture { device_id: did });
1136 with_window_event(RotationGesture {
1137 device_id: did,
1138 delta: 0.0,
1139 phase: event::TouchPhase::Started,
1140 });
1141 with_window_event(PanGesture {
1142 device_id: did,
1143 delta: PhysicalPosition::<f32>::new(0.0, 0.0),
1144 phase: event::TouchPhase::Started,
1145 });
1146 with_window_event(TouchpadPressure {
1147 device_id: did,
1148 pressure: 0.0,
1149 stage: 0,
1150 });
1151 with_window_event(AxisMotion {
1152 device_id: did,
1153 axis: 0,
1154 value: 0.0,
1155 });
1156 with_window_event(Touch(event::Touch {
1157 device_id: did,
1158 phase: event::TouchPhase::Started,
1159 location: (0.0, 0.0).into(),
1160 id: 0,
1161 force: Some(event::Force::Normalized(0.0)),
1162 }));
1163 with_window_event(ThemeChanged(crate::window::Theme::Light));
1164 with_window_event(Occluded(true));
1165 }
1166
1167 #[allow(deprecated)]
1168 {
1169 use event::DeviceEvent::*;
1170
1171 let with_device_event = |dev_ev| {
1172 x(event::Event::DeviceEvent {
1173 device_id: did,
1174 event: dev_ev,
1175 })
1176 };
1177
1178 with_device_event(Added);
1179 with_device_event(Removed);
1180 with_device_event(MouseMotion {
1181 delta: (0.0, 0.0).into(),
1182 });
1183 with_device_event(MouseWheel {
1184 delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
1185 });
1186 with_device_event(Motion {
1187 axis: 0,
1188 value: 0.0,
1189 });
1190 with_device_event(Button {
1191 button: 0,
1192 state: event::ElementState::Pressed,
1193 });
1194 }
1195 }};
1196 }
1197
1198 #[allow(clippy::redundant_clone)]
1199 #[test]
1200 fn test_event_clone() {
1201 foreach_event!(|event: event::Event<()>| {
1202 let event2 = event.clone();
1203 assert_eq!(event, event2);
1204 })
1205 }
1206
1207 #[test]
1208 fn test_map_nonuser_event() {
1209 foreach_event!(|event: event::Event<()>| {
1210 let is_user = matches!(event, event::Event::UserEvent(()));
1211 let event2 = event.map_nonuser_event::<()>();
1212 if is_user {
1213 assert_eq!(event2, Err(event::Event::UserEvent(())));
1214 } else {
1215 assert!(event2.is_ok());
1216 }
1217 })
1218 }
1219
1220 #[test]
1221 fn test_force_normalize() {
1222 let force = event::Force::Normalized(0.0);
1223 assert_eq!(force.normalized(), 0.0);
1224
1225 let force2 = event::Force::Calibrated {
1226 force: 5.0,
1227 max_possible_force: 2.5,
1228 altitude_angle: None,
1229 };
1230 assert_eq!(force2.normalized(), 2.0);
1231
1232 let force3 = event::Force::Calibrated {
1233 force: 5.0,
1234 max_possible_force: 2.5,
1235 altitude_angle: Some(std::f64::consts::PI / 2.0),
1236 };
1237 assert_eq!(force3.normalized(), 2.0);
1238 }
1239
1240 #[allow(clippy::clone_on_copy)]
1241 #[test]
1242 fn ensure_attrs_do_not_panic() {
1243 foreach_event!(|event: event::Event<()>| {
1244 let _ = format!("{:?}", event);
1245 });
1246 let _ = event::StartCause::Init.clone();
1247
1248 let did = unsafe { crate::event::DeviceId::dummy() }.clone();
1249 HashSet::new().insert(did);
1250 let mut set = [did, did, did];
1251 set.sort_unstable();
1252 let mut set2 = BTreeSet::new();
1253 set2.insert(did);
1254 set2.insert(did);
1255
1256 HashSet::new().insert(event::TouchPhase::Started.clone());
1257 HashSet::new().insert(event::MouseButton::Left.clone());
1258 HashSet::new().insert(event::Ime::Enabled);
1259
1260 let _ = event::Touch {
1261 device_id: did,
1262 phase: event::TouchPhase::Started,
1263 location: (0.0, 0.0).into(),
1264 id: 0,
1265 force: Some(event::Force::Normalized(0.0)),
1266 }
1267 .clone();
1268 let _ = event::Force::Calibrated {
1269 force: 0.0,
1270 max_possible_force: 0.0,
1271 altitude_angle: None,
1272 }
1273 .clone();
1274 }
1275}