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