Skip to main content

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/// An event from a platform input source.
9pub trait InputEvent: Sealed + 'static {
10    /// Convert this event into the platform input enum.
11    fn to_platform_input(self) -> PlatformInput;
12}
13
14/// A key event from the platform.
15pub trait KeyEvent: InputEvent {}
16
17/// A mouse event from the platform.
18pub trait MouseEvent: InputEvent {}
19
20/// A gesture event from the platform.
21pub trait GestureEvent: InputEvent {}
22
23/// What the pressed key gives on the active keyboard layout, under each shift
24/// state.
25///
26/// [`Keystroke::key`] on macOS holds the shifted key for anything but `a`-`z`,
27/// and [`Modifiers::shift`] is cleared along with that fold: `shift-1`
28/// arrives as `!` with no shift held. These fields are what the fold drops.
29#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
30pub struct KeyLayout {
31    /// The key with no modifier applied: `1` for `shift-1`.
32    pub unshifted: String,
33
34    /// The key shift gives, where the layout gives a different one: `!` for
35    /// `shift-1`, `A` for `shift-a`.
36    pub shifted: Option<String>,
37
38    /// Whether shift was held, which [`Keystroke::modifiers`] does not always
39    /// say.
40    pub shift: bool,
41}
42
43/// The key down event equivalent for the platform.
44#[derive(Clone, Debug, Eq, PartialEq)]
45pub struct KeyDownEvent {
46    /// The keystroke that was generated.
47    pub keystroke: Keystroke,
48
49    /// Whether the key is currently held down.
50    pub is_held: bool,
51
52    /// Whether to prefer character input over keybindings for this keystroke.
53    /// In some cases, like AltGr on Windows, modifiers are significant for character input.
54    pub prefer_character_input: bool,
55
56    /// What the layout gives for the key that was pressed. macOS fills this
57    /// for the text keys; the other platforms and the function keys leave it
58    /// `None`.
59    pub layout: Option<KeyLayout>,
60}
61
62impl Sealed for KeyDownEvent {}
63impl InputEvent for KeyDownEvent {
64    fn to_platform_input(self) -> PlatformInput {
65        PlatformInput::KeyDown(self)
66    }
67}
68impl KeyEvent for KeyDownEvent {}
69
70/// The key up event equivalent for the platform.
71#[derive(Clone, Debug)]
72pub struct KeyUpEvent {
73    /// The keystroke that was released.
74    pub keystroke: Keystroke,
75
76    /// What the layout gives for the key that was released. Filled where
77    /// [`KeyDownEvent::layout`] is.
78    pub layout: Option<KeyLayout>,
79}
80
81impl Sealed for KeyUpEvent {}
82impl InputEvent for KeyUpEvent {
83    fn to_platform_input(self) -> PlatformInput {
84        PlatformInput::KeyUp(self)
85    }
86}
87impl KeyEvent for KeyUpEvent {}
88
89/// The modifiers changed event equivalent for the platform.
90#[derive(Clone, Debug, Default)]
91pub struct ModifiersChangedEvent {
92    /// The new state of the modifier keys
93    pub modifiers: Modifiers,
94    /// The new state of the capslock key
95    pub capslock: Capslock,
96}
97
98impl Sealed for ModifiersChangedEvent {}
99impl InputEvent for ModifiersChangedEvent {
100    fn to_platform_input(self) -> PlatformInput {
101        PlatformInput::ModifiersChanged(self)
102    }
103}
104impl KeyEvent for ModifiersChangedEvent {}
105
106impl Deref for ModifiersChangedEvent {
107    type Target = Modifiers;
108
109    fn deref(&self) -> &Self::Target {
110        &self.modifiers
111    }
112}
113
114/// The phase of a touch motion event.
115/// Based on the winit enum of the same name.
116#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
117pub enum TouchPhase {
118    /// The touch started.
119    Started,
120    /// The touch event is moving.
121    #[default]
122    Moved,
123    /// The touch phase has ended
124    Ended,
125    /// The touch was cancelled: the system took it and it will not end
126    /// normally. Consumers must fully unwind any in-progress interaction,
127    /// treating the touch as if it never committed.
128    Cancelled,
129}
130
131/// Identifies one touch (finger or stylus contact) for its lifetime, from
132/// [`TouchPhase::Started`] through [`TouchPhase::Ended`] or
133/// [`TouchPhase::Cancelled`].
134///
135/// The value is opaque and assigned by the platform. A platform window must
136/// not reuse an identifier for a later touch.
137#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
138pub struct TouchId(pub u64);
139
140/// A raw touch event from the platform.
141///
142///
143/// Dispatch contract (core implementation pending): a touch is hit-tested
144/// once, at [`TouchPhase::Started`], occlusion-aware; all subsequent events
145/// for the same [`TouchId`] are delivered to the elements under the starting
146/// position, even after the touch moves outside them.
147#[derive(Clone, Debug, Default)]
148pub struct TouchEvent {
149    /// Which touch this event belongs to.
150    pub id: TouchId,
151    /// The phase of the touch.
152    pub phase: TouchPhase,
153    /// The position of the touch in window coordinates.
154    pub position: Point<Pixels>,
155    /// Where the platform predicts the touch will be roughly one frame from
156    /// now, in the same coordinate space as `position`, when the platform
157    /// offers a prediction for a [`TouchPhase::Moved`] event.
158    ///
159    /// Best-effort latency compensation only: it may influence how far a
160    /// recognized pan scrolls within a frame, but never hit testing, gesture
161    /// classification, or velocity estimation, and any error it introduces
162    /// must be corrected by later events for the same touch.
163    pub predicted_position: Option<Point<Pixels>>,
164    /// Normalized touch force in `0.0..=1.0`, if the hardware reports it.
165    pub force: Option<f32>,
166}
167
168impl Sealed for TouchEvent {}
169impl InputEvent for TouchEvent {
170    fn to_platform_input(self) -> PlatformInput {
171        PlatformInput::Touch(self)
172    }
173}
174
175/// A mouse down event from the platform
176#[derive(Clone, Debug, Default)]
177pub struct MouseDownEvent {
178    /// Which mouse button was pressed.
179    pub button: MouseButton,
180
181    /// The position of the mouse on the window.
182    pub position: Point<Pixels>,
183
184    /// The modifiers that were held down when the mouse was pressed.
185    pub modifiers: Modifiers,
186
187    /// The number of times the button has been clicked.
188    pub click_count: usize,
189
190    /// Whether this is the first, focusing click.
191    pub first_mouse: bool,
192}
193
194impl Sealed for MouseDownEvent {}
195impl InputEvent for MouseDownEvent {
196    fn to_platform_input(self) -> PlatformInput {
197        PlatformInput::MouseDown(self)
198    }
199}
200impl MouseEvent for MouseDownEvent {}
201
202impl MouseDownEvent {
203    /// Returns true if this mouse up event should focus the element.
204    pub fn is_focusing(&self) -> bool {
205        match self.button {
206            MouseButton::Left => true,
207            _ => false,
208        }
209    }
210}
211
212/// A mouse up event from the platform
213#[derive(Clone, Debug, Default)]
214pub struct MouseUpEvent {
215    /// Which mouse button was released.
216    pub button: MouseButton,
217
218    /// The position of the mouse on the window.
219    pub position: Point<Pixels>,
220
221    /// The modifiers that were held down when the mouse was released.
222    pub modifiers: Modifiers,
223
224    /// The number of times the button has been clicked.
225    pub click_count: usize,
226}
227
228impl Sealed for MouseUpEvent {}
229impl InputEvent for MouseUpEvent {
230    fn to_platform_input(self) -> PlatformInput {
231        PlatformInput::MouseUp(self)
232    }
233}
234
235impl MouseEvent for MouseUpEvent {}
236
237impl MouseUpEvent {
238    /// Returns true if this mouse up event should focus the element.
239    pub fn is_focusing(&self) -> bool {
240        match self.button {
241            MouseButton::Left => true,
242            _ => false,
243        }
244    }
245}
246
247/// A click event, generated when a mouse button is pressed and released.
248#[derive(Clone, Debug, Default)]
249pub struct MouseClickEvent {
250    /// The mouse event when the button was pressed.
251    pub down: MouseDownEvent,
252
253    /// The mouse event when the button was released.
254    pub up: MouseUpEvent,
255}
256
257/// The stage of a pressure click event.
258#[derive(Clone, Copy, Debug, Default, PartialEq)]
259pub enum PressureStage {
260    /// No pressure.
261    #[default]
262    Zero,
263    /// Normal click pressure.
264    Normal,
265    /// High pressure, enough to trigger a force click.
266    Force,
267}
268
269/// A mouse pressure event from the platform. Generated when a force-sensitive trackpad is pressed hard.
270/// Currently only implemented for macOS trackpads.
271#[derive(Debug, Clone, Default)]
272pub struct MousePressureEvent {
273    /// Pressure of the current stage as a float between 0 and 1
274    pub pressure: f32,
275    /// The pressure stage of the event.
276    pub stage: PressureStage,
277    /// The position of the mouse on the window.
278    pub position: Point<Pixels>,
279    /// The modifiers that were held down when the mouse pressure changed.
280    pub modifiers: Modifiers,
281}
282
283impl Sealed for MousePressureEvent {}
284impl InputEvent for MousePressureEvent {
285    fn to_platform_input(self) -> PlatformInput {
286        PlatformInput::MousePressure(self)
287    }
288}
289impl MouseEvent for MousePressureEvent {}
290
291/// A click event that was generated by a keyboard button being pressed and released.
292#[derive(Clone, Debug, Default)]
293pub struct KeyboardClickEvent {
294    /// The keyboard button that was pressed to trigger the click.
295    pub button: KeyboardButton,
296
297    /// The bounds of the element that was clicked.
298    pub bounds: Bounds<Pixels>,
299}
300
301/// A click event that was generated by a recognized tap gesture on a touch
302/// screen.
303#[derive(Clone, Debug, Default)]
304pub struct TouchClickEvent {
305    /// The position of the tap in window coordinates.
306    pub position: Point<Pixels>,
307    /// The number of consecutive taps at this location (double tap = 2),
308    /// analogous to the mouse `click_count`.
309    pub tap_count: usize,
310    /// Whether this was a long press rather than a tap. Long presses are
311    /// touch's secondary activation: they are delivered to aux-click
312    /// listeners alongside right clicks, not to primary click listeners.
313    pub long_press: bool,
314}
315
316/// A click event, generated when a mouse button or keyboard button is pressed and released,
317/// or when a tap gesture is recognized on a touch screen.
318#[derive(Clone, Debug)]
319pub enum ClickEvent {
320    /// A click event trigger by a mouse button being pressed and released.
321    Mouse(MouseClickEvent),
322    /// A click event trigger by a keyboard button being pressed and released.
323    Keyboard(KeyboardClickEvent),
324    /// A click event triggered by a recognized tap gesture on a touch screen.
325    Touch(TouchClickEvent),
326}
327
328impl Default for ClickEvent {
329    fn default() -> Self {
330        ClickEvent::Keyboard(KeyboardClickEvent::default())
331    }
332}
333
334impl ClickEvent {
335    /// Returns the modifiers that were held during the click event
336    ///
337    /// `Keyboard`: The keyboard click events never have modifiers.
338    /// `Mouse`: Modifiers that were held during the mouse key up event.
339    pub fn modifiers(&self) -> Modifiers {
340        match self {
341            // Click events are only generated from keyboard events _without any modifiers_, so we know the modifiers are always Default
342            ClickEvent::Keyboard(_) => Modifiers::default(),
343            // Click events on the web only reflect the modifiers for the keyup event,
344            // tested via observing the behavior of the `ClickEvent.shiftKey` field in Chrome 138
345            // under various combinations of modifiers and keyUp / keyDown events.
346            ClickEvent::Mouse(event) => event.up.modifiers,
347            // Touch screens have no modifier keys.
348            ClickEvent::Touch(_) => Modifiers::default(),
349        }
350    }
351
352    /// Returns the position of the click event
353    ///
354    /// `Keyboard`: The bottom left corner of the clicked hitbox
355    /// `Mouse`: The position of the mouse when the button was released.
356    /// `Touch`: The position of the tap.
357    pub fn position(&self) -> Point<Pixels> {
358        match self {
359            ClickEvent::Keyboard(event) => event.bounds.bottom_left(),
360            ClickEvent::Mouse(event) => event.up.position,
361            ClickEvent::Touch(event) => event.position,
362        }
363    }
364
365    /// Returns the mouse position of the click event
366    ///
367    /// `Keyboard`: None
368    /// `Mouse`: The position of the mouse when the button was released.
369    /// `Touch`: None, touches are not mouse input and there is no cursor.
370    pub fn mouse_position(&self) -> Option<Point<Pixels>> {
371        match self {
372            ClickEvent::Keyboard(_) => None,
373            ClickEvent::Mouse(event) => Some(event.up.position),
374            ClickEvent::Touch(_) => None,
375        }
376    }
377
378    /// Returns if this was a right click
379    ///
380    /// `Keyboard`: false
381    /// `Mouse`: Whether the right button was pressed and released
382    pub fn is_right_click(&self) -> bool {
383        match self {
384            ClickEvent::Keyboard(_) => false,
385            ClickEvent::Mouse(event) => {
386                event.down.button == MouseButton::Right && event.up.button == MouseButton::Right
387            }
388            ClickEvent::Touch(_) => false,
389        }
390    }
391
392    /// Returns if this was a middle click
393    ///
394    /// `Keyboard`: false
395    /// `Mouse`: Whether the middle button was pressed and released
396    pub fn is_middle_click(&self) -> bool {
397        match self {
398            ClickEvent::Keyboard(_) => false,
399            ClickEvent::Mouse(event) => {
400                event.down.button == MouseButton::Middle && event.up.button == MouseButton::Middle
401            }
402            ClickEvent::Touch(_) => false,
403        }
404    }
405
406    /// Returns whether the click is a secondary activation, i.e. a context
407    /// menu trigger: a right click from a mouse (macOS ctrl-clicks arrive
408    /// already converted to right clicks by the platform layer), or a long
409    /// press on a touch screen.
410    pub fn is_secondary(&self) -> bool {
411        match self {
412            ClickEvent::Keyboard(_) => false,
413            ClickEvent::Mouse(event) => {
414                event.down.button == MouseButton::Right && event.up.button == MouseButton::Right
415            }
416            ClickEvent::Touch(event) => event.long_press,
417        }
418    }
419
420    /// Returns whether the click was a standard click
421    ///
422    /// `Keyboard`: Always true
423    /// `Mouse`: Left button pressed and released
424    /// `Touch`: A tap, but not a long press
425    pub fn standard_click(&self) -> bool {
426        match self {
427            ClickEvent::Keyboard(_) => true,
428            ClickEvent::Mouse(event) => {
429                event.down.button == MouseButton::Left && event.up.button == MouseButton::Left
430            }
431            ClickEvent::Touch(event) => !event.long_press,
432        }
433    }
434
435    /// Returns whether the click focused the element
436    ///
437    /// `Keyboard`: false, keyboard clicks only work if an element is already focused
438    /// `Mouse`: Whether this was the first focusing click
439    /// `Touch`: false, mobile windows are already active when tappable
440    pub fn first_focus(&self) -> bool {
441        match self {
442            ClickEvent::Keyboard(_) => false,
443            ClickEvent::Mouse(event) => event.down.first_mouse,
444            ClickEvent::Touch(_) => false,
445        }
446    }
447
448    /// Returns the click count of the click event
449    ///
450    /// `Keyboard`: Always 1
451    /// `Mouse`: Count of clicks from MouseUpEvent
452    /// `Touch`: Count of consecutive taps
453    pub fn click_count(&self) -> usize {
454        match self {
455            ClickEvent::Keyboard(_) => 1,
456            ClickEvent::Mouse(event) => event.up.click_count,
457            ClickEvent::Touch(event) => event.tap_count,
458        }
459    }
460
461    /// Returns whether the click event is generated by a keyboard event
462    pub fn is_keyboard(&self) -> bool {
463        match self {
464            ClickEvent::Mouse(_) | ClickEvent::Touch(_) => false,
465            ClickEvent::Keyboard(_) => true,
466        }
467    }
468}
469
470/// An enum representing the keyboard button that was pressed for a click event.
471#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug, Default)]
472pub enum KeyboardButton {
473    /// Enter key was clicked
474    #[default]
475    Enter,
476    /// Space key was clicked
477    Space,
478}
479
480/// An enum representing the mouse button that was pressed.
481#[derive(Hash, Default, PartialEq, Eq, Copy, Clone, Debug)]
482pub enum MouseButton {
483    /// The left mouse button.
484    #[default]
485    Left,
486
487    /// The right mouse button.
488    Right,
489
490    /// The middle mouse button.
491    Middle,
492
493    /// A navigation button, such as back or forward.
494    Navigate(NavigationDirection),
495}
496
497impl MouseButton {
498    /// Get all the mouse buttons in a list.
499    pub fn all() -> Vec<Self> {
500        vec![
501            MouseButton::Left,
502            MouseButton::Right,
503            MouseButton::Middle,
504            MouseButton::Navigate(NavigationDirection::Back),
505            MouseButton::Navigate(NavigationDirection::Forward),
506        ]
507    }
508}
509
510/// A navigation direction, such as back or forward.
511#[derive(Hash, Default, PartialEq, Eq, Copy, Clone, Debug)]
512pub enum NavigationDirection {
513    /// The back button.
514    #[default]
515    Back,
516
517    /// The forward button.
518    Forward,
519}
520
521/// A mouse move event from the platform.
522#[derive(Clone, Debug, Default)]
523pub struct MouseMoveEvent {
524    /// The position of the mouse on the window.
525    pub position: Point<Pixels>,
526
527    /// The mouse button that was pressed, if any.
528    pub pressed_button: Option<MouseButton>,
529
530    /// The modifiers that were held down when the mouse was moved.
531    pub modifiers: Modifiers,
532}
533
534impl Sealed for MouseMoveEvent {}
535impl InputEvent for MouseMoveEvent {
536    fn to_platform_input(self) -> PlatformInput {
537        PlatformInput::MouseMove(self)
538    }
539}
540impl MouseEvent for MouseMoveEvent {}
541
542impl MouseMoveEvent {
543    /// Returns true if the left mouse button is currently held down.
544    pub fn dragging(&self) -> bool {
545        self.pressed_button == Some(MouseButton::Left)
546    }
547}
548
549/// A mouse wheel event from the platform.
550#[derive(Clone, Debug, Default)]
551pub struct ScrollWheelEvent {
552    /// The position of the mouse on the window.
553    pub position: Point<Pixels>,
554
555    /// The change in scroll wheel position for this event.
556    pub delta: ScrollDelta,
557
558    /// The modifiers that were held down when the mouse was moved.
559    pub modifiers: Modifiers,
560
561    /// The phase of the touch event.
562    pub touch_phase: TouchPhase,
563}
564
565impl Sealed for ScrollWheelEvent {}
566impl InputEvent for ScrollWheelEvent {
567    fn to_platform_input(self) -> PlatformInput {
568        PlatformInput::ScrollWheel(self)
569    }
570}
571impl MouseEvent for ScrollWheelEvent {}
572
573impl Deref for ScrollWheelEvent {
574    type Target = Modifiers;
575
576    fn deref(&self) -> &Self::Target {
577        &self.modifiers
578    }
579}
580
581/// The scroll delta for a scroll wheel event.
582#[derive(Clone, Copy, Debug)]
583pub enum ScrollDelta {
584    /// An exact scroll delta in pixels.
585    Pixels(Point<Pixels>),
586    /// An inexact scroll delta in lines.
587    Lines(Point<f32>),
588}
589
590impl Default for ScrollDelta {
591    fn default() -> Self {
592        Self::Lines(Default::default())
593    }
594}
595
596/// A pinch gesture event from the platform, generated when the user performs
597/// a pinch-to-zoom gesture (typically on a trackpad).
598///
599#[derive(Clone, Debug, Default)]
600pub struct PinchEvent {
601    /// The position of the pinch center on the window.
602    pub position: Point<Pixels>,
603
604    /// The zoom delta for this event.
605    /// Positive values indicate zooming in, negative values indicate zooming out.
606    /// For example, 0.1 represents a 10% zoom increase.
607    pub delta: f32,
608
609    /// The modifiers that were held down during the pinch gesture.
610    pub modifiers: Modifiers,
611
612    /// The phase of the pinch gesture.
613    pub phase: TouchPhase,
614}
615
616impl Sealed for PinchEvent {}
617impl InputEvent for PinchEvent {
618    fn to_platform_input(self) -> PlatformInput {
619        PlatformInput::Pinch(self)
620    }
621}
622impl GestureEvent for PinchEvent {}
623impl MouseEvent for PinchEvent {}
624
625impl Deref for PinchEvent {
626    type Target = Modifiers;
627
628    fn deref(&self) -> &Self::Target {
629        &self.modifiers
630    }
631}
632
633impl ScrollDelta {
634    /// Returns true if this is a precise scroll delta in pixels.
635    pub fn precise(&self) -> bool {
636        match self {
637            ScrollDelta::Pixels(_) => true,
638            ScrollDelta::Lines(_) => false,
639        }
640    }
641
642    /// Converts this scroll event into exact pixels.
643    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
644        match self {
645            ScrollDelta::Pixels(delta) => *delta,
646            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
647        }
648    }
649
650    /// Combines two scroll deltas into one.
651    /// If the signs of the deltas are the same (both positive or both negative),
652    /// the deltas are added together. If the signs are opposite, the second delta
653    /// (other) is used, effectively overriding the first delta.
654    pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
655        match (self, other) {
656            (ScrollDelta::Pixels(a), ScrollDelta::Pixels(b)) => {
657                let x = if a.x.signum() == b.x.signum() {
658                    a.x + b.x
659                } else {
660                    b.x
661                };
662
663                let y = if a.y.signum() == b.y.signum() {
664                    a.y + b.y
665                } else {
666                    b.y
667                };
668
669                ScrollDelta::Pixels(point(x, y))
670            }
671
672            (ScrollDelta::Lines(a), ScrollDelta::Lines(b)) => {
673                let x = if a.x.signum() == b.x.signum() {
674                    a.x + b.x
675                } else {
676                    b.x
677                };
678
679                let y = if a.y.signum() == b.y.signum() {
680                    a.y + b.y
681                } else {
682                    b.y
683                };
684
685                ScrollDelta::Lines(point(x, y))
686            }
687
688            _ => other,
689        }
690    }
691}
692
693/// A mouse exit event from the platform, generated when the mouse leaves the window.
694#[derive(Clone, Debug, Default)]
695pub struct MouseExitEvent {
696    /// The position of the mouse relative to the window.
697    pub position: Point<Pixels>,
698    /// The mouse button that was pressed, if any.
699    pub pressed_button: Option<MouseButton>,
700    /// The modifiers that were held down when the mouse was moved.
701    pub modifiers: Modifiers,
702}
703
704impl Sealed for MouseExitEvent {}
705impl InputEvent for MouseExitEvent {
706    fn to_platform_input(self) -> PlatformInput {
707        PlatformInput::MouseExited(self)
708    }
709}
710
711impl MouseEvent for MouseExitEvent {}
712
713impl Deref for MouseExitEvent {
714    type Target = Modifiers;
715
716    fn deref(&self) -> &Self::Target {
717        &self.modifiers
718    }
719}
720
721/// A collection of paths from the platform, such as from a file drop.
722#[derive(Debug, Clone, Default, Eq, PartialEq)]
723pub struct ExternalPaths(pub SmallVec<[PathBuf; 2]>);
724
725impl ExternalPaths {
726    /// Convert this collection of paths into a slice.
727    pub fn paths(&self) -> &[PathBuf] {
728        &self.0
729    }
730}
731
732/// Data offered to the platform when an internal drag leaves the window and is
733/// promoted to a native drag session.
734#[derive(Debug, Clone, Eq, PartialEq)]
735pub enum ExternalDragPayload {
736    /// Real on-disk paths, handed to the platform as an outbound file drag.
737    Files(FileDragPaths),
738}
739
740/// Paths handed to the platform for a native file drag. Directory metadata is
741/// provided by the caller to avoid querying it when the platform drag starts.
742#[derive(Debug, Clone, Default, Eq, PartialEq)]
743pub struct FileDragPaths(SmallVec<[(PathBuf, bool); 2]>);
744
745impl FileDragPaths {
746    /// Creates a native file-drag payload from paths paired with whether each path is a directory.
747    pub fn new(entries: impl IntoIterator<Item = (PathBuf, bool)>) -> Self {
748        Self(entries.into_iter().collect())
749    }
750
751    /// The dragged paths, each paired with whether it is a directory.
752    pub fn entries(&self) -> &[(PathBuf, bool)] {
753        &self.0
754    }
755}
756
757impl Render for ExternalPaths {
758    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
759        // the platform will render icons for the dragged files
760        Empty
761    }
762}
763
764/// A file drop event from the platform, generated when files are dragged and dropped onto the window.
765#[derive(Debug, Clone)]
766pub enum FileDropEvent {
767    /// The files have entered the window.
768    Entered {
769        /// The position of the mouse relative to the window.
770        position: Point<Pixels>,
771        /// The paths of the files that are being dragged.
772        paths: ExternalPaths,
773    },
774    /// The files are being dragged over the window
775    Pending {
776        /// The position of the mouse relative to the window.
777        position: Point<Pixels>,
778    },
779    /// The files have been dropped onto the window.
780    Submit {
781        /// The position of the mouse relative to the window.
782        position: Point<Pixels>,
783    },
784    /// The user has stopped dragging the files over the window.
785    Exited,
786    /// The platform-owned drag session has ended.
787    Ended,
788}
789
790impl Sealed for FileDropEvent {}
791impl InputEvent for FileDropEvent {
792    fn to_platform_input(self) -> PlatformInput {
793        PlatformInput::FileDrop(self)
794    }
795}
796impl MouseEvent for FileDropEvent {}
797
798/// An enum corresponding to all kinds of platform input events.
799#[derive(Clone, Debug)]
800pub enum PlatformInput {
801    /// A key was pressed.
802    KeyDown(KeyDownEvent),
803    /// A key was released.
804    KeyUp(KeyUpEvent),
805    /// The keyboard modifiers were changed.
806    ModifiersChanged(ModifiersChangedEvent),
807    /// The mouse was pressed.
808    MouseDown(MouseDownEvent),
809    /// The mouse was released.
810    MouseUp(MouseUpEvent),
811    /// Mouse pressure.
812    MousePressure(MousePressureEvent),
813    /// The mouse was moved.
814    MouseMove(MouseMoveEvent),
815    /// The mouse exited the window.
816    MouseExited(MouseExitEvent),
817    /// The scroll wheel was used.
818    ScrollWheel(ScrollWheelEvent),
819    /// A pinch gesture was performed.
820    Pinch(PinchEvent),
821    /// A long-press gesture recognized from touch input.
822    LongPress(LongPressEvent),
823    /// A direct touch drag claimed by an element.
824    TouchDrag(TouchDragEvent),
825    /// Files were dragged and dropped onto the window.
826    FileDrop(FileDropEvent),
827    /// A raw touch event on a touch screen.
828    Touch(TouchEvent),
829}
830
831impl PlatformInput {
832    pub(crate) fn mouse_event(&self) -> Option<&dyn Any> {
833        match self {
834            PlatformInput::KeyDown { .. } => None,
835            PlatformInput::KeyUp { .. } => None,
836            PlatformInput::ModifiersChanged { .. } => None,
837            PlatformInput::MouseDown(event) => Some(event),
838            PlatformInput::MouseUp(event) => Some(event),
839            PlatformInput::MouseMove(event) => Some(event),
840            PlatformInput::MousePressure(event) => Some(event),
841            PlatformInput::MouseExited(event) => Some(event),
842            PlatformInput::ScrollWheel(event) => Some(event),
843            PlatformInput::Pinch(event) => Some(event),
844            PlatformInput::LongPress(event) => Some(event),
845            PlatformInput::TouchDrag(event) => Some(event),
846            PlatformInput::FileDrop(event) => Some(event),
847            PlatformInput::Touch(_) => None,
848        }
849    }
850
851    pub(crate) fn keyboard_event(&self) -> Option<&dyn Any> {
852        match self {
853            PlatformInput::KeyDown(event) => Some(event),
854            PlatformInput::KeyUp(event) => Some(event),
855            PlatformInput::ModifiersChanged(event) => Some(event),
856            PlatformInput::MouseDown(_) => None,
857            PlatformInput::MouseUp(_) => None,
858            PlatformInput::MouseMove(_) => None,
859            PlatformInput::MousePressure(_) => None,
860            PlatformInput::MouseExited(_) => None,
861            PlatformInput::ScrollWheel(_) => None,
862            PlatformInput::Pinch(_) => None,
863            PlatformInput::LongPress(_) => None,
864            PlatformInput::TouchDrag(_) => None,
865            PlatformInput::FileDrop(_) => None,
866            PlatformInput::Touch(_) => None,
867        }
868    }
869
870    /// A short static name for this input's variant, for diagnostics and
871    /// telemetry.
872    pub fn kind_name(&self) -> &'static str {
873        match self {
874            PlatformInput::KeyDown(_) => "key_down",
875            PlatformInput::KeyUp(_) => "key_up",
876            PlatformInput::ModifiersChanged(_) => "modifiers_changed",
877            PlatformInput::MouseDown(_) => "mouse_down",
878            PlatformInput::MouseUp(_) => "mouse_up",
879            PlatformInput::MousePressure(_) => "mouse_pressure",
880            PlatformInput::MouseMove(_) => "mouse_move",
881            PlatformInput::MouseExited(_) => "mouse_exited",
882            PlatformInput::ScrollWheel(_) => "scroll_wheel",
883            PlatformInput::Pinch(_) => "pinch",
884            PlatformInput::LongPress(_) => "long_press",
885            PlatformInput::TouchDrag(_) => "touch_drag",
886            PlatformInput::FileDrop(_) => "file_drop",
887            PlatformInput::Touch(_) => "touch",
888        }
889    }
890
891    /// Returns the touch event contained in this input, if any.
892    pub fn touch_event(&self) -> Option<&TouchEvent> {
893        match self {
894            PlatformInput::Touch(event) => Some(event),
895            _ => None,
896        }
897    }
898}
899
900#[cfg(test)]
901mod test {
902
903    use crate::{
904        self as gpui, AppContext as _, Context, FocusHandle, InteractiveElement, IntoElement,
905        KeyBinding, Keystroke, Modifiers, ParentElement, Render, TestAppContext, Window, div,
906    };
907
908    struct TestView {
909        saw_key_down: bool,
910        saw_action: bool,
911        focus_handle: FocusHandle,
912    }
913
914    actions!(test_only, [TestAction]);
915
916    impl Render for TestView {
917        fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
918            div().id("testview").child(
919                div()
920                    .key_context("parent")
921                    .on_key_down(cx.listener(|this, _, _, cx| {
922                        cx.stop_propagation();
923                        this.saw_key_down = true
924                    }))
925                    .on_action(cx.listener(|this: &mut TestView, _: &TestAction, _, _| {
926                        this.saw_action = true
927                    }))
928                    .child(
929                        div()
930                            .key_context("nested")
931                            .track_focus(&self.focus_handle)
932                            .into_element(),
933                    ),
934            )
935        }
936    }
937
938    #[gpui::test]
939    fn test_on_events(cx: &mut TestAppContext) {
940        let window = cx.update(|cx| {
941            cx.open_window(Default::default(), |_, cx| {
942                cx.new(|cx| TestView {
943                    saw_key_down: false,
944                    saw_action: false,
945                    focus_handle: cx.focus_handle(),
946                })
947            })
948            .unwrap()
949        });
950
951        cx.update(|cx| {
952            cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
953        });
954
955        window
956            .update(cx, |test_view, window, cx| {
957                window.focus(&test_view.focus_handle, cx)
958            })
959            .unwrap();
960
961        cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap());
962        cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap());
963
964        window
965            .update(cx, |test_view, _, _| {
966                assert!(test_view.saw_key_down || test_view.saw_action);
967                assert!(test_view.saw_key_down);
968                assert!(test_view.saw_action);
969            })
970            .unwrap();
971    }
972
973    #[gpui::test]
974    fn test_multi_modifier_gesture_does_not_dispatch_standalone_modifier_binding(
975        cx: &mut TestAppContext,
976    ) {
977        let (test_view, cx) = cx.add_window_view(|_, cx| TestView {
978            saw_key_down: false,
979            saw_action: false,
980            focus_handle: cx.focus_handle(),
981        });
982
983        cx.update(|_, cx| {
984            cx.bind_keys(vec![KeyBinding::new("shift", TestAction, None)]);
985        });
986        test_view.update_in(cx, |test_view, window, cx| {
987            window.focus(&test_view.focus_handle, cx);
988        });
989
990        cx.simulate_modifiers_change(Modifiers::alt());
991        cx.simulate_modifiers_change(Modifiers::alt() | Modifiers::shift());
992        cx.simulate_modifiers_change(Modifiers::shift());
993        cx.simulate_modifiers_change(Modifiers::none());
994        assert!(!test_view.read_with(cx, |test_view, _| test_view.saw_action));
995
996        cx.simulate_modifiers_change(Modifiers::shift());
997        cx.simulate_modifiers_change(Modifiers::none());
998        assert!(test_view.read_with(cx, |test_view, _| test_view.saw_action));
999    }
1000}