interactive.rs

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