interactive.rs

  1use crate::{
  2    Capslock, Context, Empty, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, Window,
  3    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/// The key down event equivalent for the platform.
 21#[derive(Clone, Debug, Eq, PartialEq)]
 22pub struct KeyDownEvent {
 23    /// The keystroke that was generated.
 24    pub keystroke: Keystroke,
 25
 26    /// Whether the key is currently held down.
 27    pub is_held: bool,
 28}
 29
 30impl Sealed for KeyDownEvent {}
 31impl InputEvent for KeyDownEvent {
 32    fn to_platform_input(self) -> PlatformInput {
 33        PlatformInput::KeyDown(self)
 34    }
 35}
 36impl KeyEvent for KeyDownEvent {}
 37
 38/// The key up event equivalent for the platform.
 39#[derive(Clone, Debug)]
 40pub struct KeyUpEvent {
 41    /// The keystroke that was released.
 42    pub keystroke: Keystroke,
 43}
 44
 45impl Sealed for KeyUpEvent {}
 46impl InputEvent for KeyUpEvent {
 47    fn to_platform_input(self) -> PlatformInput {
 48        PlatformInput::KeyUp(self)
 49    }
 50}
 51impl KeyEvent for KeyUpEvent {}
 52
 53/// The modifiers changed event equivalent for the platform.
 54#[derive(Clone, Debug, Default)]
 55pub struct ModifiersChangedEvent {
 56    /// The new state of the modifier keys
 57    pub modifiers: Modifiers,
 58    /// The new state of the capslock key
 59    pub capslock: Capslock,
 60}
 61
 62impl Sealed for ModifiersChangedEvent {}
 63impl InputEvent for ModifiersChangedEvent {
 64    fn to_platform_input(self) -> PlatformInput {
 65        PlatformInput::ModifiersChanged(self)
 66    }
 67}
 68impl KeyEvent for ModifiersChangedEvent {}
 69
 70impl Deref for ModifiersChangedEvent {
 71    type Target = Modifiers;
 72
 73    fn deref(&self) -> &Self::Target {
 74        &self.modifiers
 75    }
 76}
 77
 78/// The phase of a touch motion event.
 79/// Based on the winit enum of the same name.
 80#[derive(Clone, Copy, Debug, Default)]
 81pub enum TouchPhase {
 82    /// The touch started.
 83    Started,
 84    /// The touch event is moving.
 85    #[default]
 86    Moved,
 87    /// The touch phase has ended
 88    Ended,
 89}
 90
 91/// A mouse down event from the platform
 92#[derive(Clone, Debug, Default)]
 93pub struct MouseDownEvent {
 94    /// Which mouse button was pressed.
 95    pub button: MouseButton,
 96
 97    /// The position of the mouse on the window.
 98    pub position: Point<Pixels>,
 99
100    /// The modifiers that were held down when the mouse was pressed.
101    pub modifiers: Modifiers,
102
103    /// The number of times the button has been clicked.
104    pub click_count: usize,
105
106    /// Whether this is the first, focusing click.
107    pub first_mouse: bool,
108}
109
110impl Sealed for MouseDownEvent {}
111impl InputEvent for MouseDownEvent {
112    fn to_platform_input(self) -> PlatformInput {
113        PlatformInput::MouseDown(self)
114    }
115}
116impl MouseEvent for MouseDownEvent {}
117
118/// A mouse up event from the platform
119#[derive(Clone, Debug, Default)]
120pub struct MouseUpEvent {
121    /// Which mouse button was released.
122    pub button: MouseButton,
123
124    /// The position of the mouse on the window.
125    pub position: Point<Pixels>,
126
127    /// The modifiers that were held down when the mouse was released.
128    pub modifiers: Modifiers,
129
130    /// The number of times the button has been clicked.
131    pub click_count: usize,
132}
133
134impl Sealed for MouseUpEvent {}
135impl InputEvent for MouseUpEvent {
136    fn to_platform_input(self) -> PlatformInput {
137        PlatformInput::MouseUp(self)
138    }
139}
140impl MouseEvent for MouseUpEvent {}
141
142/// A click event, generated when a mouse button is pressed and released.
143#[derive(Clone, Debug, Default)]
144pub struct ClickEvent {
145    /// The mouse event when the button was pressed.
146    pub down: MouseDownEvent,
147
148    /// The mouse event when the button was released.
149    pub up: MouseUpEvent,
150}
151
152impl ClickEvent {
153    /// Returns the modifiers that were held down during both the
154    /// mouse down and mouse up events
155    pub fn modifiers(&self) -> Modifiers {
156        Modifiers {
157            control: self.up.modifiers.control && self.down.modifiers.control,
158            alt: self.up.modifiers.alt && self.down.modifiers.alt,
159            shift: self.up.modifiers.shift && self.down.modifiers.shift,
160            platform: self.up.modifiers.platform && self.down.modifiers.platform,
161            function: self.up.modifiers.function && self.down.modifiers.function,
162        }
163    }
164}
165
166/// An enum representing the mouse button that was pressed.
167#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
168pub enum MouseButton {
169    /// The left mouse button.
170    Left,
171
172    /// The right mouse button.
173    Right,
174
175    /// The middle mouse button.
176    Middle,
177
178    /// A navigation button, such as back or forward.
179    Navigate(NavigationDirection),
180}
181
182impl MouseButton {
183    /// Get all the mouse buttons in a list.
184    pub fn all() -> Vec<Self> {
185        vec![
186            MouseButton::Left,
187            MouseButton::Right,
188            MouseButton::Middle,
189            MouseButton::Navigate(NavigationDirection::Back),
190            MouseButton::Navigate(NavigationDirection::Forward),
191        ]
192    }
193}
194
195impl Default for MouseButton {
196    fn default() -> Self {
197        Self::Left
198    }
199}
200
201/// A navigation direction, such as back or forward.
202#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
203pub enum NavigationDirection {
204    /// The back button.
205    Back,
206
207    /// The forward button.
208    Forward,
209}
210
211impl Default for NavigationDirection {
212    fn default() -> Self {
213        Self::Back
214    }
215}
216
217/// A mouse move event from the platform
218#[derive(Clone, Debug, Default)]
219pub struct MouseMoveEvent {
220    /// The position of the mouse on the window.
221    pub position: Point<Pixels>,
222
223    /// The mouse button that was pressed, if any.
224    pub pressed_button: Option<MouseButton>,
225
226    /// The modifiers that were held down when the mouse was moved.
227    pub modifiers: Modifiers,
228}
229
230impl Sealed for MouseMoveEvent {}
231impl InputEvent for MouseMoveEvent {
232    fn to_platform_input(self) -> PlatformInput {
233        PlatformInput::MouseMove(self)
234    }
235}
236impl MouseEvent for MouseMoveEvent {}
237
238impl MouseMoveEvent {
239    /// Returns true if the left mouse button is currently held down.
240    pub fn dragging(&self) -> bool {
241        self.pressed_button == Some(MouseButton::Left)
242    }
243}
244
245/// A mouse wheel event from the platform
246#[derive(Clone, Debug, Default)]
247pub struct ScrollWheelEvent {
248    /// The position of the mouse on the window.
249    pub position: Point<Pixels>,
250
251    /// The change in scroll wheel position for this event.
252    pub delta: ScrollDelta,
253
254    /// The modifiers that were held down when the mouse was moved.
255    pub modifiers: Modifiers,
256
257    /// The phase of the touch event.
258    pub touch_phase: TouchPhase,
259}
260
261impl Sealed for ScrollWheelEvent {}
262impl InputEvent for ScrollWheelEvent {
263    fn to_platform_input(self) -> PlatformInput {
264        PlatformInput::ScrollWheel(self)
265    }
266}
267impl MouseEvent for ScrollWheelEvent {}
268
269impl Deref for ScrollWheelEvent {
270    type Target = Modifiers;
271
272    fn deref(&self) -> &Self::Target {
273        &self.modifiers
274    }
275}
276
277/// The scroll delta for a scroll wheel event.
278#[derive(Clone, Copy, Debug)]
279pub enum ScrollDelta {
280    /// An exact scroll delta in pixels.
281    Pixels(Point<Pixels>),
282    /// An inexact scroll delta in lines.
283    Lines(Point<f32>),
284}
285
286impl Default for ScrollDelta {
287    fn default() -> Self {
288        Self::Lines(Default::default())
289    }
290}
291
292impl ScrollDelta {
293    /// Returns true if this is a precise scroll delta in pixels.
294    pub fn precise(&self) -> bool {
295        match self {
296            ScrollDelta::Pixels(_) => true,
297            ScrollDelta::Lines(_) => false,
298        }
299    }
300
301    /// Converts this scroll event into exact pixels.
302    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
303        match self {
304            ScrollDelta::Pixels(delta) => *delta,
305            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
306        }
307    }
308
309    /// Combines two scroll deltas into one.
310    /// If the signs of the deltas are the same (both positive or both negative),
311    /// the deltas are added together. If the signs are opposite, the second delta
312    /// (other) is used, effectively overriding the first delta.
313    pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
314        match (self, other) {
315            (ScrollDelta::Pixels(a), ScrollDelta::Pixels(b)) => {
316                let x = if a.x.signum() == b.x.signum() {
317                    a.x + b.x
318                } else {
319                    b.x
320                };
321
322                let y = if a.y.signum() == b.y.signum() {
323                    a.y + b.y
324                } else {
325                    b.y
326                };
327
328                ScrollDelta::Pixels(point(x, y))
329            }
330
331            (ScrollDelta::Lines(a), ScrollDelta::Lines(b)) => {
332                let x = if a.x.signum() == b.x.signum() {
333                    a.x + b.x
334                } else {
335                    b.x
336                };
337
338                let y = if a.y.signum() == b.y.signum() {
339                    a.y + b.y
340                } else {
341                    b.y
342                };
343
344                ScrollDelta::Lines(point(x, y))
345            }
346
347            _ => other,
348        }
349    }
350}
351
352/// A mouse exit event from the platform, generated when the mouse leaves the window.
353#[derive(Clone, Debug, Default)]
354pub struct MouseExitEvent {
355    /// The position of the mouse relative to the window.
356    pub position: Point<Pixels>,
357    /// The mouse button that was pressed, if any.
358    pub pressed_button: Option<MouseButton>,
359    /// The modifiers that were held down when the mouse was moved.
360    pub modifiers: Modifiers,
361}
362
363impl Sealed for MouseExitEvent {}
364impl InputEvent for MouseExitEvent {
365    fn to_platform_input(self) -> PlatformInput {
366        PlatformInput::MouseExited(self)
367    }
368}
369impl MouseEvent for MouseExitEvent {}
370
371impl Deref for MouseExitEvent {
372    type Target = Modifiers;
373
374    fn deref(&self) -> &Self::Target {
375        &self.modifiers
376    }
377}
378
379/// A collection of paths from the platform, such as from a file drop.
380#[derive(Debug, Clone, Default)]
381pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
382
383impl ExternalPaths {
384    /// Convert this collection of paths into a slice.
385    pub fn paths(&self) -> &[PathBuf] {
386        &self.0
387    }
388}
389
390impl Render for ExternalPaths {
391    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
392        // the platform will render icons for the dragged files
393        Empty
394    }
395}
396
397/// A file drop event from the platform, generated when files are dragged and dropped onto the window.
398#[derive(Debug, Clone)]
399pub enum FileDropEvent {
400    /// The files have entered the window.
401    Entered {
402        /// The position of the mouse relative to the window.
403        position: Point<Pixels>,
404        /// The paths of the files that are being dragged.
405        paths: ExternalPaths,
406    },
407    /// The files are being dragged over the window
408    Pending {
409        /// The position of the mouse relative to the window.
410        position: Point<Pixels>,
411    },
412    /// The files have been dropped onto the window.
413    Submit {
414        /// The position of the mouse relative to the window.
415        position: Point<Pixels>,
416    },
417    /// The user has stopped dragging the files over the window.
418    Exited,
419}
420
421impl Sealed for FileDropEvent {}
422impl InputEvent for FileDropEvent {
423    fn to_platform_input(self) -> PlatformInput {
424        PlatformInput::FileDrop(self)
425    }
426}
427impl MouseEvent for FileDropEvent {}
428
429/// An enum corresponding to all kinds of platform input events.
430#[derive(Clone, Debug)]
431pub enum PlatformInput {
432    /// A key was pressed.
433    KeyDown(KeyDownEvent),
434    /// A key was released.
435    KeyUp(KeyUpEvent),
436    /// The keyboard modifiers were changed.
437    ModifiersChanged(ModifiersChangedEvent),
438    /// The mouse was pressed.
439    MouseDown(MouseDownEvent),
440    /// The mouse was released.
441    MouseUp(MouseUpEvent),
442    /// The mouse was moved.
443    MouseMove(MouseMoveEvent),
444    /// The mouse exited the window.
445    MouseExited(MouseExitEvent),
446    /// The scroll wheel was used.
447    ScrollWheel(ScrollWheelEvent),
448    /// Files were dragged and dropped onto the window.
449    FileDrop(FileDropEvent),
450}
451
452impl PlatformInput {
453    pub(crate) fn mouse_event(&self) -> Option<&dyn Any> {
454        match self {
455            PlatformInput::KeyDown { .. } => None,
456            PlatformInput::KeyUp { .. } => None,
457            PlatformInput::ModifiersChanged { .. } => None,
458            PlatformInput::MouseDown(event) => Some(event),
459            PlatformInput::MouseUp(event) => Some(event),
460            PlatformInput::MouseMove(event) => Some(event),
461            PlatformInput::MouseExited(event) => Some(event),
462            PlatformInput::ScrollWheel(event) => Some(event),
463            PlatformInput::FileDrop(event) => Some(event),
464        }
465    }
466
467    pub(crate) fn keyboard_event(&self) -> Option<&dyn Any> {
468        match self {
469            PlatformInput::KeyDown(event) => Some(event),
470            PlatformInput::KeyUp(event) => Some(event),
471            PlatformInput::ModifiersChanged(event) => Some(event),
472            PlatformInput::MouseDown(_) => None,
473            PlatformInput::MouseUp(_) => None,
474            PlatformInput::MouseMove(_) => None,
475            PlatformInput::MouseExited(_) => None,
476            PlatformInput::ScrollWheel(_) => None,
477            PlatformInput::FileDrop(_) => None,
478        }
479    }
480}
481
482#[cfg(test)]
483mod test {
484
485    use crate::{
486        self as gpui, AppContext as _, Context, FocusHandle, InteractiveElement, IntoElement,
487        KeyBinding, Keystroke, ParentElement, Render, TestAppContext, Window, div,
488    };
489
490    struct TestView {
491        saw_key_down: bool,
492        saw_action: bool,
493        focus_handle: FocusHandle,
494    }
495
496    actions!(test_only, [TestAction]);
497
498    impl Render for TestView {
499        fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
500            div().id("testview").child(
501                div()
502                    .key_context("parent")
503                    .on_key_down(cx.listener(|this, _, _, cx| {
504                        cx.stop_propagation();
505                        this.saw_key_down = true
506                    }))
507                    .on_action(cx.listener(|this: &mut TestView, _: &TestAction, _, _| {
508                        this.saw_action = true
509                    }))
510                    .child(
511                        div()
512                            .key_context("nested")
513                            .track_focus(&self.focus_handle)
514                            .into_element(),
515                    ),
516            )
517        }
518    }
519
520    #[gpui::test]
521    fn test_on_events(cx: &mut TestAppContext) {
522        let window = cx.update(|cx| {
523            cx.open_window(Default::default(), |_, cx| {
524                cx.new(|cx| TestView {
525                    saw_key_down: false,
526                    saw_action: false,
527                    focus_handle: cx.focus_handle(),
528                })
529            })
530            .unwrap()
531        });
532
533        cx.update(|cx| {
534            cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
535        });
536
537        window
538            .update(cx, |test_view, window, _cx| {
539                window.focus(&test_view.focus_handle)
540            })
541            .unwrap();
542
543        cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap());
544        cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap());
545
546        window
547            .update(cx, |test_view, _, _| {
548                assert!(test_view.saw_key_down || test_view.saw_action);
549                assert!(test_view.saw_key_down);
550                assert!(test_view.saw_action);
551            })
552            .unwrap();
553    }
554}