interactive.rs

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