interactive.rs

  1use crate::{
  2    div, point, Div, Element, FocusHandle, IntoElement, Keystroke, Modifiers, Pixels, Point,
  3    Render, ViewContext,
  4};
  5use smallvec::SmallVec;
  6use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf};
  7
  8#[derive(Clone, Debug, Eq, PartialEq)]
  9pub struct KeyDownEvent {
 10    pub keystroke: Keystroke,
 11    pub is_held: bool,
 12}
 13
 14#[derive(Clone, Debug)]
 15pub struct KeyUpEvent {
 16    pub keystroke: Keystroke,
 17}
 18
 19#[derive(Clone, Debug, Default)]
 20pub struct ModifiersChangedEvent {
 21    pub modifiers: Modifiers,
 22}
 23
 24impl Deref for ModifiersChangedEvent {
 25    type Target = Modifiers;
 26
 27    fn deref(&self) -> &Self::Target {
 28        &self.modifiers
 29    }
 30}
 31
 32/// The phase of a touch motion event.
 33/// Based on the winit enum of the same name.
 34#[derive(Clone, Copy, Debug)]
 35pub enum TouchPhase {
 36    Started,
 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)]
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
184#[derive(Clone, Debug, Default)]
185pub struct MouseExitEvent {
186    pub position: Point<Pixels>,
187    pub pressed_button: Option<MouseButton>,
188    pub modifiers: Modifiers,
189}
190
191impl Deref for MouseExitEvent {
192    type Target = Modifiers;
193
194    fn deref(&self) -> &Self::Target {
195        &self.modifiers
196    }
197}
198
199#[derive(Debug, Clone, Default)]
200pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
201
202impl ExternalPaths {
203    pub fn paths(&self) -> &[PathBuf] {
204        &self.0
205    }
206}
207
208impl Render for ExternalPaths {
209    type Element = Div;
210
211    fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
212        div() // Intentionally left empty because the platform will render icons for the dragged files
213    }
214}
215
216#[derive(Debug, Clone)]
217pub enum FileDropEvent {
218    Entered {
219        position: Point<Pixels>,
220        files: ExternalPaths,
221    },
222    Pending {
223        position: Point<Pixels>,
224    },
225    Submit {
226        position: Point<Pixels>,
227    },
228    Exited,
229}
230
231#[derive(Clone, Debug)]
232pub enum InputEvent {
233    KeyDown(KeyDownEvent),
234    KeyUp(KeyUpEvent),
235    ModifiersChanged(ModifiersChangedEvent),
236    MouseDown(MouseDownEvent),
237    MouseUp(MouseUpEvent),
238    MouseMove(MouseMoveEvent),
239    MouseExited(MouseExitEvent),
240    ScrollWheel(ScrollWheelEvent),
241    FileDrop(FileDropEvent),
242}
243
244impl InputEvent {
245    pub fn position(&self) -> Option<Point<Pixels>> {
246        match self {
247            InputEvent::KeyDown { .. } => None,
248            InputEvent::KeyUp { .. } => None,
249            InputEvent::ModifiersChanged { .. } => None,
250            InputEvent::MouseDown(event) => Some(event.position),
251            InputEvent::MouseUp(event) => Some(event.position),
252            InputEvent::MouseMove(event) => Some(event.position),
253            InputEvent::MouseExited(event) => Some(event.position),
254            InputEvent::ScrollWheel(event) => Some(event.position),
255            InputEvent::FileDrop(FileDropEvent::Exited) => None,
256            InputEvent::FileDrop(
257                FileDropEvent::Entered { position, .. }
258                | FileDropEvent::Pending { position, .. }
259                | FileDropEvent::Submit { position, .. },
260            ) => Some(*position),
261        }
262    }
263
264    pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
265        match self {
266            InputEvent::KeyDown { .. } => None,
267            InputEvent::KeyUp { .. } => None,
268            InputEvent::ModifiersChanged { .. } => None,
269            InputEvent::MouseDown(event) => Some(event),
270            InputEvent::MouseUp(event) => Some(event),
271            InputEvent::MouseMove(event) => Some(event),
272            InputEvent::MouseExited(event) => Some(event),
273            InputEvent::ScrollWheel(event) => Some(event),
274            InputEvent::FileDrop(event) => Some(event),
275        }
276    }
277
278    pub fn keyboard_event<'a>(&'a self) -> Option<&'a dyn Any> {
279        match self {
280            InputEvent::KeyDown(event) => Some(event),
281            InputEvent::KeyUp(event) => Some(event),
282            InputEvent::ModifiersChanged(event) => Some(event),
283            InputEvent::MouseDown(_) => None,
284            InputEvent::MouseUp(_) => None,
285            InputEvent::MouseMove(_) => None,
286            InputEvent::MouseExited(_) => None,
287            InputEvent::ScrollWheel(_) => None,
288            InputEvent::FileDrop(_) => None,
289        }
290    }
291}
292
293pub struct FocusEvent {
294    pub blurred: Option<FocusHandle>,
295    pub focused: Option<FocusHandle>,
296}
297
298#[cfg(test)]
299mod test {
300    use crate::{
301        self as gpui, div, Div, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
302        Keystroke, ParentElement, Render, Stateful, TestAppContext, VisualContext,
303    };
304
305    struct TestView {
306        saw_key_down: bool,
307        saw_action: bool,
308        focus_handle: FocusHandle,
309    }
310
311    actions!(test, [TestAction]);
312
313    impl Render for TestView {
314        type Element = Stateful<Div>;
315
316        fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
317            div().id("testview").child(
318                div()
319                    .key_context("parent")
320                    .on_key_down(cx.listener(|this, _, _| this.saw_key_down = true))
321                    .on_action(
322                        cx.listener(|this: &mut TestView, _: &TestAction, _| {
323                            this.saw_action = true
324                        }),
325                    )
326                    .child(
327                        div()
328                            .key_context("nested")
329                            .track_focus(&self.focus_handle)
330                            .into_element(),
331                    ),
332            )
333        }
334    }
335
336    #[gpui::test]
337    fn test_on_events(cx: &mut TestAppContext) {
338        let window = cx.update(|cx| {
339            cx.open_window(Default::default(), |cx| {
340                cx.build_view(|cx| TestView {
341                    saw_key_down: false,
342                    saw_action: false,
343                    focus_handle: cx.focus_handle(),
344                })
345            })
346        });
347
348        cx.update(|cx| {
349            cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
350        });
351
352        window
353            .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
354            .unwrap();
355
356        cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
357
358        window
359            .update(cx, |test_view, _| {
360                assert!(test_view.saw_key_down || test_view.saw_action);
361                assert!(test_view.saw_key_down);
362                assert!(test_view.saw_action);
363            })
364            .unwrap();
365    }
366}