interactive.rs

  1use crate::{
  2    div, point, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
  3};
  4use smallvec::SmallVec;
  5use std::{any::Any, fmt::Debug, 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
 62#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
 63pub enum MouseButton {
 64    Left,
 65    Right,
 66    Middle,
 67    Navigate(NavigationDirection),
 68}
 69
 70impl MouseButton {
 71    pub fn all() -> Vec<Self> {
 72        vec![
 73            MouseButton::Left,
 74            MouseButton::Right,
 75            MouseButton::Middle,
 76            MouseButton::Navigate(NavigationDirection::Back),
 77            MouseButton::Navigate(NavigationDirection::Forward),
 78        ]
 79    }
 80}
 81
 82impl Default for MouseButton {
 83    fn default() -> Self {
 84        Self::Left
 85    }
 86}
 87
 88#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
 89pub enum NavigationDirection {
 90    Back,
 91    Forward,
 92}
 93
 94impl Default for NavigationDirection {
 95    fn default() -> Self {
 96        Self::Back
 97    }
 98}
 99
100#[derive(Clone, Debug, Default)]
101pub struct MouseMoveEvent {
102    pub position: Point<Pixels>,
103    pub pressed_button: Option<MouseButton>,
104    pub modifiers: Modifiers,
105}
106
107#[derive(Clone, Debug)]
108pub struct ScrollWheelEvent {
109    pub position: Point<Pixels>,
110    pub delta: ScrollDelta,
111    pub modifiers: Modifiers,
112    pub touch_phase: TouchPhase,
113}
114
115impl Deref for ScrollWheelEvent {
116    type Target = Modifiers;
117
118    fn deref(&self) -> &Self::Target {
119        &self.modifiers
120    }
121}
122
123#[derive(Clone, Copy, Debug)]
124pub enum ScrollDelta {
125    Pixels(Point<Pixels>),
126    Lines(Point<f32>),
127}
128
129impl Default for ScrollDelta {
130    fn default() -> Self {
131        Self::Lines(Default::default())
132    }
133}
134
135impl ScrollDelta {
136    pub fn precise(&self) -> bool {
137        match self {
138            ScrollDelta::Pixels(_) => true,
139            ScrollDelta::Lines(_) => false,
140        }
141    }
142
143    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
144        match self {
145            ScrollDelta::Pixels(delta) => *delta,
146            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
147        }
148    }
149}
150
151#[derive(Clone, Debug, Default)]
152pub struct MouseExitEvent {
153    pub position: Point<Pixels>,
154    pub pressed_button: Option<MouseButton>,
155    pub modifiers: Modifiers,
156}
157
158impl Deref for MouseExitEvent {
159    type Target = Modifiers;
160
161    fn deref(&self) -> &Self::Target {
162        &self.modifiers
163    }
164}
165
166#[derive(Debug, Clone, Default)]
167pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
168
169impl Render for ExternalPaths {
170    type Element = Div;
171
172    fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
173        div() // Intentionally left empty because the platform will render icons for the dragged files
174    }
175}
176
177#[derive(Debug, Clone)]
178pub enum FileDropEvent {
179    Entered {
180        position: Point<Pixels>,
181        files: ExternalPaths,
182    },
183    Pending {
184        position: Point<Pixels>,
185    },
186    Submit {
187        position: Point<Pixels>,
188    },
189    Exited,
190}
191
192#[derive(Clone, Debug)]
193pub enum InputEvent {
194    KeyDown(KeyDownEvent),
195    KeyUp(KeyUpEvent),
196    ModifiersChanged(ModifiersChangedEvent),
197    MouseDown(MouseDownEvent),
198    MouseUp(MouseUpEvent),
199    MouseMove(MouseMoveEvent),
200    MouseExited(MouseExitEvent),
201    ScrollWheel(ScrollWheelEvent),
202    FileDrop(FileDropEvent),
203}
204
205impl InputEvent {
206    pub fn position(&self) -> Option<Point<Pixels>> {
207        match self {
208            InputEvent::KeyDown { .. } => None,
209            InputEvent::KeyUp { .. } => None,
210            InputEvent::ModifiersChanged { .. } => None,
211            InputEvent::MouseDown(event) => Some(event.position),
212            InputEvent::MouseUp(event) => Some(event.position),
213            InputEvent::MouseMove(event) => Some(event.position),
214            InputEvent::MouseExited(event) => Some(event.position),
215            InputEvent::ScrollWheel(event) => Some(event.position),
216            InputEvent::FileDrop(FileDropEvent::Exited) => None,
217            InputEvent::FileDrop(
218                FileDropEvent::Entered { position, .. }
219                | FileDropEvent::Pending { position, .. }
220                | FileDropEvent::Submit { position, .. },
221            ) => Some(*position),
222        }
223    }
224
225    pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
226        match self {
227            InputEvent::KeyDown { .. } => None,
228            InputEvent::KeyUp { .. } => None,
229            InputEvent::ModifiersChanged { .. } => None,
230            InputEvent::MouseDown(event) => Some(event),
231            InputEvent::MouseUp(event) => Some(event),
232            InputEvent::MouseMove(event) => Some(event),
233            InputEvent::MouseExited(event) => Some(event),
234            InputEvent::ScrollWheel(event) => Some(event),
235            InputEvent::FileDrop(event) => Some(event),
236        }
237    }
238
239    pub fn keyboard_event<'a>(&'a self) -> Option<&'a dyn Any> {
240        match self {
241            InputEvent::KeyDown(event) => Some(event),
242            InputEvent::KeyUp(event) => Some(event),
243            InputEvent::ModifiersChanged(event) => Some(event),
244            InputEvent::MouseDown(_) => None,
245            InputEvent::MouseUp(_) => None,
246            InputEvent::MouseMove(_) => None,
247            InputEvent::MouseExited(_) => None,
248            InputEvent::ScrollWheel(_) => None,
249            InputEvent::FileDrop(_) => None,
250        }
251    }
252}
253
254pub struct FocusEvent {
255    pub blurred: Option<FocusHandle>,
256    pub focused: Option<FocusHandle>,
257}
258
259#[cfg(test)]
260mod test {
261    use crate::{
262        self as gpui, div, Component, Div, FocusHandle, InteractiveComponent, KeyBinding,
263        Keystroke, ParentComponent, Render, Stateful, TestAppContext, VisualContext,
264    };
265
266    struct TestView {
267        saw_key_down: bool,
268        saw_action: bool,
269        focus_handle: FocusHandle,
270    }
271
272    actions!(TestAction);
273
274    impl Render for TestView {
275        type Element = Stateful<Div>;
276
277        fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
278            div().id("testview").child(
279                div()
280                    .key_context("parent")
281                    .on_key_down(cx.callback(|this, _, _| this.saw_key_down = true))
282                    .on_action(
283                        cx.callback(|this: &mut TestView, _: &TestAction, _| {
284                            this.saw_action = true
285                        }),
286                    )
287                    .child(
288                        div()
289                            .key_context("nested")
290                            .track_focus(&self.focus_handle)
291                            .render(),
292                    ),
293            )
294        }
295    }
296
297    #[gpui::test]
298    fn test_on_events(cx: &mut TestAppContext) {
299        let window = cx.update(|cx| {
300            cx.open_window(Default::default(), |cx| {
301                cx.build_view(|cx| TestView {
302                    saw_key_down: false,
303                    saw_action: false,
304                    focus_handle: cx.focus_handle(),
305                })
306            })
307        });
308
309        cx.update(|cx| {
310            cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
311        });
312
313        window
314            .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
315            .unwrap();
316
317        cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
318
319        window
320            .update(cx, |test_view, _| {
321                assert!(test_view.saw_key_down || test_view.saw_action);
322                assert!(test_view.saw_key_down);
323                assert!(test_view.saw_action);
324            })
325            .unwrap();
326    }
327}