kas_core/event/event.rs
1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4// https://www.apache.org/licenses/LICENSE-2.0
5
6//! Event handling: `Event` type and dependencies
7
8use cast::CastApprox;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12use super::{EventCx, IsUsed, TimerHandle, Unused, Used};
13#[allow(unused)] use super::{EventState, GrabMode};
14use super::{Key, KeyEvent, NamedKey, PhysicalKey, Press, PressStart};
15use crate::geom::{Affine, Offset, Vec2};
16#[allow(unused)] use crate::{Events, window::Popup};
17use crate::{Id, dir::Direction, window::WindowId};
18
19/// Events addressed to a widget
20///
21/// Note that a few events are received by disabled widgets; see
22/// [`Event::pass_when_disabled`].
23#[non_exhaustive]
24#[derive(Clone, Debug, PartialEq)]
25pub enum Event<'a> {
26 /// Command input
27 ///
28 /// A generic "command". The source is often but not always a key press.
29 /// In many cases (but not all) the target widget has navigation focus.
30 ///
31 /// A [`PhysicalKey`] is attached when the command is caused by a key press.
32 /// The recipient may use this to call [`EventState::depress_with_key`].
33 ///
34 /// If a widget has keyboard input focus (see
35 /// [`EventState::request_key_focus`]) it will instead receive
36 /// [`Event::Key`] for key presses (but may still receive `Event::Command`
37 /// from other sources).
38 Command(Command, Option<PhysicalKey>),
39 /// Keyboard input: `event, is_synthetic`
40 ///
41 /// This is only received by a widget with character focus (see
42 /// [`EventState::request_key_focus`]).
43 ///
44 /// On some platforms, synthetic key events are generated when a window
45 /// gains or loses focus with a key held (see documentation of
46 /// [`winit::event::WindowEvent::KeyboardInput`]). This is indicated by the
47 /// second parameter, `is_synthetic`. Unless you need to track key states
48 /// it is advised only to match `Event::Key(event, false)`.
49 ///
50 /// Some key presses can be mapped to a [`Command`]. To do this (normally
51 /// only when `event.state == ElementState::Pressed && !is_synthetic`), use
52 /// `cx.config().shortcuts(|s| s.try_match(cx.modifiers(), &event.logical_key)`
53 /// or (omitting shortcut matching) `Command::new(event.logical_key)`.
54 /// Note that if the handler returns [`Unused`] the widget might
55 /// then receive [`Event::Command`] for the same key press, but this is not
56 /// guaranteed (behaviour may change in future versions).
57 ///
58 /// For standard text input, simply consume `event.text` when
59 /// `event.state == ElementState::Pressed && !is_synthetic`.
60 /// NOTE: unlike Winit, we force `text = None` for control chars and when
61 /// <kbd>Ctrl</kbd>, <kbd>Alt</kbd> or <kbd>Super</kbd> modifier keys are
62 /// pressed. This is subject to change.
63 Key(&'a KeyEvent, bool),
64 /// Input Method Editor: composed text changed
65 ///
66 /// Parameters are `text, cursor`.
67 ///
68 /// This is only received after
69 /// [requesting key focus](EventState::request_key_focus) with some `ime`
70 /// purpose.
71 ImePreedit(&'a str, Option<(usize, usize)>),
72 /// Input Method Editor: composed text committed
73 ///
74 /// Parameters are `text`.
75 ///
76 /// This is only received after
77 /// [requesting key focus](EventState::request_key_focus) with some `ime`
78 /// purpose.
79 ImeCommit(&'a str),
80 /// A mouse or touchpad scroll event
81 Scroll(ScrollDelta),
82 /// A mouse or touch-screen move/zoom/rotate event
83 ///
84 /// This event is sent for certain types of grab ([`PressStart::grab`]),
85 /// enabling two-finger scale/rotate gestures as well as translation.
86 ///
87 /// Mouse-grabs generate translation (`delta` component) only. Touch grabs
88 /// optionally also generate rotation and scaling components, depending on
89 /// the [`GrabMode`].
90 Pan(Affine),
91 /// Movement of mouse cursor without press
92 ///
93 /// This event is only sent one case: when the mouse is moved while a
94 /// [`Popup`] is open and there is not an active [`PressStart::grab`] on the
95 /// mouse cursor.
96 ///
97 /// This event may be sent 10+ times per frame, thus it is important that
98 /// the handler be fast. It may be useful to schedule a pre-draw update
99 /// with [`EventState::request_frame_timer`] to handle any post-move
100 /// updates.
101 CursorMove { press: Press },
102 /// A mouse button was pressed or touch event started
103 ///
104 /// Call [`PressStart::grab`] in order to "grab" corresponding motion
105 /// and release events.
106 ///
107 /// This event is sent to the widget under the mouse or touch position. If
108 /// no such widget is found, this event is not sent.
109 PressStart(PressStart),
110 /// Movement of mouse or a touch press
111 ///
112 /// This event is only sent when a ([`PressStart::grab`]) is active.
113 /// Motion events for the grabbed mouse pointer or touched finger are sent.
114 ///
115 /// If `cur_id` is `None`, no widget was found at the coordinate (either
116 /// outside the window or [`crate::Layout::try_probe`] failed).
117 ///
118 /// This event may be sent 10+ times per frame, thus it is important that
119 /// the handler be fast. It may be useful to schedule a pre-draw update
120 /// with [`EventState::request_frame_timer`] to handle any post-move
121 /// updates.
122 PressMove { press: Press, delta: Vec2 },
123 /// End of a click/touch press
124 ///
125 /// If `success`, this is a button-release or touch finish; otherwise this
126 /// is a cancelled/interrupted grab. "Activation events" (e.g. clicking of a
127 /// button or menu item) should only happen on `success`. "Movement events"
128 /// such as panning, moving a slider or opening a menu should not be undone
129 /// when cancelling: the panned item or slider should be released as is, or
130 /// the menu should remain open.
131 ///
132 /// This event is only sent when a ([`PressStart::grab`]) is active.
133 /// Release/cancel events for the same mouse button or touched finger are
134 /// sent.
135 ///
136 /// If `cur_id` is `None`, no widget was found at the coordinate (either
137 /// outside the window or [`crate::Layout::try_probe`] failed).
138 PressEnd { press: Press, success: bool },
139 /// Update from a timer
140 ///
141 /// This event must be requested by [`EventState::request_timer`].
142 Timer(TimerHandle),
143 /// Notification that a popup has been closed
144 ///
145 /// This is sent to the popup when closed.
146 /// Since popups may be removed directly by the [`EventCx`], the parent should
147 /// clean up any associated state here.
148 #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
149 #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
150 PopupClosed(WindowId),
151 /// Notification that a widget has gained navigation focus
152 ///
153 /// Navigation focus implies that the widget is highlighted and will be the
154 /// primary target of [`Event::Command`], and is thus able to receive basic
155 /// keyboard input (e.g. arrow keys). To receive full keyboard input
156 /// ([`Event::Key`]), call [`EventState::request_key_focus`].
157 ///
158 /// With [`FocusSource::Pointer`] the widget should already have received
159 /// [`Event::PressStart`].
160 ///
161 /// With [`FocusSource::Key`], [`EventCx::set_scroll`] is
162 /// called automatically (to ensure that the widget is visible) and the
163 /// response will be forced to [`Used`].
164 NavFocus(FocusSource),
165 /// Notification that a widget has lost navigation focus
166 LostNavFocus,
167 /// Notification that a widget has gained selection focus
168 ///
169 /// This focus must be requested by calling
170 /// [`EventState::request_sel_focus`] or [`EventState::request_key_focus`].
171 SelFocus(FocusSource),
172 /// Notification that a widget has lost selection focus
173 ///
174 /// In the case the widget also had character focus, [`Event::LostKeyFocus`] is
175 /// received first.
176 LostSelFocus,
177 /// Notification that a widget has gained keyboard input focus
178 ///
179 /// This focus must be requested by calling
180 /// [`EventState::request_key_focus`].
181 ///
182 /// This is always preceeded by [`Event::SelFocus`] and is received prior to
183 /// [`Event::Key`] events.
184 KeyFocus,
185 /// Notification that a widget has lost keyboard input focus
186 LostKeyFocus,
187 /// Notification that a widget has gained IME focus
188 ///
189 /// The widget should call [`EventState::set_ime_cursor_area`] immediately
190 /// and each time the area changes (relative to the widget's coordinate
191 /// space), until [`Event::LostImeFocus`] is received. Failure to do so will
192 /// result in the widget's entire `rect` being used as the IME cursor area.
193 ImeFocus,
194 /// Notification that a widget has lost IME focus
195 LostImeFocus,
196 /// Notification that the mouse moves over or leaves a widget
197 ///
198 /// The state is `true` on mouse over, `false` when the mouse leaves.
199 MouseOver(bool),
200}
201
202impl<'a> std::ops::Add<Offset> for Event<'a> {
203 type Output = Self;
204
205 #[inline]
206 fn add(mut self, offset: Offset) -> Self {
207 self += offset;
208 self
209 }
210}
211
212impl<'a> std::ops::AddAssign<Offset> for Event<'a> {
213 fn add_assign(&mut self, offset: Offset) {
214 match self {
215 Event::CursorMove { press } => {
216 press.coord += offset;
217 }
218 Event::PressStart(press) => {
219 *press += offset;
220 }
221 Event::PressMove { press, .. } => {
222 press.coord += offset;
223 }
224 Event::PressEnd { press, .. } => {
225 press.coord += offset;
226 }
227 _ => (),
228 }
229 }
230}
231
232impl<'a> Event<'a> {
233 /// Call `f` on "click" or any "activation" event
234 ///
235 /// This is a convenience method which calls `f` on any of the following:
236 ///
237 /// - Mouse click and release on the same widget
238 /// - Touchscreen press and release on the same widget
239 /// - `Event::Command(cmd, _)` where [`cmd.is_activate()`](Command::is_activate)
240 ///
241 /// This method has some side effects:
242 ///
243 /// - [`Event::PressStart`] is grabbed using [`GrabMode::Click`]
244 /// - [`Event::Command`] with a key will cause `id` to be depressed until
245 /// that key is released (see [`EventState::depress_with_key`]).
246 pub fn on_click<F: FnOnce(&mut EventCx)>(self, cx: &mut EventCx, id: Id, f: F) -> IsUsed {
247 match self {
248 Event::Command(cmd, code) if cmd.is_activate() => {
249 cx.depress_with_key(id, code);
250 f(cx);
251 Used
252 }
253 Event::PressStart(press) if press.is_primary() => press.grab_click(id).complete(cx),
254 Event::PressEnd { press, success, .. } => {
255 if success && id == press.id {
256 f(cx);
257 }
258 Used
259 }
260 _ => Unused,
261 }
262 }
263
264 /// Pass to disabled widgets?
265 ///
266 /// When a widget is disabled:
267 ///
268 /// - New input events (`Command`, `PressStart`, `Scroll`) are not passed
269 /// - Continuing input actions (`PressMove`, `PressEnd`) are passed (or
270 /// the input sequence may be terminated).
271 /// - New focus notifications are not passed
272 /// - Focus-loss notifications are passed
273 /// - Requested events like `Timer` are passed
274 pub fn pass_when_disabled(&self) -> bool {
275 use Event::*;
276 match self {
277 Command(_, _) => false,
278 Key(_, _) | ImePreedit(_, _) | ImeCommit(_) | Scroll(_) => false,
279 CursorMove { .. } | PressStart(_) => false,
280 Pan { .. } | PressMove { .. } | PressEnd { .. } => true,
281 Timer(_) | PopupClosed(_) => true,
282 NavFocus { .. } | SelFocus(_) | KeyFocus | ImeFocus | MouseOver(true) => false,
283 LostNavFocus | LostKeyFocus | LostSelFocus | LostImeFocus | MouseOver(false) => true,
284 }
285 }
286
287 /// Can the event be received by [`Events::handle_event`] during unwinding?
288 ///
289 /// Some events are sent to the widget with navigation focus (e.g.
290 /// [`Event::Command`]). Others are sent to the widget under the mouse (e.g.
291 /// [`Event::PressStart`]). All these events may be "reused" by an ancestor
292 /// widget if not [`Used`] by the original target.
293 ///
294 /// Other events are sent to a specific widget as a result of a request
295 /// (e.g. [`Event::Key`], [`Event::PressEnd`]), or as a notification of
296 /// focus change (e.g. [`Event::LostKeyFocus`]). These events may never be
297 /// "reused".
298 ///
299 /// Note: this could alternatively be seen as a property of the addressing
300 /// mechanism, currently just an [`Id`].
301 pub fn is_reusable(&self) -> bool {
302 use Event::*;
303 match self {
304 // Events sent to navigation focus given some code,
305 // otherwise sent to a specific target.
306 Command(_, code) => code.is_some(),
307
308 // Events sent to mouse focus
309 Scroll(_) | Pan { .. } => true,
310 CursorMove { .. } | PressStart(_) => true,
311
312 // Events sent to requester
313 Key(_, _) | ImePreedit(_, _) | ImeCommit(_) => false,
314 PressMove { .. } | PressEnd { .. } => false,
315 Timer(_) => false,
316
317 // Notifications of focus/status change
318 PopupClosed(_) => false,
319 NavFocus { .. } | LostNavFocus => false,
320 SelFocus(_) | LostSelFocus => false,
321 KeyFocus | LostKeyFocus => false,
322 ImeFocus | LostImeFocus => false,
323 MouseOver(_) => false,
324 }
325 }
326}
327
328/// Command input ([`Event::Command`])
329///
330/// `Command` events are mostly produced as a result of OS-specific keyboard
331/// bindings; for example, [`Command::Copy`] is produced by pressing
332/// <kbd>Command+C</kbd> on MacOS or <kbd>Ctrl+C</kbd> on other platforms.
333/// See [`crate::config::Shortcuts`] for more on these bindings.
334///
335/// A `Command` event does not necessarily come from keyboard input; for example
336/// some menu widgets send [`Command::Activate`] to trigger an entry as a result
337/// of mouse input.
338///
339/// *Most* `Command` entries represent an action (such as `Copy` or `FindNext`)
340/// but some represent an important key whose action may be context-dependent
341/// (e.g. `Escape`, `Space`).
342#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
343#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
344#[non_exhaustive]
345pub enum Command {
346 /// Escape key
347 ///
348 /// Each press of this key should somehow relax control. It is expected that
349 /// widgets receiving this key repeatedly eventually (soon) have no more
350 /// use for this themselves and return it via [`Unused`].
351 ///
352 /// This is in some cases remapped to [`Command::Deselect`].
353 Escape,
354 /// Programmatic activation
355 ///
356 /// A synthetic event to activate widgets. Consider matching
357 /// [`Command::is_activate`] or using using [`Event::on_click`]
358 /// instead for generally applicable activation.
359 Activate,
360 /// Return / enter key
361 ///
362 /// This may insert a line-break or may activate something.
363 Enter,
364 /// Space bar key
365 Space,
366 /// Tab key
367 ///
368 /// This key is used to insert (horizontal) tabulators as well as to
369 /// navigate focus (in reverse when combined with Shift).
370 ///
371 /// This is usually not sent to widgets but instead used for navigation.
372 Tab,
373
374 /// Move view up without affecting selection
375 ViewUp,
376 /// Move view down without affecting selection
377 ViewDown,
378
379 /// Move left
380 Left,
381 /// Move right
382 Right,
383 /// Move up
384 Up,
385 /// Move down
386 Down,
387 /// Move left one word
388 WordLeft,
389 /// Move right one word
390 WordRight,
391 /// Move to start (of the line)
392 Home,
393 /// Move to end (of the line)
394 End,
395 /// Move to start of the document
396 DocHome,
397 /// Move to end of the document
398 DocEnd,
399 /// Move up a page
400 PageUp,
401 /// Move down a page
402 PageDown,
403
404 /// Capture a screenshot
405 Snapshot,
406 /// Lock output of screen
407 ScrollLock,
408 /// Pause key
409 Pause,
410 /// Insert key
411 Insert,
412
413 /// Delete forwards
414 Delete,
415 /// Delete backwards (Backspace key)
416 DelBack,
417 /// Delete forwards one word
418 DelWord,
419 /// Delete backwards one word
420 DelWordBack,
421
422 /// Clear any selections
423 Deselect,
424 /// Select all contents
425 SelectAll,
426
427 /// Find (start)
428 Find,
429 /// Find and replace (start)
430 FindReplace,
431 /// Find next
432 FindNext,
433 /// Find previous
434 FindPrevious,
435
436 /// Make text bold
437 Bold,
438 /// Make text italic
439 Italic,
440 /// Underline text
441 Underline,
442 /// Insert a link
443 Link,
444
445 /// Copy to clipboard and clear
446 Cut,
447 /// Copy to clipboard
448 Copy,
449 /// Copy from clipboard
450 Paste,
451 /// Undo the last action
452 Undo,
453 /// Redo the last undone action
454 Redo,
455
456 /// New document
457 New,
458 /// Open document
459 Open,
460 /// Save document
461 Save,
462 /// Print document
463 Print,
464
465 /// Navigate forwards one page/item
466 NavNext,
467 /// Navigate backwards one page/item
468 NavPrevious,
469 /// Navigate to the parent item
470 ///
471 /// May be used to browse "up" to a parent directory.
472 NavParent,
473 /// Navigate "down"
474 ///
475 /// This is an opposite to `NavParent`, and will mostly not be used.
476 NavDown,
477
478 /// Open a new tab
479 TabNew,
480 /// Navigate to next tab
481 TabNext,
482 /// Navigate to previous tab
483 TabPrevious,
484
485 /// Show help
486 Help,
487 /// Rename
488 Rename,
489 /// Refresh
490 Refresh,
491 /// Debug
492 Debug,
493 /// Spell-check tool
494 SpellCheck,
495 /// Open the context menu
496 ContextMenu,
497 /// Open or activate the application menu / menubar
498 Menu,
499 /// Make view fullscreen
500 Fullscreen,
501
502 /// Close window/tab/popup
503 Close,
504 /// Exit program (e.g. Ctrl+Q)
505 Exit,
506}
507
508impl Command {
509 /// Try constructing from a [`winit::keyboard::Key`]
510 pub fn new(key: &Key) -> Option<Self> {
511 match key {
512 Key::Named(named) => Some(match named {
513 NamedKey::ScrollLock => Command::ScrollLock,
514 NamedKey::Enter => Command::Enter,
515 NamedKey::Tab => Command::Tab,
516 NamedKey::Space => Command::Space,
517 NamedKey::ArrowDown => Command::Down,
518 NamedKey::ArrowLeft => Command::Left,
519 NamedKey::ArrowRight => Command::Right,
520 NamedKey::ArrowUp => Command::Up,
521 NamedKey::End => Command::End,
522 NamedKey::Home => Command::Home,
523 NamedKey::PageDown => Command::PageDown,
524 NamedKey::PageUp => Command::PageUp,
525 NamedKey::Backspace => Command::DelBack,
526 NamedKey::Clear => Command::Deselect,
527 NamedKey::Copy => Command::Copy,
528 NamedKey::Cut => Command::Cut,
529 NamedKey::Delete => Command::Delete,
530 NamedKey::Insert => Command::Insert,
531 NamedKey::Paste => Command::Paste,
532 NamedKey::Redo | NamedKey::Again => Command::Redo,
533 NamedKey::Undo => Command::Undo,
534 NamedKey::ContextMenu => Command::ContextMenu,
535 NamedKey::Escape => Command::Escape,
536 NamedKey::Execute => Command::Activate,
537 NamedKey::Find => Command::Find,
538 NamedKey::Help => Command::Help,
539 NamedKey::Pause => Command::Pause,
540 NamedKey::Select => Command::SelectAll,
541 NamedKey::PrintScreen => Command::Snapshot,
542 // NamedKey::Close => CloseDocument ?
543 NamedKey::New => Command::New,
544 NamedKey::Open => Command::Open,
545 NamedKey::Print => Command::Print,
546 NamedKey::Save => Command::Save,
547 NamedKey::SpellCheck => Command::SpellCheck,
548 NamedKey::BrowserBack | NamedKey::GoBack => Command::NavPrevious,
549 NamedKey::BrowserForward => Command::NavNext,
550 NamedKey::BrowserRefresh => Command::Refresh,
551 NamedKey::Exit => Command::Exit,
552 _ => return None,
553 }),
554 _ => None,
555 }
556 }
557
558 /// True for "activation" commands
559 ///
560 /// This matches:
561 ///
562 /// - [`Self::Activate`] — programmatic activation
563 /// - [`Self::Enter`] — <kbd>Enter</kbd> and <kbd>Return</kbd> keys
564 /// - [`Self::Space`] — <kbd>Space</kbd> key
565 pub fn is_activate(self) -> bool {
566 use Command::*;
567 matches!(self, Activate | Enter | Space)
568 }
569
570 /// Convert to selection-focus command
571 ///
572 /// Certain limited commands may be sent to widgets with selection focus but
573 /// not character or navigation focus.
574 pub fn suitable_for_sel_focus(self) -> bool {
575 use Command::*;
576 matches!(self, Escape | Cut | Copy | Deselect)
577 }
578
579 /// Convert arrow keys to a direction
580 pub fn as_direction(self) -> Option<Direction> {
581 match self {
582 Command::Left => Some(Direction::Left),
583 Command::Right => Some(Direction::Right),
584 Command::Up => Some(Direction::Up),
585 Command::Down => Some(Direction::Down),
586 _ => None,
587 }
588 }
589}
590
591/// Reason that navigation focus is received
592#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
593pub enum FocusSource {
594 /// Focus is received as a result of a mouse or touch event
595 Pointer,
596 /// Focus is received as a result of keyboard navigation (usually
597 /// <kbd>Tab</kbd>) or a command ([`Event::Command`])
598 Key,
599 /// Focus is received from a programmatic event
600 Synthetic,
601}
602
603impl FocusSource {
604 pub fn key_or_synthetic(self) -> bool {
605 match self {
606 FocusSource::Pointer => false,
607 FocusSource::Key | FocusSource::Synthetic => true,
608 }
609 }
610}
611
612/// Type used by [`Event::Scroll`]
613#[derive(Clone, Copy, Debug, PartialEq)]
614pub enum ScrollDelta {
615 /// Scroll a given number of lines
616 ///
617 /// Positive values indicate that the content that is being scrolled should
618 /// move right and down (revealing more content left and up).
619 /// Typically values are integral but this is not guaranteed.
620 Lines(f32, f32),
621 /// Scroll a given number of pixels
622 ///
623 /// For a ‘natural scrolling’ touch pad (that acts like a touch screen) this
624 /// means moving your fingers right and down should give positive values,
625 /// and move the content right and down (to reveal more things left and up).
626 PixelDelta(Vec2),
627}
628
629impl ScrollDelta {
630 /// True if the x-axis delta is zero
631 pub fn is_vertical(self) -> bool {
632 match self {
633 ScrollDelta::Lines(0.0, _) => true,
634 ScrollDelta::PixelDelta(Vec2(0.0, _)) => true,
635 _ => false,
636 }
637 }
638
639 /// True if the y-axis delta is zero
640 pub fn is_horizontal(self) -> bool {
641 match self {
642 ScrollDelta::Lines(_, 0.0) => true,
643 ScrollDelta::PixelDelta(Vec2(_, 0.0)) => true,
644 _ => false,
645 }
646 }
647
648 /// Convert to a pan offset
649 ///
650 /// Line deltas are converted to a distance based on `scroll_distance` configuration.
651 pub fn as_offset(self, cx: &EventState) -> Vec2 {
652 match self {
653 ScrollDelta::Lines(x, y) => cx.config().event().scroll_distance((x, y)),
654 ScrollDelta::PixelDelta(d) => d,
655 }
656 }
657
658 /// Convert to a zoom factor
659 pub fn as_factor(self, _: &EventState) -> f32 {
660 // TODO: this should be configurable?
661 match self {
662 ScrollDelta::Lines(_, y) => -0.5 * y,
663 ScrollDelta::PixelDelta(Vec2(_, y)) => -0.01 * y,
664 }
665 }
666
667 /// Convert to a pan offset or zoom factor
668 ///
669 /// This is used for surfaces where panning/scrolling is preferred over
670 /// zooming, though both are supported (for example, a web page).
671 /// The <kbd>Ctrl</kbd> key is used to select between the two modes.
672 pub fn as_offset_or_factor(self, cx: &EventState) -> Result<Vec2, f32> {
673 if cx.modifiers().control_key() {
674 Err(self.as_factor(cx))
675 } else {
676 Ok(self.as_offset(cx))
677 }
678 }
679
680 /// Convert to a zoom factor or pan offset
681 ///
682 /// This is used for surfaces where zooming is preferred over panning,
683 /// though both are supported (for example, a map view where click-and-drag
684 /// may also be used to pan). Mouse wheel actions always zoom while the
685 /// touchpad scrolling may cause either effect.
686 pub fn as_factor_or_offset(self, cx: &EventState) -> Result<f32, Vec2> {
687 if matches!(self, ScrollDelta::Lines(_, _)) || cx.modifiers().control_key() {
688 Ok(self.as_factor(cx))
689 } else {
690 Err(self.as_offset(cx))
691 }
692 }
693
694 /// Attempt to interpret as a mouse wheel action
695 ///
696 /// Infers the "scroll delta" as an integral step, if appropriate.
697 /// This may be used e.g. to change the selected value of a `ComboBox`.
698 ///
699 /// Positive values indicate scrolling up.
700 pub fn as_wheel_action(self, cx: &EventState) -> Option<i32> {
701 match self {
702 ScrollDelta::Lines(_, y) if cx.config().event().mouse_wheel_actions() => {
703 y.try_cast_approx().ok()
704 }
705 _ => None,
706 }
707 }
708}
709
710#[cfg(test)]
711#[test]
712fn sizes() {
713 use core::mem::size_of;
714 assert_eq!(size_of::<Command>(), 1);
715 assert_eq!(size_of::<PhysicalKey>(), 8);
716 assert_eq!(size_of::<KeyEvent>(), 128);
717 assert_eq!(size_of::<ScrollDelta>(), 12);
718 assert_eq!(size_of::<Affine>(), 32);
719 assert_eq!(size_of::<Press>(), 24);
720 assert_eq!(size_of::<TimerHandle>(), 8);
721 assert_eq!(size_of::<WindowId>(), 4);
722 assert_eq!(size_of::<FocusSource>(), 1);
723 assert_eq!(size_of::<Event>(), 40);
724}