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
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 ExternalPaths {
197    pub fn paths(&self) -> &[PathBuf] {
198        &self.0
199    }
200}
201
202impl Render for ExternalPaths {
203    type Element = Div;
204
205    fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
206        div() // Intentionally left empty because the platform will render icons for the dragged files
207    }
208}
209
210#[derive(Debug, Clone)]
211pub enum FileDropEvent {
212    Entered {
213        position: Point<Pixels>,
214        files: ExternalPaths,
215    },
216    Pending {
217        position: Point<Pixels>,
218    },
219    Submit {
220        position: Point<Pixels>,
221    },
222    Exited,
223}
224
225#[derive(Clone, Debug)]
226pub enum InputEvent {
227    KeyDown(KeyDownEvent),
228    KeyUp(KeyUpEvent),
229    ModifiersChanged(ModifiersChangedEvent),
230    MouseDown(MouseDownEvent),
231    MouseUp(MouseUpEvent),
232    MouseMove(MouseMoveEvent),
233    MouseExited(MouseExitEvent),
234    ScrollWheel(ScrollWheelEvent),
235    FileDrop(FileDropEvent),
236}
237
238impl InputEvent {
239    pub fn position(&self) -> Option<Point<Pixels>> {
240        match self {
241            InputEvent::KeyDown { .. } => None,
242            InputEvent::KeyUp { .. } => None,
243            InputEvent::ModifiersChanged { .. } => None,
244            InputEvent::MouseDown(event) => Some(event.position),
245            InputEvent::MouseUp(event) => Some(event.position),
246            InputEvent::MouseMove(event) => Some(event.position),
247            InputEvent::MouseExited(event) => Some(event.position),
248            InputEvent::ScrollWheel(event) => Some(event.position),
249            InputEvent::FileDrop(FileDropEvent::Exited) => None,
250            InputEvent::FileDrop(
251                FileDropEvent::Entered { position, .. }
252                | FileDropEvent::Pending { position, .. }
253                | FileDropEvent::Submit { position, .. },
254            ) => Some(*position),
255        }
256    }
257
258    pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
259        match self {
260            InputEvent::KeyDown { .. } => None,
261            InputEvent::KeyUp { .. } => None,
262            InputEvent::ModifiersChanged { .. } => None,
263            InputEvent::MouseDown(event) => Some(event),
264            InputEvent::MouseUp(event) => Some(event),
265            InputEvent::MouseMove(event) => Some(event),
266            InputEvent::MouseExited(event) => Some(event),
267            InputEvent::ScrollWheel(event) => Some(event),
268            InputEvent::FileDrop(event) => Some(event),
269        }
270    }
271
272    pub fn keyboard_event<'a>(&'a self) -> Option<&'a dyn Any> {
273        match self {
274            InputEvent::KeyDown(event) => Some(event),
275            InputEvent::KeyUp(event) => Some(event),
276            InputEvent::ModifiersChanged(event) => Some(event),
277            InputEvent::MouseDown(_) => None,
278            InputEvent::MouseUp(_) => None,
279            InputEvent::MouseMove(_) => None,
280            InputEvent::MouseExited(_) => None,
281            InputEvent::ScrollWheel(_) => None,
282            InputEvent::FileDrop(_) => None,
283        }
284    }
285}
286
287pub struct FocusEvent {
288    pub blurred: Option<FocusHandle>,
289    pub focused: Option<FocusHandle>,
290}
291
292#[cfg(test)]
293mod test {
294    use crate::{
295        self as gpui, div, Div, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
296        Keystroke, ParentElement, Render, Stateful, TestAppContext, VisualContext,
297    };
298
299    struct TestView {
300        saw_key_down: bool,
301        saw_action: bool,
302        focus_handle: FocusHandle,
303    }
304
305    actions!(test, [TestAction]);
306
307    impl Render for TestView {
308        type Element = Stateful<Div>;
309
310        fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
311            div().id("testview").child(
312                div()
313                    .key_context("parent")
314                    .on_key_down(cx.listener(|this, _, _| this.saw_key_down = true))
315                    .on_action(
316                        cx.listener(|this: &mut TestView, _: &TestAction, _| {
317                            this.saw_action = true
318                        }),
319                    )
320                    .child(
321                        div()
322                            .key_context("nested")
323                            .track_focus(&self.focus_handle)
324                            .into_element(),
325                    ),
326            )
327        }
328    }
329
330    #[gpui::test]
331    fn test_on_events(cx: &mut TestAppContext) {
332        let window = cx.update(|cx| {
333            cx.open_window(Default::default(), |cx| {
334                cx.build_view(|cx| TestView {
335                    saw_key_down: false,
336                    saw_action: false,
337                    focus_handle: cx.focus_handle(),
338                })
339            })
340        });
341
342        cx.update(|cx| {
343            cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
344        });
345
346        window
347            .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
348            .unwrap();
349
350        cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
351
352        window
353            .update(cx, |test_view, _| {
354                assert!(test_view.saw_key_down || test_view.saw_action);
355                assert!(test_view.saw_key_down);
356                assert!(test_view.saw_action);
357            })
358            .unwrap();
359    }
360}