interactive.rs

  1use crate::{
  2    div, point, Div, Element, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, RenderOnce,
  3    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: RenderOnce,
 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
134#[derive(Clone, Debug)]
135pub struct ScrollWheelEvent {
136    pub position: Point<Pixels>,
137    pub delta: ScrollDelta,
138    pub modifiers: Modifiers,
139    pub touch_phase: TouchPhase,
140}
141
142impl Deref for ScrollWheelEvent {
143    type Target = Modifiers;
144
145    fn deref(&self) -> &Self::Target {
146        &self.modifiers
147    }
148}
149
150#[derive(Clone, Copy, Debug)]
151pub enum ScrollDelta {
152    Pixels(Point<Pixels>),
153    Lines(Point<f32>),
154}
155
156impl Default for ScrollDelta {
157    fn default() -> Self {
158        Self::Lines(Default::default())
159    }
160}
161
162impl ScrollDelta {
163    pub fn precise(&self) -> bool {
164        match self {
165            ScrollDelta::Pixels(_) => true,
166            ScrollDelta::Lines(_) => false,
167        }
168    }
169
170    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
171        match self {
172            ScrollDelta::Pixels(delta) => *delta,
173            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
174        }
175    }
176}
177
178#[derive(Clone, Debug, Default)]
179pub struct MouseExitEvent {
180    pub position: Point<Pixels>,
181    pub pressed_button: Option<MouseButton>,
182    pub modifiers: Modifiers,
183}
184
185impl Deref for MouseExitEvent {
186    type Target = Modifiers;
187
188    fn deref(&self) -> &Self::Target {
189        &self.modifiers
190    }
191}
192
193#[derive(Debug, Clone, Default)]
194pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
195
196impl Render for ExternalPaths {
197    type Element = Div;
198
199    fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
200        div() // Intentionally left empty because the platform will render icons for the dragged files
201    }
202}
203
204#[derive(Debug, Clone)]
205pub enum FileDropEvent {
206    Entered {
207        position: Point<Pixels>,
208        files: ExternalPaths,
209    },
210    Pending {
211        position: Point<Pixels>,
212    },
213    Submit {
214        position: Point<Pixels>,
215    },
216    Exited,
217}
218
219#[derive(Clone, Debug)]
220pub enum InputEvent {
221    KeyDown(KeyDownEvent),
222    KeyUp(KeyUpEvent),
223    ModifiersChanged(ModifiersChangedEvent),
224    MouseDown(MouseDownEvent),
225    MouseUp(MouseUpEvent),
226    MouseMove(MouseMoveEvent),
227    MouseExited(MouseExitEvent),
228    ScrollWheel(ScrollWheelEvent),
229    FileDrop(FileDropEvent),
230}
231
232impl InputEvent {
233    pub fn position(&self) -> Option<Point<Pixels>> {
234        match self {
235            InputEvent::KeyDown { .. } => None,
236            InputEvent::KeyUp { .. } => None,
237            InputEvent::ModifiersChanged { .. } => None,
238            InputEvent::MouseDown(event) => Some(event.position),
239            InputEvent::MouseUp(event) => Some(event.position),
240            InputEvent::MouseMove(event) => Some(event.position),
241            InputEvent::MouseExited(event) => Some(event.position),
242            InputEvent::ScrollWheel(event) => Some(event.position),
243            InputEvent::FileDrop(FileDropEvent::Exited) => None,
244            InputEvent::FileDrop(
245                FileDropEvent::Entered { position, .. }
246                | FileDropEvent::Pending { position, .. }
247                | FileDropEvent::Submit { position, .. },
248            ) => Some(*position),
249        }
250    }
251
252    pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
253        match self {
254            InputEvent::KeyDown { .. } => None,
255            InputEvent::KeyUp { .. } => None,
256            InputEvent::ModifiersChanged { .. } => None,
257            InputEvent::MouseDown(event) => Some(event),
258            InputEvent::MouseUp(event) => Some(event),
259            InputEvent::MouseMove(event) => Some(event),
260            InputEvent::MouseExited(event) => Some(event),
261            InputEvent::ScrollWheel(event) => Some(event),
262            InputEvent::FileDrop(event) => Some(event),
263        }
264    }
265
266    pub fn keyboard_event<'a>(&'a self) -> Option<&'a dyn Any> {
267        match self {
268            InputEvent::KeyDown(event) => Some(event),
269            InputEvent::KeyUp(event) => Some(event),
270            InputEvent::ModifiersChanged(event) => Some(event),
271            InputEvent::MouseDown(_) => None,
272            InputEvent::MouseUp(_) => None,
273            InputEvent::MouseMove(_) => None,
274            InputEvent::MouseExited(_) => None,
275            InputEvent::ScrollWheel(_) => None,
276            InputEvent::FileDrop(_) => None,
277        }
278    }
279}
280
281pub struct FocusEvent {
282    pub blurred: Option<FocusHandle>,
283    pub focused: Option<FocusHandle>,
284}
285
286#[cfg(test)]
287mod test {
288    use crate::{
289        self as gpui, div, Div, FocusHandle, InteractiveElement, KeyBinding, Keystroke,
290        ParentElement, Render, RenderOnce, Stateful, TestAppContext, VisualContext,
291    };
292
293    struct TestView {
294        saw_key_down: bool,
295        saw_action: bool,
296        focus_handle: FocusHandle,
297    }
298
299    actions!(TestAction);
300
301    impl Render for TestView {
302        type Element = Stateful<Div>;
303
304        fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
305            div().id("testview").child(
306                div()
307                    .key_context("parent")
308                    .on_key_down(cx.listener(|this, _, _| this.saw_key_down = true))
309                    .on_action(
310                        cx.listener(|this: &mut TestView, _: &TestAction, _| {
311                            this.saw_action = true
312                        }),
313                    )
314                    .child(
315                        div()
316                            .key_context("nested")
317                            .track_focus(&self.focus_handle)
318                            .render_once(),
319                    ),
320            )
321        }
322    }
323
324    #[gpui::test]
325    fn test_on_events(cx: &mut TestAppContext) {
326        let window = cx.update(|cx| {
327            cx.open_window(Default::default(), |cx| {
328                cx.build_view(|cx| TestView {
329                    saw_key_down: false,
330                    saw_action: false,
331                    focus_handle: cx.focus_handle(),
332                })
333            })
334        });
335
336        cx.update(|cx| {
337            cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
338        });
339
340        window
341            .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
342            .unwrap();
343
344        cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
345
346        window
347            .update(cx, |test_view, _| {
348                assert!(test_view.saw_key_down || test_view.saw_action);
349                assert!(test_view.saw_key_down);
350                assert!(test_view.saw_action);
351            })
352            .unwrap();
353    }
354}