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
  7pub trait InputEvent: Sealed + 'static {
  8    fn to_platform_input(self) -> PlatformInput;
  9}
 10pub trait KeyEvent: InputEvent {}
 11pub trait MouseEvent: InputEvent {}
 12
 13#[derive(Clone, Debug, Eq, PartialEq)]
 14pub struct KeyDownEvent {
 15    pub keystroke: Keystroke,
 16    pub is_held: bool,
 17}
 18
 19impl Sealed for KeyDownEvent {}
 20impl InputEvent for KeyDownEvent {
 21    fn to_platform_input(self) -> PlatformInput {
 22        PlatformInput::KeyDown(self)
 23    }
 24}
 25impl KeyEvent for KeyDownEvent {}
 26
 27#[derive(Clone, Debug)]
 28pub struct KeyUpEvent {
 29    pub keystroke: Keystroke,
 30}
 31
 32impl Sealed for KeyUpEvent {}
 33impl InputEvent for KeyUpEvent {
 34    fn to_platform_input(self) -> PlatformInput {
 35        PlatformInput::KeyUp(self)
 36    }
 37}
 38impl KeyEvent for KeyUpEvent {}
 39
 40#[derive(Clone, Debug, Default)]
 41pub struct ModifiersChangedEvent {
 42    pub modifiers: Modifiers,
 43}
 44
 45impl Sealed for ModifiersChangedEvent {}
 46impl InputEvent for ModifiersChangedEvent {
 47    fn to_platform_input(self) -> PlatformInput {
 48        PlatformInput::ModifiersChanged(self)
 49    }
 50}
 51impl KeyEvent for ModifiersChangedEvent {}
 52
 53impl Deref for ModifiersChangedEvent {
 54    type Target = Modifiers;
 55
 56    fn deref(&self) -> &Self::Target {
 57        &self.modifiers
 58    }
 59}
 60
 61/// The phase of a touch motion event.
 62/// Based on the winit enum of the same name.
 63#[derive(Clone, Copy, Debug, Default)]
 64pub enum TouchPhase {
 65    Started,
 66    #[default]
 67    Moved,
 68    Ended,
 69}
 70
 71#[derive(Clone, Debug, Default)]
 72pub struct MouseDownEvent {
 73    pub button: MouseButton,
 74    pub position: Point<Pixels>,
 75    pub modifiers: Modifiers,
 76    pub click_count: usize,
 77}
 78
 79impl Sealed for MouseDownEvent {}
 80impl InputEvent for MouseDownEvent {
 81    fn to_platform_input(self) -> PlatformInput {
 82        PlatformInput::MouseDown(self)
 83    }
 84}
 85impl MouseEvent for MouseDownEvent {}
 86
 87#[derive(Clone, Debug, Default)]
 88pub struct MouseUpEvent {
 89    pub button: MouseButton,
 90    pub position: Point<Pixels>,
 91    pub modifiers: Modifiers,
 92    pub click_count: usize,
 93}
 94
 95impl Sealed for MouseUpEvent {}
 96impl InputEvent for MouseUpEvent {
 97    fn to_platform_input(self) -> PlatformInput {
 98        PlatformInput::MouseUp(self)
 99    }
100}
101impl MouseEvent for MouseUpEvent {}
102
103#[derive(Clone, Debug, Default)]
104pub struct ClickEvent {
105    pub down: MouseDownEvent,
106    pub up: MouseUpEvent,
107}
108
109#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
110pub enum MouseButton {
111    Left,
112    Right,
113    Middle,
114    Navigate(NavigationDirection),
115}
116
117impl MouseButton {
118    pub fn all() -> Vec<Self> {
119        vec![
120            MouseButton::Left,
121            MouseButton::Right,
122            MouseButton::Middle,
123            MouseButton::Navigate(NavigationDirection::Back),
124            MouseButton::Navigate(NavigationDirection::Forward),
125        ]
126    }
127}
128
129impl Default for MouseButton {
130    fn default() -> Self {
131        Self::Left
132    }
133}
134
135#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
136pub enum NavigationDirection {
137    Back,
138    Forward,
139}
140
141impl Default for NavigationDirection {
142    fn default() -> Self {
143        Self::Back
144    }
145}
146
147#[derive(Clone, Debug, Default)]
148pub struct MouseMoveEvent {
149    pub position: Point<Pixels>,
150    pub pressed_button: Option<MouseButton>,
151    pub modifiers: Modifiers,
152}
153
154impl Sealed for MouseMoveEvent {}
155impl InputEvent for MouseMoveEvent {
156    fn to_platform_input(self) -> PlatformInput {
157        PlatformInput::MouseMove(self)
158    }
159}
160impl MouseEvent for MouseMoveEvent {}
161
162impl MouseMoveEvent {
163    pub fn dragging(&self) -> bool {
164        self.pressed_button == Some(MouseButton::Left)
165    }
166}
167
168#[derive(Clone, Debug, Default)]
169pub struct ScrollWheelEvent {
170    pub position: Point<Pixels>,
171    pub delta: ScrollDelta,
172    pub modifiers: Modifiers,
173    pub touch_phase: TouchPhase,
174}
175
176impl Sealed for ScrollWheelEvent {}
177impl InputEvent for ScrollWheelEvent {
178    fn to_platform_input(self) -> PlatformInput {
179        PlatformInput::ScrollWheel(self)
180    }
181}
182impl MouseEvent for ScrollWheelEvent {}
183
184impl Deref for ScrollWheelEvent {
185    type Target = Modifiers;
186
187    fn deref(&self) -> &Self::Target {
188        &self.modifiers
189    }
190}
191
192#[derive(Clone, Copy, Debug)]
193pub enum ScrollDelta {
194    Pixels(Point<Pixels>),
195    Lines(Point<f32>),
196}
197
198impl Default for ScrollDelta {
199    fn default() -> Self {
200        Self::Lines(Default::default())
201    }
202}
203
204impl ScrollDelta {
205    pub fn precise(&self) -> bool {
206        match self {
207            ScrollDelta::Pixels(_) => true,
208            ScrollDelta::Lines(_) => false,
209        }
210    }
211
212    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
213        match self {
214            ScrollDelta::Pixels(delta) => *delta,
215            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
216        }
217    }
218
219    pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
220        match (self, other) {
221            (ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
222                ScrollDelta::Pixels(px_a + px_b)
223            }
224
225            (ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => {
226                ScrollDelta::Lines(lines_a + lines_b)
227            }
228
229            _ => other,
230        }
231    }
232}
233
234#[derive(Clone, Debug, Default)]
235pub struct MouseExitEvent {
236    pub position: Point<Pixels>,
237    pub pressed_button: Option<MouseButton>,
238    pub modifiers: Modifiers,
239}
240
241impl Sealed for MouseExitEvent {}
242impl InputEvent for MouseExitEvent {
243    fn to_platform_input(self) -> PlatformInput {
244        PlatformInput::MouseExited(self)
245    }
246}
247impl MouseEvent for MouseExitEvent {}
248
249impl Deref for MouseExitEvent {
250    type Target = Modifiers;
251
252    fn deref(&self) -> &Self::Target {
253        &self.modifiers
254    }
255}
256
257#[derive(Debug, Clone, Default)]
258pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
259
260impl ExternalPaths {
261    pub fn paths(&self) -> &[PathBuf] {
262        &self.0
263    }
264}
265
266impl Render for ExternalPaths {
267    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
268        () // Intentionally left empty because the platform will render icons for the dragged files
269    }
270}
271
272#[derive(Debug, Clone)]
273pub enum FileDropEvent {
274    Entered {
275        position: Point<Pixels>,
276        paths: ExternalPaths,
277    },
278    Pending {
279        position: Point<Pixels>,
280    },
281    Submit {
282        position: Point<Pixels>,
283    },
284    Exited,
285}
286
287impl Sealed for FileDropEvent {}
288impl InputEvent for FileDropEvent {
289    fn to_platform_input(self) -> PlatformInput {
290        PlatformInput::FileDrop(self)
291    }
292}
293impl MouseEvent for FileDropEvent {}
294
295#[derive(Clone, Debug)]
296pub enum PlatformInput {
297    KeyDown(KeyDownEvent),
298    KeyUp(KeyUpEvent),
299    ModifiersChanged(ModifiersChangedEvent),
300    MouseDown(MouseDownEvent),
301    MouseUp(MouseUpEvent),
302    MouseMove(MouseMoveEvent),
303    MouseExited(MouseExitEvent),
304    ScrollWheel(ScrollWheelEvent),
305    FileDrop(FileDropEvent),
306}
307
308impl PlatformInput {
309    pub fn position(&self) -> Option<Point<Pixels>> {
310        match self {
311            PlatformInput::KeyDown { .. } => None,
312            PlatformInput::KeyUp { .. } => None,
313            PlatformInput::ModifiersChanged { .. } => None,
314            PlatformInput::MouseDown(event) => Some(event.position),
315            PlatformInput::MouseUp(event) => Some(event.position),
316            PlatformInput::MouseMove(event) => Some(event.position),
317            PlatformInput::MouseExited(event) => Some(event.position),
318            PlatformInput::ScrollWheel(event) => Some(event.position),
319            PlatformInput::FileDrop(FileDropEvent::Exited) => None,
320            PlatformInput::FileDrop(
321                FileDropEvent::Entered { position, .. }
322                | FileDropEvent::Pending { position, .. }
323                | FileDropEvent::Submit { position, .. },
324            ) => Some(*position),
325        }
326    }
327
328    pub fn mouse_event(&self) -> Option<&dyn Any> {
329        match self {
330            PlatformInput::KeyDown { .. } => None,
331            PlatformInput::KeyUp { .. } => None,
332            PlatformInput::ModifiersChanged { .. } => None,
333            PlatformInput::MouseDown(event) => Some(event),
334            PlatformInput::MouseUp(event) => Some(event),
335            PlatformInput::MouseMove(event) => Some(event),
336            PlatformInput::MouseExited(event) => Some(event),
337            PlatformInput::ScrollWheel(event) => Some(event),
338            PlatformInput::FileDrop(event) => Some(event),
339        }
340    }
341
342    pub fn keyboard_event(&self) -> Option<&dyn Any> {
343        match self {
344            PlatformInput::KeyDown(event) => Some(event),
345            PlatformInput::KeyUp(event) => Some(event),
346            PlatformInput::ModifiersChanged(event) => Some(event),
347            PlatformInput::MouseDown(_) => None,
348            PlatformInput::MouseUp(_) => None,
349            PlatformInput::MouseMove(_) => None,
350            PlatformInput::MouseExited(_) => None,
351            PlatformInput::ScrollWheel(_) => None,
352            PlatformInput::FileDrop(_) => None,
353        }
354    }
355}
356
357#[cfg(test)]
358mod test {
359    use crate::{
360        self as gpui, div, Element, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
361        Keystroke, ParentElement, Render, TestAppContext, VisualContext,
362    };
363
364    struct TestView {
365        saw_key_down: bool,
366        saw_action: bool,
367        focus_handle: FocusHandle,
368    }
369
370    actions!(test, [TestAction]);
371
372    impl Render for TestView {
373        fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl Element {
374            div().id("testview").child(
375                div()
376                    .key_context("parent")
377                    .on_key_down(cx.listener(|this, _, cx| {
378                        cx.stop_propagation();
379                        this.saw_key_down = true
380                    }))
381                    .on_action(
382                        cx.listener(|this: &mut TestView, _: &TestAction, _| {
383                            this.saw_action = true
384                        }),
385                    )
386                    .child(
387                        div()
388                            .key_context("nested")
389                            .track_focus(&self.focus_handle)
390                            .into_element(),
391                    ),
392            )
393        }
394    }
395
396    #[gpui::test]
397    fn test_on_events(cx: &mut TestAppContext) {
398        let window = cx.update(|cx| {
399            cx.open_window(Default::default(), |cx| {
400                cx.new_view(|cx| TestView {
401                    saw_key_down: false,
402                    saw_action: false,
403                    focus_handle: cx.focus_handle(),
404                })
405            })
406        });
407
408        cx.update(|cx| {
409            cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
410        });
411
412        window
413            .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
414            .unwrap();
415
416        cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap(), false);
417        cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
418
419        window
420            .update(cx, |test_view, _| {
421                assert!(test_view.saw_key_down || test_view.saw_action);
422                assert!(test_view.saw_key_down);
423                assert!(test_view.saw_action);
424            })
425            .unwrap();
426    }
427}