interactive.rs

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