interactive.rs

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