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}