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