Skip to main content

gpui_platform/
input.rs

1//! Platform input event vocabulary shared by `gpui` and its platform backends.
2
3use crate::ExternalPaths;
4use gpui_types::{
5    Capslock, KeyDownEvent, KeyUpEvent, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent,
6    Pixels, Point, point,
7};
8use std::{any::Any, ops::Deref};
9
10/// A direct touch drag claimed by an element before touch input becomes a tap,
11/// long press, or scrolling gesture.
12#[derive(Clone, Debug)]
13pub struct TouchDragEvent {
14    /// The phase of the touch drag.
15    pub phase: TouchPhase,
16    /// The position where the touch started.
17    pub start_position: Point<Pixels>,
18    /// The touch's current position.
19    pub position: Point<Pixels>,
20}
21
22/// A phased long-press gesture recognized from a touch.
23#[derive(Clone, Debug)]
24pub struct LongPressEvent {
25    /// The phase of the long press.
26    pub phase: TouchPhase,
27    /// The position where the touch started.
28    pub start_position: Point<Pixels>,
29    /// The touch's current position.
30    pub position: Point<Pixels>,
31}
32
33impl Default for LongPressEvent {
34    fn default() -> Self {
35        Self {
36            phase: TouchPhase::Started,
37            start_position: Point::default(),
38            position: Point::default(),
39        }
40    }
41}
42
43/// The modifiers changed event equivalent for the platform.
44#[derive(Clone, Debug, Default)]
45pub struct ModifiersChangedEvent {
46    /// The new state of the modifier keys
47    pub modifiers: Modifiers,
48    /// The new state of the capslock key
49    pub capslock: Capslock,
50}
51
52impl Deref for ModifiersChangedEvent {
53    type Target = Modifiers;
54
55    fn deref(&self) -> &Self::Target {
56        &self.modifiers
57    }
58}
59
60/// The phase of a touch motion event.
61/// Based on the winit enum of the same name.
62#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
63pub enum TouchPhase {
64    /// The touch started.
65    Started,
66    /// The touch event is moving.
67    #[default]
68    Moved,
69    /// The touch phase has ended
70    Ended,
71    /// The touch was cancelled: the system took it and it will not end
72    /// normally. Consumers must fully unwind any in-progress interaction,
73    /// treating the touch as if it never committed.
74    Cancelled,
75}
76
77/// Identifies one touch (finger or stylus contact) for its lifetime, from
78/// [`TouchPhase::Started`] through [`TouchPhase::Ended`] or
79/// [`TouchPhase::Cancelled`].
80///
81/// The value is opaque and assigned by the platform. A platform window must
82/// not reuse an identifier for a later touch.
83#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
84pub struct TouchId(pub u64);
85
86/// A raw touch event from the platform.
87///
88///
89/// Dispatch contract (core implementation pending): a touch is hit-tested
90/// once, at [`TouchPhase::Started`], occlusion-aware; all subsequent events
91/// for the same [`TouchId`] are delivered to the elements under the starting
92/// position, even after the touch moves outside them.
93#[derive(Clone, Debug, Default)]
94pub struct TouchEvent {
95    /// Which touch this event belongs to.
96    pub id: TouchId,
97    /// The phase of the touch.
98    pub phase: TouchPhase,
99    /// The position of the touch in window coordinates.
100    pub position: Point<Pixels>,
101    /// Where the platform predicts the touch will be roughly one frame from
102    /// now, in the same coordinate space as `position`, when the platform
103    /// offers a prediction for a [`TouchPhase::Moved`] event.
104    ///
105    /// Best-effort latency compensation only: it may influence how far a
106    /// recognized pan scrolls within a frame, but never hit testing, gesture
107    /// classification, or velocity estimation, and any error it introduces
108    /// must be corrected by later events for the same touch.
109    pub predicted_position: Option<Point<Pixels>>,
110    /// Normalized touch force in `0.0..=1.0`, if the hardware reports it.
111    pub force: Option<f32>,
112}
113
114/// A mouse up event from the platform
115#[derive(Clone, Debug, Default)]
116pub struct MouseUpEvent {
117    /// Which mouse button was released.
118    pub button: MouseButton,
119
120    /// The position of the mouse on the window.
121    pub position: Point<Pixels>,
122
123    /// The modifiers that were held down when the mouse was released.
124    pub modifiers: Modifiers,
125
126    /// The number of times the button has been clicked.
127    pub click_count: usize,
128}
129
130impl MouseUpEvent {
131    /// Returns true if this mouse up event should focus the element.
132    pub fn is_focusing(&self) -> bool {
133        matches!(self.button, MouseButton::Left)
134    }
135}
136
137/// The stage of a pressure click event.
138#[derive(Clone, Copy, Debug, Default, PartialEq)]
139pub enum PressureStage {
140    /// No pressure.
141    #[default]
142    Zero,
143    /// Normal click pressure.
144    Normal,
145    /// High pressure, enough to trigger a force click.
146    Force,
147}
148
149/// A mouse pressure event from the platform. Generated when a force-sensitive trackpad is pressed hard.
150/// Currently only implemented for macOS trackpads.
151#[derive(Debug, Clone, Default)]
152pub struct MousePressureEvent {
153    /// Pressure of the current stage as a float between 0 and 1
154    pub pressure: f32,
155    /// The pressure stage of the event.
156    pub stage: PressureStage,
157    /// The position of the mouse on the window.
158    pub position: Point<Pixels>,
159    /// The modifiers that were held down when the mouse pressure changed.
160    pub modifiers: Modifiers,
161}
162
163/// A mouse wheel event from the platform.
164#[derive(Clone, Debug, Default)]
165pub struct ScrollWheelEvent {
166    /// The position of the mouse on the window.
167    pub position: Point<Pixels>,
168
169    /// The change in scroll wheel position for this event.
170    pub delta: ScrollDelta,
171
172    /// The modifiers that were held down when the mouse was moved.
173    pub modifiers: Modifiers,
174
175    /// The phase of the touch event.
176    pub touch_phase: TouchPhase,
177}
178
179impl Deref for ScrollWheelEvent {
180    type Target = Modifiers;
181
182    fn deref(&self) -> &Self::Target {
183        &self.modifiers
184    }
185}
186
187/// The scroll delta for a scroll wheel event.
188#[derive(Clone, Copy, Debug)]
189pub enum ScrollDelta {
190    /// An exact scroll delta in pixels.
191    Pixels(Point<Pixels>),
192    /// An inexact scroll delta in lines.
193    Lines(Point<f32>),
194}
195
196impl Default for ScrollDelta {
197    fn default() -> Self {
198        Self::Lines(Default::default())
199    }
200}
201
202impl ScrollDelta {
203    /// Returns true if this is a precise scroll delta in pixels.
204    pub fn precise(&self) -> bool {
205        match self {
206            ScrollDelta::Pixels(_) => true,
207            ScrollDelta::Lines(_) => false,
208        }
209    }
210
211    /// Converts this scroll event into exact pixels.
212    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
213        match self {
214            ScrollDelta::Pixels(delta) => *delta,
215            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
216        }
217    }
218
219    /// Combines two scroll deltas into one.
220    /// If the signs of the deltas are the same (both positive or both negative),
221    /// the deltas are added together. If the signs are opposite, the second delta
222    /// (other) is used, effectively overriding the first delta.
223    pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
224        match (self, other) {
225            (ScrollDelta::Pixels(a), ScrollDelta::Pixels(b)) => {
226                let x = if a.x.signum() == b.x.signum() {
227                    a.x + b.x
228                } else {
229                    b.x
230                };
231
232                let y = if a.y.signum() == b.y.signum() {
233                    a.y + b.y
234                } else {
235                    b.y
236                };
237
238                ScrollDelta::Pixels(point(x, y))
239            }
240
241            (ScrollDelta::Lines(a), ScrollDelta::Lines(b)) => {
242                let x = if a.x.signum() == b.x.signum() {
243                    a.x + b.x
244                } else {
245                    b.x
246                };
247
248                let y = if a.y.signum() == b.y.signum() {
249                    a.y + b.y
250                } else {
251                    b.y
252                };
253
254                ScrollDelta::Lines(point(x, y))
255            }
256
257            _ => other,
258        }
259    }
260}
261
262/// A pinch gesture event from the platform, generated when the user performs
263/// a pinch-to-zoom gesture (typically on a trackpad).
264#[derive(Clone, Debug, Default)]
265pub struct PinchEvent {
266    /// The position of the pinch center on the window.
267    pub position: Point<Pixels>,
268
269    /// The zoom delta for this event.
270    /// Positive values indicate zooming in, negative values indicate zooming out.
271    /// For example, 0.1 represents a 10% zoom increase.
272    pub delta: f32,
273
274    /// The modifiers that were held down during the pinch gesture.
275    pub modifiers: Modifiers,
276
277    /// The phase of the pinch gesture.
278    pub phase: TouchPhase,
279}
280
281impl Deref for PinchEvent {
282    type Target = Modifiers;
283
284    fn deref(&self) -> &Self::Target {
285        &self.modifiers
286    }
287}
288
289/// A mouse exit event from the platform, generated when the mouse leaves the window.
290#[derive(Clone, Debug, Default)]
291pub struct MouseExitEvent {
292    /// The position of the mouse relative to the window.
293    pub position: Point<Pixels>,
294    /// The mouse button that was pressed, if any.
295    pub pressed_button: Option<MouseButton>,
296    /// The modifiers that were held down when the mouse was moved.
297    pub modifiers: Modifiers,
298}
299
300impl Deref for MouseExitEvent {
301    type Target = Modifiers;
302
303    fn deref(&self) -> &Self::Target {
304        &self.modifiers
305    }
306}
307
308/// A file drop event from the platform, generated when files are dragged and dropped onto the window.
309#[derive(Debug, Clone)]
310pub enum FileDropEvent {
311    /// The files have entered the window.
312    Entered {
313        /// The position of the mouse relative to the window.
314        position: Point<Pixels>,
315        /// The paths of the files that are being dragged.
316        paths: ExternalPaths,
317    },
318    /// The files are being dragged over the window
319    Pending {
320        /// The position of the mouse relative to the window.
321        position: Point<Pixels>,
322    },
323    /// The files have been dropped onto the window.
324    Submit {
325        /// The position of the mouse relative to the window.
326        position: Point<Pixels>,
327    },
328    /// The user has stopped dragging the files over the window.
329    Exited,
330    /// The platform-owned drag session has ended.
331    Ended,
332}
333
334/// An enum corresponding to all kinds of platform input events.
335#[derive(Clone, Debug)]
336pub enum PlatformInput {
337    /// A key was pressed.
338    KeyDown(KeyDownEvent),
339    /// A key was released.
340    KeyUp(KeyUpEvent),
341    /// The keyboard modifiers were changed.
342    ModifiersChanged(ModifiersChangedEvent),
343    /// The mouse was pressed.
344    MouseDown(MouseDownEvent),
345    /// The mouse was released.
346    MouseUp(MouseUpEvent),
347    /// Mouse pressure.
348    MousePressure(MousePressureEvent),
349    /// The mouse was moved.
350    MouseMove(MouseMoveEvent),
351    /// The mouse exited the window.
352    MouseExited(MouseExitEvent),
353    /// The scroll wheel was used.
354    ScrollWheel(ScrollWheelEvent),
355    /// A pinch gesture was performed.
356    Pinch(PinchEvent),
357    /// A long-press gesture recognized from touch input.
358    LongPress(LongPressEvent),
359    /// A direct touch drag claimed by an element.
360    TouchDrag(TouchDragEvent),
361    /// Files were dragged and dropped onto the window.
362    FileDrop(FileDropEvent),
363    /// A raw touch event on a touch screen.
364    Touch(TouchEvent),
365}
366
367impl PlatformInput {
368    /// Returns this input as a mouse event, if it is one.
369    pub fn mouse_event(&self) -> Option<&dyn Any> {
370        match self {
371            PlatformInput::KeyDown { .. } => None,
372            PlatformInput::KeyUp { .. } => None,
373            PlatformInput::ModifiersChanged { .. } => None,
374            PlatformInput::MouseDown(event) => Some(event),
375            PlatformInput::MouseUp(event) => Some(event),
376            PlatformInput::MouseMove(event) => Some(event),
377            PlatformInput::MousePressure(event) => Some(event),
378            PlatformInput::MouseExited(event) => Some(event),
379            PlatformInput::ScrollWheel(event) => Some(event),
380            PlatformInput::Pinch(event) => Some(event),
381            PlatformInput::LongPress(event) => Some(event),
382            PlatformInput::TouchDrag(event) => Some(event),
383            PlatformInput::FileDrop(event) => Some(event),
384            PlatformInput::Touch(_) => None,
385        }
386    }
387
388    /// Returns this input as a keyboard event, if it is one.
389    pub fn keyboard_event(&self) -> Option<&dyn Any> {
390        match self {
391            PlatformInput::KeyDown(event) => Some(event),
392            PlatformInput::KeyUp(event) => Some(event),
393            PlatformInput::ModifiersChanged(event) => Some(event),
394            PlatformInput::MouseDown(_) => None,
395            PlatformInput::MouseUp(_) => None,
396            PlatformInput::MouseMove(_) => None,
397            PlatformInput::MousePressure(_) => None,
398            PlatformInput::MouseExited(_) => None,
399            PlatformInput::ScrollWheel(_) => None,
400            PlatformInput::Pinch(_) => None,
401            PlatformInput::LongPress(_) => None,
402            PlatformInput::TouchDrag(_) => None,
403            PlatformInput::FileDrop(_) => None,
404            PlatformInput::Touch(_) => None,
405        }
406    }
407
408    /// A short static name for this input's variant, for diagnostics and
409    /// telemetry.
410    pub fn kind_name(&self) -> &'static str {
411        match self {
412            PlatformInput::KeyDown(_) => "key_down",
413            PlatformInput::KeyUp(_) => "key_up",
414            PlatformInput::ModifiersChanged(_) => "modifiers_changed",
415            PlatformInput::MouseDown(_) => "mouse_down",
416            PlatformInput::MouseUp(_) => "mouse_up",
417            PlatformInput::MousePressure(_) => "mouse_pressure",
418            PlatformInput::MouseMove(_) => "mouse_move",
419            PlatformInput::MouseExited(_) => "mouse_exited",
420            PlatformInput::ScrollWheel(_) => "scroll_wheel",
421            PlatformInput::Pinch(_) => "pinch",
422            PlatformInput::LongPress(_) => "long_press",
423            PlatformInput::TouchDrag(_) => "touch_drag",
424            PlatformInput::FileDrop(_) => "file_drop",
425            PlatformInput::Touch(_) => "touch",
426        }
427    }
428
429    /// Returns the touch event contained in this input, if any.
430    pub fn touch_event(&self) -> Option<&TouchEvent> {
431        match self {
432            PlatformInput::Touch(event) => Some(event),
433            _ => None,
434        }
435    }
436}