gpui/interactive.rs
1use crate::{
2 Bounds, Capslock, Context, Empty, IntoElement, Keystroke, LongPressEvent, Modifiers, Pixels,
3 Point, Render, TouchDragEvent, Window, point, seal::Sealed,
4};
5use smallvec::SmallVec;
6use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
7
8/// A platform input event that GPUI can dispatch to a window.
9///
10/// Implemented by the event structs in this module. Each one converts into the
11/// matching [`PlatformInput`] variant. The trait is sealed, so only GPUI can
12/// implement it.
13pub trait InputEvent: Sealed + 'static {
14 /// Converts this event into the [`PlatformInput`] variant that wraps it.
15 fn to_platform_input(self) -> PlatformInput;
16}
17
18/// An input event from the keyboard: [`KeyDownEvent`], [`KeyUpEvent`] or
19/// [`ModifiersChangedEvent`].
20pub trait KeyEvent: InputEvent {}
21
22/// An input event from a pointer or touch: mouse buttons, movement, scrolling,
23/// pinch, pressure, file drops and the gesture events.
24pub trait MouseEvent: InputEvent {}
25
26/// A gesture event recognized from touch input, such as [`PinchEvent`],
27/// [`LongPressEvent`] or [`TouchDragEvent`].
28pub trait GestureEvent: InputEvent {}
29
30/// A key press, sent when the platform reports that a key went down.
31///
32/// GPUI first matches the keystroke against your key bindings and dispatches
33/// matching actions. Unless an action stops propagation, the event then reaches
34/// [`InteractiveElement::on_key_down`](crate::InteractiveElement::on_key_down)
35/// listeners on the focus path. The [keyboard guide](crate::_documentation::guides::keyboard)
36/// explains the order.
37#[derive(Clone, Debug, Eq, PartialEq)]
38pub struct KeyDownEvent {
39 /// The key and modifiers that were pressed.
40 pub keystroke: Keystroke,
41
42 /// Whether this is an automatic repeat from a key that is still held down.
43 pub is_held: bool,
44
45 /// Whether the focused text input should receive this keystroke as text
46 /// instead of matching key bindings.
47 ///
48 /// Set on a keystroke that GPUI replays after a multi-stroke binding fails to
49 /// complete. Bindings are skipped only if the focused input handler accepts
50 /// text input.
51 pub prefer_character_input: bool,
52}
53
54impl Sealed for KeyDownEvent {}
55impl InputEvent for KeyDownEvent {
56 fn to_platform_input(self) -> PlatformInput {
57 PlatformInput::KeyDown(self)
58 }
59}
60impl KeyEvent for KeyDownEvent {}
61
62/// A key release, sent when the platform reports that a key went up.
63///
64/// Dispatched to [`InteractiveElement::on_key_up`](crate::InteractiveElement::on_key_up) listeners on the focus path.
65#[derive(Clone, Debug)]
66pub struct KeyUpEvent {
67 /// The key and modifiers that were released.
68 pub keystroke: Keystroke,
69}
70
71impl Sealed for KeyUpEvent {}
72impl InputEvent for KeyUpEvent {
73 fn to_platform_input(self) -> PlatformInput {
74 PlatformInput::KeyUp(self)
75 }
76}
77impl KeyEvent for KeyUpEvent {}
78
79/// A change to the modifier keys (Shift, Control, Alt, Platform, Function) or
80/// Caps Lock, sent without a key press.
81///
82/// Dereferences to [`Modifiers`], so `event.shift` reads the new Shift state.
83/// Modifier-only bindings, such as one bound to `"shift"`, are matched from
84/// this event.
85#[derive(Clone, Debug, Default)]
86pub struct ModifiersChangedEvent {
87 /// The modifier keys held after the change.
88 pub modifiers: Modifiers,
89 /// The Caps Lock state after the change.
90 pub capslock: Capslock,
91}
92
93impl Sealed for ModifiersChangedEvent {}
94impl InputEvent for ModifiersChangedEvent {
95 fn to_platform_input(self) -> PlatformInput {
96 PlatformInput::ModifiersChanged(self)
97 }
98}
99impl KeyEvent for ModifiersChangedEvent {}
100
101impl Deref for ModifiersChangedEvent {
102 type Target = Modifiers;
103
104 fn deref(&self) -> &Self::Target {
105 &self.modifiers
106 }
107}
108
109/// The lifecycle stage of a touch, scroll or pinch event.
110///
111/// Based on the winit enum of the same name. A gesture reports `Started` when it
112/// begins, zero or more `Moved` events, and then `Ended` or `Cancelled`.
113#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
114pub enum TouchPhase {
115 /// The touch, scroll or pinch began.
116 Started,
117 /// The touch, scroll or pinch changed position or scale.
118 #[default]
119 Moved,
120 /// The touch, scroll or pinch finished normally.
121 Ended,
122 /// The system ended the gesture abnormally. Consumers must fully unwind any
123 /// in-progress interaction, treating the touch as if it never committed.
124 Cancelled,
125}
126
127/// Identifies one touch (finger or stylus contact) from its
128/// [`TouchPhase::Started`] event through its [`TouchPhase::Ended`] or
129/// [`TouchPhase::Cancelled`] event.
130///
131/// The value is assigned by the platform and is opaque. A platform window must
132/// not reuse an identifier for a later touch.
133#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
134pub struct TouchId(pub u64);
135
136/// A raw touch sample from a touch screen, before gesture recognition.
137///
138/// GPUI does not deliver raw touches to element listeners. The gesture
139/// recognizer reads them and reports what they mean: a tap becomes a
140/// synthesized left mouse down and up, a pan becomes [`ScrollWheelEvent`]s, a
141/// held touch becomes [`LongPressEvent`], and a drag claimed by an element
142/// becomes [`TouchDragEvent`]. See the
143/// [mouse and touch guide](crate::_documentation::guides::mouse_and_touch).
144#[derive(Clone, Debug, Default)]
145pub struct TouchEvent {
146 /// The touch this sample belongs to.
147 pub id: TouchId,
148 /// Whether the touch started, moved, ended or was cancelled.
149 pub phase: TouchPhase,
150 /// The touch position in window coordinates, in logical pixels.
151 pub position: Point<Pixels>,
152 /// The position the platform predicts the touch will reach about one frame
153 /// later, in the same coordinates as `position`. Only set for
154 /// [`TouchPhase::Moved`] samples, and only when the platform can predict.
155 ///
156 /// This is latency compensation. It can change how far a pan scrolls within
157 /// a frame. It never affects hit testing, gesture classification or
158 /// velocity, and later samples correct any error it introduces.
159 pub predicted_position: Option<Point<Pixels>>,
160 /// The normalized touch force from `0.0` to `1.0`, if the hardware reports it.
161 pub force: Option<f32>,
162}
163
164impl Sealed for TouchEvent {}
165impl InputEvent for TouchEvent {
166 fn to_platform_input(self) -> PlatformInput {
167 PlatformInput::Touch(self)
168 }
169}
170
171/// A mouse button press, sent to [`InteractiveElement::on_mouse_down`](crate::InteractiveElement::on_mouse_down) listeners.
172///
173/// Use it to react as soon as the button goes down. Use [`MouseClickEvent`]
174/// instead when you need the completed click.
175#[derive(Clone, Debug, Default)]
176pub struct MouseDownEvent {
177 /// The button that was pressed.
178 pub button: MouseButton,
179
180 /// The pointer position in window coordinates, in logical pixels.
181 pub position: Point<Pixels>,
182
183 /// The modifier keys held when the button was pressed.
184 pub modifiers: Modifiers,
185
186 /// The number of presses in a row at this spot, starting at 1. A double
187 /// click has a count of 2.
188 pub click_count: usize,
189
190 /// Whether this press activated an inactive window. Set on macOS and Wayland
191 /// when the window accepts the press that brings it forward.
192 pub first_mouse: bool,
193}
194
195impl Sealed for MouseDownEvent {}
196impl InputEvent for MouseDownEvent {
197 fn to_platform_input(self) -> PlatformInput {
198 PlatformInput::MouseDown(self)
199 }
200}
201impl MouseEvent for MouseDownEvent {}
202
203impl MouseDownEvent {
204 /// Returns `true` if this press should focus the element.
205 ///
206 /// Only left button presses focus. Right and middle presses leave focus where
207 /// it is.
208 pub fn is_focusing(&self) -> bool {
209 match self.button {
210 MouseButton::Left => true,
211 _ => false,
212 }
213 }
214}
215
216/// A mouse button release, sent to [`InteractiveElement::on_mouse_up`](crate::InteractiveElement::on_mouse_up) listeners.
217///
218/// A release alone is not a click. Use [`MouseClickEvent`] or
219/// [`StatefulInteractiveElement::on_click`](crate::StatefulInteractiveElement::on_click) for a completed click.
220#[derive(Clone, Debug, Default)]
221pub struct MouseUpEvent {
222 /// The button that was released.
223 pub button: MouseButton,
224
225 /// The pointer position in window coordinates, in logical pixels.
226 pub position: Point<Pixels>,
227
228 /// The modifier keys held when the button was released.
229 pub modifiers: Modifiers,
230
231 /// The number of presses in a row, as reported by the platform with this release.
232 pub click_count: usize,
233}
234
235impl Sealed for MouseUpEvent {}
236impl InputEvent for MouseUpEvent {
237 fn to_platform_input(self) -> PlatformInput {
238 PlatformInput::MouseUp(self)
239 }
240}
241
242impl MouseEvent for MouseUpEvent {}
243
244impl MouseUpEvent {
245 /// Returns `true` if this release should focus the element.
246 ///
247 /// Only left button releases focus.
248 pub fn is_focusing(&self) -> bool {
249 match self.button {
250 MouseButton::Left => true,
251 _ => false,
252 }
253 }
254}
255
256/// A completed mouse click: the press and the release of one button on one
257/// element.
258///
259/// GPUI sends it to [`StatefulInteractiveElement::on_click`](crate::StatefulInteractiveElement::on_click) for the left button and to
260/// [`StatefulInteractiveElement::on_aux_click`](crate::StatefulInteractiveElement::on_aux_click) for every other button. It is wrapped in
261/// [`ClickEvent::Mouse`].
262#[derive(Clone, Debug, Default)]
263pub struct MouseClickEvent {
264 /// The press that started the click.
265 pub down: MouseDownEvent,
266
267 /// The release that completed the click.
268 pub up: MouseUpEvent,
269}
270
271/// How hard a force-sensitive trackpad is pressed.
272#[derive(Clone, Copy, Debug, Default, PartialEq)]
273pub enum PressureStage {
274 /// No pressure.
275 #[default]
276 Zero,
277 /// Normal click pressure.
278 Normal,
279 /// Firm pressure, enough to trigger a force click.
280 Force,
281}
282
283/// A pressure update from a force-sensitive trackpad, sent as the user presses
284/// harder or eases off. Only macOS trackpads report it today.
285#[derive(Debug, Clone, Default)]
286pub struct MousePressureEvent {
287 /// The pressure within the current stage, from `0.0` to `1.0`.
288 pub pressure: f32,
289 /// The pressure stage the trackpad is in.
290 pub stage: PressureStage,
291 /// The pointer position in window coordinates, in logical pixels.
292 pub position: Point<Pixels>,
293 /// The modifier keys held when the pressure changed.
294 pub modifiers: Modifiers,
295}
296
297impl Sealed for MousePressureEvent {}
298impl InputEvent for MousePressureEvent {
299 fn to_platform_input(self) -> PlatformInput {
300 PlatformInput::MousePressure(self)
301 }
302}
303impl MouseEvent for MousePressureEvent {}
304
305/// A click triggered from the keyboard: Enter or Space on a focused element.
306///
307/// Keyboard clicks carry no pointer data. The element's bounds stand in for the
308/// position.
309#[derive(Clone, Debug, Default)]
310pub struct KeyboardClickEvent {
311 /// The key that triggered the click.
312 pub button: KeyboardButton,
313
314 /// The bounds of the clicked element in window coordinates, in logical pixels.
315 pub bounds: Bounds<Pixels>,
316}
317
318/// A tap, or a long press, recognized on a touch screen.
319///
320/// GPUI does not produce this event yet. Taps currently reach click listeners
321/// as [`ClickEvent::Mouse`] with a left button press and release.
322#[derive(Clone, Debug, Default)]
323pub struct TouchClickEvent {
324 /// The position of the tap in window coordinates, in logical pixels.
325 pub position: Point<Pixels>,
326 /// The number of consecutive taps at this spot. A double tap has a count of 2.
327 pub tap_count: usize,
328 /// Whether this was a long press rather than a tap. A long press is the touch
329 /// equivalent of a right click.
330 pub long_press: bool,
331}
332
333/// A completed click from the mouse, keyboard or touch.
334///
335/// Click listeners receive this type. [`StatefulInteractiveElement::on_click`](crate::StatefulInteractiveElement::on_click) handles
336/// the primary action, and [`StatefulInteractiveElement::on_aux_click`](crate::StatefulInteractiveElement::on_aux_click) handles the
337/// other mouse buttons. Use the methods on this type, such as
338/// [`ClickEvent::is_secondary`], to tell the cases apart.
339///
340/// # Examples
341///
342/// ```
343/// use gpui::{ClickEvent, KeyboardButton, KeyboardClickEvent, Modifiers, MouseButton,
344/// MouseClickEvent, MouseDownEvent, MouseUpEvent};
345///
346/// let right_click = ClickEvent::Mouse(MouseClickEvent {
347/// down: MouseDownEvent { button: MouseButton::Right, ..Default::default() },
348/// up: MouseUpEvent { button: MouseButton::Right, ..Default::default() },
349/// });
350/// assert!(right_click.is_right_click());
351/// assert!(right_click.is_secondary());
352/// assert!(!right_click.standard_click());
353///
354/// let enter = ClickEvent::Keyboard(KeyboardClickEvent {
355/// button: KeyboardButton::Enter,
356/// ..Default::default()
357/// });
358/// assert!(enter.is_keyboard());
359/// assert_eq!(enter.click_count(), 1);
360/// assert_eq!(enter.modifiers(), Modifiers::default());
361/// ```
362#[derive(Clone, Debug)]
363pub enum ClickEvent {
364 /// A click from a mouse button, a press and a release on the same element.
365 Mouse(MouseClickEvent),
366 /// A click from Enter or Space on a focused element.
367 Keyboard(KeyboardClickEvent),
368 /// A touch tap or long press. GPUI reserves this variant for direct touch
369 /// click delivery; the recognizer currently reports those gestures through
370 /// mouse events instead.
371 Touch(TouchClickEvent),
372}
373
374impl Default for ClickEvent {
375 fn default() -> Self {
376 ClickEvent::Keyboard(KeyboardClickEvent::default())
377 }
378}
379
380impl ClickEvent {
381 /// Returns the modifier keys held during the click.
382 ///
383 /// Keyboard and touch clicks have none, so this returns the default
384 /// [`Modifiers`]. A mouse click returns the modifiers held when the button was
385 /// released.
386 pub fn modifiers(&self) -> Modifiers {
387 match self {
388 // Click events are only generated from keyboard events _without any modifiers_, so we know the modifiers are always Default
389 ClickEvent::Keyboard(_) => Modifiers::default(),
390 // Click events on the web only reflect the modifiers for the keyup event,
391 // tested via observing the behavior of the `ClickEvent.shiftKey` field in Chrome 138
392 // under various combinations of modifiers and keyUp / keyDown events.
393 ClickEvent::Mouse(event) => event.up.modifiers,
394 // Touch screens have no modifier keys.
395 ClickEvent::Touch(_) => Modifiers::default(),
396 }
397 }
398
399 /// Returns the window position of the click, in logical pixels.
400 ///
401 /// For a mouse click this is where the button was released. For a touch it
402 /// is where the tap happened. For a keyboard click, which has no pointer,
403 /// it is the bottom-left corner of the clicked element.
404 pub fn position(&self) -> Point<Pixels> {
405 match self {
406 ClickEvent::Keyboard(event) => event.bounds.bottom_left(),
407 ClickEvent::Mouse(event) => event.up.position,
408 ClickEvent::Touch(event) => event.position,
409 }
410 }
411
412 /// Returns the mouse position of the click, or `None` if it did not come from
413 /// a mouse.
414 ///
415 /// Keyboard and touch clicks return `None`. Use [`ClickEvent::position`] when
416 /// you need a position for every kind.
417 pub fn mouse_position(&self) -> Option<Point<Pixels>> {
418 match self {
419 ClickEvent::Keyboard(_) => None,
420 ClickEvent::Mouse(event) => Some(event.up.position),
421 ClickEvent::Touch(_) => None,
422 }
423 }
424
425 /// Returns `true` if this is a mouse click made with the right button.
426 ///
427 /// Keyboard and touch clicks always return `false`. Use
428 /// [`ClickEvent::is_secondary`] to also match long presses.
429 pub fn is_right_click(&self) -> bool {
430 match self {
431 ClickEvent::Keyboard(_) => false,
432 ClickEvent::Mouse(event) => {
433 event.down.button == MouseButton::Right && event.up.button == MouseButton::Right
434 }
435 ClickEvent::Touch(_) => false,
436 }
437 }
438
439 /// Returns `true` if this is a mouse click made with the middle button.
440 ///
441 /// Keyboard and touch clicks always return `false`.
442 pub fn is_middle_click(&self) -> bool {
443 match self {
444 ClickEvent::Keyboard(_) => false,
445 ClickEvent::Mouse(event) => {
446 event.down.button == MouseButton::Middle && event.up.button == MouseButton::Middle
447 }
448 ClickEvent::Touch(_) => false,
449 }
450 }
451
452 /// Returns `true` if this click is a secondary activation, the kind that
453 /// usually opens a context menu.
454 ///
455 /// That is a right-button mouse click, or a long press when a future direct
456 /// touch click is delivered. On macOS, Control-clicks already arrive as right
457 /// clicks. Keyboard clicks always return `false`.
458 pub fn is_secondary(&self) -> bool {
459 match self {
460 ClickEvent::Keyboard(_) => false,
461 ClickEvent::Mouse(event) => {
462 event.down.button == MouseButton::Right && event.up.button == MouseButton::Right
463 }
464 ClickEvent::Touch(event) => event.long_press,
465 }
466 }
467
468 /// Returns `true` for a primary activation: a left-button click, a keyboard
469 /// click, or a direct touch tap that is not a long press.
470 ///
471 /// Right, middle and navigation button clicks return `false`. Use
472 /// [`ClickEvent::is_secondary`] to test for the right button and long presses.
473 pub fn standard_click(&self) -> bool {
474 match self {
475 ClickEvent::Keyboard(_) => true,
476 ClickEvent::Mouse(event) => {
477 event.down.button == MouseButton::Left && event.up.button == MouseButton::Left
478 }
479 ClickEvent::Touch(event) => !event.long_press,
480 }
481 }
482
483 /// Returns `true` if this click activated the window, as reported by the
484 /// platform in [`MouseDownEvent::first_mouse`].
485 ///
486 /// Keyboard and touch clicks always return `false`. A keyboard click needs
487 /// the element to be focused already.
488 pub fn first_focus(&self) -> bool {
489 match self {
490 ClickEvent::Keyboard(_) => false,
491 ClickEvent::Mouse(event) => event.down.first_mouse,
492 ClickEvent::Touch(_) => false,
493 }
494 }
495
496 /// Returns how many clicks in a row this click completes. A double click is 2.
497 ///
498 /// A keyboard click always returns 1. A mouse click returns the count from its
499 /// release. A touch click returns its tap count.
500 pub fn click_count(&self) -> usize {
501 match self {
502 ClickEvent::Keyboard(_) => 1,
503 ClickEvent::Mouse(event) => event.up.click_count,
504 ClickEvent::Touch(event) => event.tap_count,
505 }
506 }
507
508 /// Returns `true` if this click came from the keyboard.
509 pub fn is_keyboard(&self) -> bool {
510 match self {
511 ClickEvent::Mouse(_) | ClickEvent::Touch(_) => false,
512 ClickEvent::Keyboard(_) => true,
513 }
514 }
515}
516
517/// The key that triggered a keyboard click: Enter or Space.
518#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug, Default)]
519pub enum KeyboardButton {
520 /// The Enter key.
521 #[default]
522 Enter,
523 /// The Space bar.
524 Space,
525}
526
527/// A mouse button.
528///
529/// Compare buttons with `==` to choose a handler, as in
530/// `event.button == MouseButton::Right`. The default is [`MouseButton::Left`].
531#[derive(Hash, Default, PartialEq, Eq, Copy, Clone, Debug)]
532pub enum MouseButton {
533 /// The primary button, usually on the left.
534 #[default]
535 Left,
536
537 /// The secondary button, usually on the right.
538 Right,
539
540 /// The middle button, usually the scroll wheel.
541 Middle,
542
543 /// A navigation button, such as back or forward on a side button.
544 Navigate(NavigationDirection),
545}
546
547impl MouseButton {
548 /// Returns every mouse button GPUI knows, in a fixed order.
549 ///
550 /// The order is `Left`, `Right`, `Middle`, `Navigate(Back)` and
551 /// `Navigate(Forward)`. Use it to iterate over all buttons, for example to
552 /// build a table of handlers.
553 ///
554 /// # Examples
555 ///
556 /// ```
557 /// use gpui::{MouseButton, NavigationDirection};
558 ///
559 /// assert_eq!(MouseButton::all().len(), 5);
560 /// assert_eq!(MouseButton::all()[3], MouseButton::Navigate(NavigationDirection::Back));
561 /// ```
562 pub fn all() -> Vec<Self> {
563 vec![
564 MouseButton::Left,
565 MouseButton::Right,
566 MouseButton::Middle,
567 MouseButton::Navigate(NavigationDirection::Back),
568 MouseButton::Navigate(NavigationDirection::Forward),
569 ]
570 }
571}
572
573/// The direction of a navigation button, the side buttons on some mice.
574#[derive(Hash, Default, PartialEq, Eq, Copy, Clone, Debug)]
575pub enum NavigationDirection {
576 /// The back button.
577 #[default]
578 Back,
579
580 /// The forward button.
581 Forward,
582}
583
584/// The pointer moved over the window, with or without a button held.
585///
586/// Use [`MouseMoveEvent::dragging`] to tell a left-button drag from a hover.
587#[derive(Clone, Debug, Default)]
588pub struct MouseMoveEvent {
589 /// The pointer position in window coordinates, in logical pixels.
590 pub position: Point<Pixels>,
591
592 /// The button held down during the move, or `None` if no button is held.
593 pub pressed_button: Option<MouseButton>,
594
595 /// The modifier keys held during the move.
596 pub modifiers: Modifiers,
597}
598
599impl Sealed for MouseMoveEvent {}
600impl InputEvent for MouseMoveEvent {
601 fn to_platform_input(self) -> PlatformInput {
602 PlatformInput::MouseMove(self)
603 }
604}
605impl MouseEvent for MouseMoveEvent {}
606
607impl MouseMoveEvent {
608 /// Returns `true` if the left button is held down during this move.
609 ///
610 /// Other buttons, such as the right button, return `false`.
611 pub fn dragging(&self) -> bool {
612 self.pressed_button == Some(MouseButton::Left)
613 }
614}
615
616/// A scroll from a mouse wheel, a trackpad, or a touch pan.
617///
618/// Sent to [`InteractiveElement::on_scroll_wheel`](crate::InteractiveElement::on_scroll_wheel) listeners at the pointer
619/// position. Scroll containers use the `delta` to move their content.
620///
621/// Dereferences to [`Modifiers`], so `event.shift` reads the Shift state.
622#[derive(Clone, Debug, Default)]
623pub struct ScrollWheelEvent {
624 /// The pointer position in window coordinates, in logical pixels. For a
625 /// touch pan, the position where the pan started.
626 pub position: Point<Pixels>,
627
628 /// How far the content should scroll for this event.
629 pub delta: ScrollDelta,
630
631 /// The modifier keys held during the scroll.
632 pub modifiers: Modifiers,
633
634 /// Where this event falls in its scroll gesture. Trackpad and touch scrolls
635 /// report `Started`, `Moved` and `Ended`. Wheels that don't report phases
636 /// leave it at `Moved`.
637 pub touch_phase: TouchPhase,
638}
639
640impl Sealed for ScrollWheelEvent {}
641impl InputEvent for ScrollWheelEvent {
642 fn to_platform_input(self) -> PlatformInput {
643 PlatformInput::ScrollWheel(self)
644 }
645}
646impl MouseEvent for ScrollWheelEvent {}
647
648impl Deref for ScrollWheelEvent {
649 type Target = Modifiers;
650
651 fn deref(&self) -> &Self::Target {
652 &self.modifiers
653 }
654}
655
656/// The amount a scroll event moves the content.
657///
658/// Trackpads and touch pans report [`ScrollDelta::Pixels`]. Line-based mouse
659/// wheels report [`ScrollDelta::Lines`]. Use [`ScrollDelta::pixel_delta`] to get
660/// pixels for either.
661#[derive(Clone, Copy, Debug)]
662pub enum ScrollDelta {
663 /// An exact amount in logical pixels.
664 Pixels(Point<Pixels>),
665 /// An approximate amount in lines of text. Multiply by the line height to get
666 /// pixels.
667 Lines(Point<f32>),
668}
669
670impl Default for ScrollDelta {
671 fn default() -> Self {
672 Self::Lines(Default::default())
673 }
674}
675
676/// A pinch-to-zoom gesture, typically from a trackpad.
677///
678/// Sent to [`InteractiveElement::on_pinch`](crate::InteractiveElement::on_pinch) listeners. Pinch recognition from
679/// touch input is not implemented yet, so pinch events come from the platform.
680///
681/// Dereferences to [`Modifiers`].
682#[derive(Clone, Debug, Default)]
683pub struct PinchEvent {
684 /// The center of the pinch in window coordinates, in logical pixels.
685 pub position: Point<Pixels>,
686
687 /// The change in scale since the previous event. `0.1` is a 10% zoom in and
688 /// `-0.1` is a 10% zoom out.
689 pub delta: f32,
690
691 /// The modifier keys held during the pinch.
692 pub modifiers: Modifiers,
693
694 /// Where this event falls in the pinch gesture.
695 pub phase: TouchPhase,
696}
697
698impl Sealed for PinchEvent {}
699impl InputEvent for PinchEvent {
700 fn to_platform_input(self) -> PlatformInput {
701 PlatformInput::Pinch(self)
702 }
703}
704impl GestureEvent for PinchEvent {}
705impl MouseEvent for PinchEvent {}
706
707impl Deref for PinchEvent {
708 type Target = Modifiers;
709
710 fn deref(&self) -> &Self::Target {
711 &self.modifiers
712 }
713}
714
715impl ScrollDelta {
716 /// Returns `true` if this delta is exact, in pixels, rather than in lines.
717 pub fn precise(&self) -> bool {
718 match self {
719 ScrollDelta::Pixels(_) => true,
720 ScrollDelta::Lines(_) => false,
721 }
722 }
723
724 /// Returns this delta in logical pixels.
725 ///
726 /// A pixel delta is returned unchanged. A line delta is multiplied by
727 /// `line_height` on each axis, so pass the line height of the text you are
728 /// scrolling.
729 ///
730 /// # Examples
731 ///
732 /// ```
733 /// use gpui::{ScrollDelta, point, px};
734 ///
735 /// let lines = ScrollDelta::Lines(point(0., 2.));
736 /// assert_eq!(lines.pixel_delta(px(20.)), point(px(0.), px(40.)));
737 /// ```
738 pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
739 match self {
740 ScrollDelta::Pixels(delta) => *delta,
741 ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
742 }
743 }
744
745 /// Merges this delta with a later one, as when two scroll events arrive in
746 /// the same frame.
747 ///
748 /// Each axis is handled separately. If both deltas move that axis the same
749 /// way, the amounts add. If they move it in opposite directions, `other`
750 /// wins on that axis. Two deltas of different kinds, one in pixels and one
751 /// in lines, return `other` unchanged.
752 ///
753 /// A component of zero counts as positive, so a zero in `other` replaces a
754 /// negative component of `self`, and a zero in `self` is replaced by a
755 /// negative component of `other`.
756 ///
757 /// # Examples
758 ///
759 /// ```
760 /// use gpui::{ScrollDelta, point, px};
761 ///
762 /// // Same direction on x adds (10 + 5); opposite direction on y takes the later 3.
763 /// let merged = ScrollDelta::Pixels(point(px(10.), px(-5.)))
764 /// .coalesce(ScrollDelta::Pixels(point(px(5.), px(3.))));
765 /// assert_eq!(merged.pixel_delta(px(16.)), point(px(15.), px(3.)));
766 /// ```
767 pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
768 match (self, other) {
769 (ScrollDelta::Pixels(a), ScrollDelta::Pixels(b)) => {
770 let x = if a.x.signum() == b.x.signum() {
771 a.x + b.x
772 } else {
773 b.x
774 };
775
776 let y = if a.y.signum() == b.y.signum() {
777 a.y + b.y
778 } else {
779 b.y
780 };
781
782 ScrollDelta::Pixels(point(x, y))
783 }
784
785 (ScrollDelta::Lines(a), ScrollDelta::Lines(b)) => {
786 let x = if a.x.signum() == b.x.signum() {
787 a.x + b.x
788 } else {
789 b.x
790 };
791
792 let y = if a.y.signum() == b.y.signum() {
793 a.y + b.y
794 } else {
795 b.y
796 };
797
798 ScrollDelta::Lines(point(x, y))
799 }
800
801 _ => other,
802 }
803 }
804}
805
806/// The pointer left the window.
807///
808/// Use it to end hover state. Dereferences to [`Modifiers`].
809#[derive(Clone, Debug, Default)]
810pub struct MouseExitEvent {
811 /// The last pointer position in window coordinates, in logical pixels.
812 pub position: Point<Pixels>,
813 /// The button held when the pointer left, or `None` if none was held.
814 pub pressed_button: Option<MouseButton>,
815 /// The modifier keys held when the pointer left.
816 pub modifiers: Modifiers,
817}
818
819impl Sealed for MouseExitEvent {}
820impl InputEvent for MouseExitEvent {
821 fn to_platform_input(self) -> PlatformInput {
822 PlatformInput::MouseExited(self)
823 }
824}
825
826impl MouseEvent for MouseExitEvent {}
827
828impl Deref for MouseExitEvent {
829 type Target = Modifiers;
830
831 fn deref(&self) -> &Self::Target {
832 &self.modifiers
833 }
834}
835
836/// The file paths carried by a file drop.
837///
838/// Wraps the paths the platform reports when files are dragged onto the window.
839#[derive(Debug, Clone, Default, Eq, PartialEq)]
840pub struct ExternalPaths(pub SmallVec<[PathBuf; 2]>);
841
842impl ExternalPaths {
843 /// Returns the dropped paths in the order the platform reported them.
844 pub fn paths(&self) -> &[PathBuf] {
845 &self.0
846 }
847}
848
849/// The content of a drag that leaves the window and becomes a native drag
850/// session handed to the operating system.
851#[derive(Debug, Clone, Eq, PartialEq)]
852pub enum ExternalDragPayload {
853 /// Files on disk, offered to the platform as an outbound file drag.
854 Files(FileDragPaths),
855}
856
857/// The files for a native file drag, each paired with whether it is a directory.
858///
859/// The caller supplies the directory flags up front, so the platform does not
860/// have to query the file system when the drag starts.
861#[derive(Debug, Clone, Default, Eq, PartialEq)]
862pub struct FileDragPaths(SmallVec<[(PathBuf, bool); 2]>);
863
864impl FileDragPaths {
865 /// Creates a drag payload from paths, each paired with whether it is a directory.
866 pub fn new(entries: impl IntoIterator<Item = (PathBuf, bool)>) -> Self {
867 Self(entries.into_iter().collect())
868 }
869
870 /// Returns the dragged paths, each paired with whether it is a directory.
871 pub fn entries(&self) -> &[(PathBuf, bool)] {
872 &self.0
873 }
874}
875
876impl Render for ExternalPaths {
877 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
878 // the platform will render icons for the dragged files
879 Empty
880 }
881}
882
883/// A stage of a file drag from the operating system into the window.
884///
885/// `Entered` starts a drag, `Pending` reports movement during it, `Submit` drops
886/// the files, and `Exited` ends a drag that did not drop. `Ended` reports that the
887/// platform's drag session has finished.
888#[derive(Debug, Clone)]
889pub enum FileDropEvent {
890 /// The drag entered the window.
891 Entered {
892 /// The pointer position in window coordinates, in logical pixels.
893 position: Point<Pixels>,
894 /// The paths of the files being dragged.
895 paths: ExternalPaths,
896 },
897 /// The drag moved over the window.
898 Pending {
899 /// The pointer position in window coordinates, in logical pixels.
900 position: Point<Pixels>,
901 },
902 /// The user dropped the files on the window.
903 Submit {
904 /// The pointer position in window coordinates, in logical pixels.
905 position: Point<Pixels>,
906 },
907 /// The drag left the window without a drop.
908 Exited,
909 /// The platform's drag session ended.
910 Ended,
911}
912
913impl Sealed for FileDropEvent {}
914impl InputEvent for FileDropEvent {
915 fn to_platform_input(self) -> PlatformInput {
916 PlatformInput::FileDrop(self)
917 }
918}
919impl MouseEvent for FileDropEvent {}
920
921/// Any input event the platform sends to a window.
922///
923/// Platform backends build this enum and hand it to the window, which routes
924/// each variant to the matching listeners. You rarely construct it yourself.
925/// Match on it to see the raw event, or call [`PlatformInput::kind_name`] for a
926/// label.
927#[derive(Clone, Debug)]
928pub enum PlatformInput {
929 /// A key was pressed. See [`KeyDownEvent`].
930 KeyDown(KeyDownEvent),
931 /// A key was released. See [`KeyUpEvent`].
932 KeyUp(KeyUpEvent),
933 /// The modifier keys or Caps Lock changed. See [`ModifiersChangedEvent`].
934 ModifiersChanged(ModifiersChangedEvent),
935 /// A mouse button was pressed. See [`MouseDownEvent`].
936 MouseDown(MouseDownEvent),
937 /// A mouse button was released. See [`MouseUpEvent`].
938 MouseUp(MouseUpEvent),
939 /// A force-sensitive trackpad changed pressure. See [`MousePressureEvent`].
940 MousePressure(MousePressureEvent),
941 /// The pointer moved. See [`MouseMoveEvent`].
942 MouseMove(MouseMoveEvent),
943 /// The pointer left the window. See [`MouseExitEvent`].
944 MouseExited(MouseExitEvent),
945 /// A scroll from a wheel, trackpad or touch pan. See [`ScrollWheelEvent`].
946 ScrollWheel(ScrollWheelEvent),
947 /// A pinch-to-zoom gesture. See [`PinchEvent`].
948 Pinch(PinchEvent),
949 /// A long press recognized from touch input. See [`LongPressEvent`].
950 LongPress(LongPressEvent),
951 /// A touch drag claimed by an element. See [`TouchDragEvent`].
952 TouchDrag(TouchDragEvent),
953 /// Files were dragged onto, or dropped on, the window. See [`FileDropEvent`].
954 FileDrop(FileDropEvent),
955 /// A raw sample from a touch screen, before gesture recognition. See [`TouchEvent`].
956 Touch(TouchEvent),
957}
958
959impl PlatformInput {
960 pub(crate) fn mouse_event(&self) -> Option<&dyn Any> {
961 match self {
962 PlatformInput::KeyDown { .. } => None,
963 PlatformInput::KeyUp { .. } => None,
964 PlatformInput::ModifiersChanged { .. } => None,
965 PlatformInput::MouseDown(event) => Some(event),
966 PlatformInput::MouseUp(event) => Some(event),
967 PlatformInput::MouseMove(event) => Some(event),
968 PlatformInput::MousePressure(event) => Some(event),
969 PlatformInput::MouseExited(event) => Some(event),
970 PlatformInput::ScrollWheel(event) => Some(event),
971 PlatformInput::Pinch(event) => Some(event),
972 PlatformInput::LongPress(event) => Some(event),
973 PlatformInput::TouchDrag(event) => Some(event),
974 PlatformInput::FileDrop(event) => Some(event),
975 PlatformInput::Touch(_) => None,
976 }
977 }
978
979 pub(crate) fn keyboard_event(&self) -> Option<&dyn Any> {
980 match self {
981 PlatformInput::KeyDown(event) => Some(event),
982 PlatformInput::KeyUp(event) => Some(event),
983 PlatformInput::ModifiersChanged(event) => Some(event),
984 PlatformInput::MouseDown(_) => None,
985 PlatformInput::MouseUp(_) => None,
986 PlatformInput::MouseMove(_) => None,
987 PlatformInput::MousePressure(_) => None,
988 PlatformInput::MouseExited(_) => None,
989 PlatformInput::ScrollWheel(_) => None,
990 PlatformInput::Pinch(_) => None,
991 PlatformInput::LongPress(_) => None,
992 PlatformInput::TouchDrag(_) => None,
993 PlatformInput::FileDrop(_) => None,
994 PlatformInput::Touch(_) => None,
995 }
996 }
997
998 /// Returns a short, fixed name for this variant, such as `"mouse_down"`.
999 ///
1000 /// Use it in logs and telemetry. The names are stable snake_case strings.
1001 pub fn kind_name(&self) -> &'static str {
1002 match self {
1003 PlatformInput::KeyDown(_) => "key_down",
1004 PlatformInput::KeyUp(_) => "key_up",
1005 PlatformInput::ModifiersChanged(_) => "modifiers_changed",
1006 PlatformInput::MouseDown(_) => "mouse_down",
1007 PlatformInput::MouseUp(_) => "mouse_up",
1008 PlatformInput::MousePressure(_) => "mouse_pressure",
1009 PlatformInput::MouseMove(_) => "mouse_move",
1010 PlatformInput::MouseExited(_) => "mouse_exited",
1011 PlatformInput::ScrollWheel(_) => "scroll_wheel",
1012 PlatformInput::Pinch(_) => "pinch",
1013 PlatformInput::LongPress(_) => "long_press",
1014 PlatformInput::TouchDrag(_) => "touch_drag",
1015 PlatformInput::FileDrop(_) => "file_drop",
1016 PlatformInput::Touch(_) => "touch",
1017 }
1018 }
1019
1020 /// Returns the raw [`TouchEvent`] if this input is [`PlatformInput::Touch`],
1021 /// or `None` for every other variant.
1022 pub fn touch_event(&self) -> Option<&TouchEvent> {
1023 match self {
1024 PlatformInput::Touch(event) => Some(event),
1025 _ => None,
1026 }
1027 }
1028}
1029
1030#[cfg(test)]
1031mod test {
1032
1033 use crate::{
1034 self as gpui, AppContext as _, Context, FocusHandle, InteractiveElement, IntoElement,
1035 KeyBinding, Keystroke, Modifiers, ParentElement, Render, TestAppContext, Window, div,
1036 };
1037
1038 struct TestView {
1039 saw_key_down: bool,
1040 saw_action: bool,
1041 focus_handle: FocusHandle,
1042 }
1043
1044 actions!(test_only, [TestAction]);
1045
1046 impl Render for TestView {
1047 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1048 div().id("testview").child(
1049 div()
1050 .key_context("parent")
1051 .on_key_down(cx.listener(|this, _, _, cx| {
1052 cx.stop_propagation();
1053 this.saw_key_down = true
1054 }))
1055 .on_action(cx.listener(|this: &mut TestView, _: &TestAction, _, _| {
1056 this.saw_action = true
1057 }))
1058 .child(
1059 div()
1060 .key_context("nested")
1061 .track_focus(&self.focus_handle)
1062 .into_element(),
1063 ),
1064 )
1065 }
1066 }
1067
1068 #[gpui::test]
1069 fn test_on_events(cx: &mut TestAppContext) {
1070 let window = cx.update(|cx| {
1071 cx.open_window(Default::default(), |_, cx| {
1072 cx.new(|cx| TestView {
1073 saw_key_down: false,
1074 saw_action: false,
1075 focus_handle: cx.focus_handle(),
1076 })
1077 })
1078 .unwrap()
1079 });
1080
1081 cx.update(|cx| {
1082 cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
1083 });
1084
1085 window
1086 .update(cx, |test_view, window, cx| {
1087 window.focus(&test_view.focus_handle, cx)
1088 })
1089 .unwrap();
1090
1091 cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap());
1092 cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap());
1093
1094 window
1095 .update(cx, |test_view, _, _| {
1096 assert!(test_view.saw_key_down || test_view.saw_action);
1097 assert!(test_view.saw_key_down);
1098 assert!(test_view.saw_action);
1099 })
1100 .unwrap();
1101 }
1102
1103 #[gpui::test]
1104 fn test_multi_modifier_gesture_does_not_dispatch_standalone_modifier_binding(
1105 cx: &mut TestAppContext,
1106 ) {
1107 let (test_view, cx) = cx.add_window_view(|_, cx| TestView {
1108 saw_key_down: false,
1109 saw_action: false,
1110 focus_handle: cx.focus_handle(),
1111 });
1112
1113 cx.update(|_, cx| {
1114 cx.bind_keys(vec![KeyBinding::new("shift", TestAction, None)]);
1115 });
1116 test_view.update_in(cx, |test_view, window, cx| {
1117 window.focus(&test_view.focus_handle, cx);
1118 });
1119
1120 cx.simulate_modifiers_change(Modifiers::alt());
1121 cx.simulate_modifiers_change(Modifiers::alt() | Modifiers::shift());
1122 cx.simulate_modifiers_change(Modifiers::shift());
1123 cx.simulate_modifiers_change(Modifiers::none());
1124 assert!(!test_view.read_with(cx, |test_view, _| test_view.saw_action));
1125
1126 cx.simulate_modifiers_change(Modifiers::shift());
1127 cx.simulate_modifiers_change(Modifiers::none());
1128 assert!(test_view.read_with(cx, |test_view, _| test_view.saw_action));
1129 }
1130}