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