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