1use crate::{
2 point, seal::Sealed, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
3};
4use smallvec::SmallVec;
5use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
6
7/// An event from a platform input source.
8pub trait InputEvent: Sealed + 'static {
9 /// Convert this event into the platform input enum.
10 fn to_platform_input(self) -> PlatformInput;
11}
12
13/// A key event from the platform.
14pub trait KeyEvent: InputEvent {}
15
16/// A mouse event from the platform.
17pub trait MouseEvent: InputEvent {}
18
19/// The key down event equivalent for the platform.
20#[derive(Clone, Debug, Eq, PartialEq)]
21pub struct KeyDownEvent {
22 /// The keystroke that was generated.
23 pub keystroke: Keystroke,
24
25 /// Whether the key is currently held down.
26 pub is_held: bool,
27}
28
29impl Sealed for KeyDownEvent {}
30impl InputEvent for KeyDownEvent {
31 fn to_platform_input(self) -> PlatformInput {
32 PlatformInput::KeyDown(self)
33 }
34}
35impl KeyEvent for KeyDownEvent {}
36
37/// The key up event equivalent for the platform.
38#[derive(Clone, Debug)]
39pub struct KeyUpEvent {
40 /// The keystroke that was released.
41 pub keystroke: Keystroke,
42}
43
44impl Sealed for KeyUpEvent {}
45impl InputEvent for KeyUpEvent {
46 fn to_platform_input(self) -> PlatformInput {
47 PlatformInput::KeyUp(self)
48 }
49}
50impl KeyEvent for KeyUpEvent {}
51
52/// The modifiers changed event equivalent for the platform.
53#[derive(Clone, Debug, Default)]
54pub struct ModifiersChangedEvent {
55 /// The new state of the modifier keys
56 pub modifiers: Modifiers,
57}
58
59impl Sealed for ModifiersChangedEvent {}
60impl InputEvent for ModifiersChangedEvent {
61 fn to_platform_input(self) -> PlatformInput {
62 PlatformInput::ModifiersChanged(self)
63 }
64}
65impl KeyEvent for ModifiersChangedEvent {}
66
67impl Deref for ModifiersChangedEvent {
68 type Target = Modifiers;
69
70 fn deref(&self) -> &Self::Target {
71 &self.modifiers
72 }
73}
74
75/// The phase of a touch motion event.
76/// Based on the winit enum of the same name.
77#[derive(Clone, Copy, Debug, Default)]
78pub enum TouchPhase {
79 /// The touch started.
80 Started,
81 /// The touch event is moving.
82 #[default]
83 Moved,
84 /// The touch phase has ended
85 Ended,
86}
87
88/// A mouse down event from the platform
89#[derive(Clone, Debug, Default)]
90pub struct MouseDownEvent {
91 /// Which mouse button was pressed.
92 pub button: MouseButton,
93
94 /// The position of the mouse on the window.
95 pub position: Point<Pixels>,
96
97 /// The modifiers that were held down when the mouse was pressed.
98 pub modifiers: Modifiers,
99
100 /// The number of times the button has been clicked.
101 pub click_count: usize,
102}
103
104impl Sealed for MouseDownEvent {}
105impl InputEvent for MouseDownEvent {
106 fn to_platform_input(self) -> PlatformInput {
107 PlatformInput::MouseDown(self)
108 }
109}
110impl MouseEvent for MouseDownEvent {}
111
112/// A mouse up event from the platform
113#[derive(Clone, Debug, Default)]
114pub struct MouseUpEvent {
115 /// Which mouse button was released.
116 pub button: MouseButton,
117
118 /// The position of the mouse on the window.
119 pub position: Point<Pixels>,
120
121 /// The modifiers that were held down when the mouse was released.
122 pub modifiers: Modifiers,
123
124 /// The number of times the button has been clicked.
125 pub click_count: usize,
126}
127
128impl Sealed for MouseUpEvent {}
129impl InputEvent for MouseUpEvent {
130 fn to_platform_input(self) -> PlatformInput {
131 PlatformInput::MouseUp(self)
132 }
133}
134impl MouseEvent for MouseUpEvent {}
135
136/// A click event, generated when a mouse button is pressed and released.
137#[derive(Clone, Debug, Default)]
138pub struct ClickEvent {
139 /// The mouse event when the button was pressed.
140 pub down: MouseDownEvent,
141
142 /// The mouse event when the button was released.
143 pub up: MouseUpEvent,
144}
145
146/// An enum representing the mouse button that was pressed.
147#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
148pub enum MouseButton {
149 /// The left mouse button.
150 Left,
151
152 /// The right mouse button.
153 Right,
154
155 /// The middle mouse button.
156 Middle,
157
158 /// A navigation button, such as back or forward.
159 Navigate(NavigationDirection),
160}
161
162impl MouseButton {
163 /// Get all the mouse buttons in a list.
164 pub fn all() -> Vec<Self> {
165 vec![
166 MouseButton::Left,
167 MouseButton::Right,
168 MouseButton::Middle,
169 MouseButton::Navigate(NavigationDirection::Back),
170 MouseButton::Navigate(NavigationDirection::Forward),
171 ]
172 }
173}
174
175impl Default for MouseButton {
176 fn default() -> Self {
177 Self::Left
178 }
179}
180
181/// A navigation direction, such as back or forward.
182#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
183pub enum NavigationDirection {
184 /// The back button.
185 Back,
186
187 /// The forward button.
188 Forward,
189}
190
191impl Default for NavigationDirection {
192 fn default() -> Self {
193 Self::Back
194 }
195}
196
197/// A mouse move event from the platform
198#[derive(Clone, Debug, Default)]
199pub struct MouseMoveEvent {
200 /// The position of the mouse on the window.
201 pub position: Point<Pixels>,
202
203 /// The mouse button that was pressed, if any.
204 pub pressed_button: Option<MouseButton>,
205
206 /// The modifiers that were held down when the mouse was moved.
207 pub modifiers: Modifiers,
208}
209
210impl Sealed for MouseMoveEvent {}
211impl InputEvent for MouseMoveEvent {
212 fn to_platform_input(self) -> PlatformInput {
213 PlatformInput::MouseMove(self)
214 }
215}
216impl MouseEvent for MouseMoveEvent {}
217
218impl MouseMoveEvent {
219 /// Returns true if the left mouse button is currently held down.
220 pub fn dragging(&self) -> bool {
221 self.pressed_button == Some(MouseButton::Left)
222 }
223}
224
225/// A mouse wheel event from the platform
226#[derive(Clone, Debug, Default)]
227pub struct ScrollWheelEvent {
228 /// The position of the mouse on the window.
229 pub position: Point<Pixels>,
230
231 /// The change in scroll wheel position for this event.
232 pub delta: ScrollDelta,
233
234 /// The modifiers that were held down when the mouse was moved.
235 pub modifiers: Modifiers,
236
237 /// The phase of the touch event.
238 pub touch_phase: TouchPhase,
239}
240
241impl Sealed for ScrollWheelEvent {}
242impl InputEvent for ScrollWheelEvent {
243 fn to_platform_input(self) -> PlatformInput {
244 PlatformInput::ScrollWheel(self)
245 }
246}
247impl MouseEvent for ScrollWheelEvent {}
248
249impl Deref for ScrollWheelEvent {
250 type Target = Modifiers;
251
252 fn deref(&self) -> &Self::Target {
253 &self.modifiers
254 }
255}
256
257/// The scroll delta for a scroll wheel event.
258#[derive(Clone, Copy, Debug)]
259pub enum ScrollDelta {
260 /// An exact scroll delta in pixels.
261 Pixels(Point<Pixels>),
262 /// An inexact scroll delta in lines.
263 Lines(Point<f32>),
264}
265
266impl Default for ScrollDelta {
267 fn default() -> Self {
268 Self::Lines(Default::default())
269 }
270}
271
272impl ScrollDelta {
273 /// Returns true if this is a precise scroll delta in pixels.
274 pub fn precise(&self) -> bool {
275 match self {
276 ScrollDelta::Pixels(_) => true,
277 ScrollDelta::Lines(_) => false,
278 }
279 }
280
281 /// Converts this scroll event into exact pixels.
282 pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
283 match self {
284 ScrollDelta::Pixels(delta) => *delta,
285 ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
286 }
287 }
288
289 /// Combines two scroll deltas into one.
290 pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
291 match (self, other) {
292 (ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
293 ScrollDelta::Pixels(px_a + px_b)
294 }
295
296 (ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => {
297 ScrollDelta::Lines(lines_a + lines_b)
298 }
299
300 _ => other,
301 }
302 }
303}
304
305/// A mouse exit event from the platform, generated when the mouse leaves the window.
306/// The position generated should be just outside of the window's bounds.
307#[derive(Clone, Debug, Default)]
308pub struct MouseExitEvent {
309 /// The position of the mouse relative to the window.
310 pub position: Point<Pixels>,
311 /// The mouse button that was pressed, if any.
312 pub pressed_button: Option<MouseButton>,
313 /// The modifiers that were held down when the mouse was moved.
314 pub modifiers: Modifiers,
315}
316
317impl Sealed for MouseExitEvent {}
318impl InputEvent for MouseExitEvent {
319 fn to_platform_input(self) -> PlatformInput {
320 PlatformInput::MouseExited(self)
321 }
322}
323impl MouseEvent for MouseExitEvent {}
324
325impl Deref for MouseExitEvent {
326 type Target = Modifiers;
327
328 fn deref(&self) -> &Self::Target {
329 &self.modifiers
330 }
331}
332
333/// A collection of paths from the platform, such as from a file drop.
334#[derive(Debug, Clone, Default)]
335pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
336
337impl ExternalPaths {
338 /// Convert this collection of paths into a slice.
339 pub fn paths(&self) -> &[PathBuf] {
340 &self.0
341 }
342}
343
344impl Render for ExternalPaths {
345 fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
346 // Intentionally left empty because the platform will render icons for the dragged files
347 }
348}
349
350/// A file drop event from the platform, generated when files are dragged and dropped onto the window.
351#[derive(Debug, Clone)]
352pub enum FileDropEvent {
353 /// The files have entered the window.
354 Entered {
355 /// The position of the mouse relative to the window.
356 position: Point<Pixels>,
357 /// The paths of the files that are being dragged.
358 paths: ExternalPaths,
359 },
360 /// The files are being dragged over the window
361 Pending {
362 /// The position of the mouse relative to the window.
363 position: Point<Pixels>,
364 },
365 /// The files have been dropped onto the window.
366 Submit {
367 /// The position of the mouse relative to the window.
368 position: Point<Pixels>,
369 },
370 /// The user has stopped dragging the files over the window.
371 Exited,
372}
373
374impl Sealed for FileDropEvent {}
375impl InputEvent for FileDropEvent {
376 fn to_platform_input(self) -> PlatformInput {
377 PlatformInput::FileDrop(self)
378 }
379}
380impl MouseEvent for FileDropEvent {}
381
382/// An enum corresponding to all kinds of platform input events.
383#[derive(Clone, Debug)]
384pub enum PlatformInput {
385 /// A key was pressed.
386 KeyDown(KeyDownEvent),
387 /// A key was released.
388 KeyUp(KeyUpEvent),
389 /// The keyboard modifiers were changed.
390 ModifiersChanged(ModifiersChangedEvent),
391 /// The mouse was pressed.
392 MouseDown(MouseDownEvent),
393 /// The mouse was released.
394 MouseUp(MouseUpEvent),
395 /// The mouse was moved.
396 MouseMove(MouseMoveEvent),
397 /// The mouse exited the window.
398 MouseExited(MouseExitEvent),
399 /// The scroll wheel was used.
400 ScrollWheel(ScrollWheelEvent),
401 /// Files were dragged and dropped onto the window.
402 FileDrop(FileDropEvent),
403}
404
405impl PlatformInput {
406 pub(crate) fn mouse_event(&self) -> Option<&dyn Any> {
407 match self {
408 PlatformInput::KeyDown { .. } => None,
409 PlatformInput::KeyUp { .. } => None,
410 PlatformInput::ModifiersChanged { .. } => None,
411 PlatformInput::MouseDown(event) => Some(event),
412 PlatformInput::MouseUp(event) => Some(event),
413 PlatformInput::MouseMove(event) => Some(event),
414 PlatformInput::MouseExited(event) => Some(event),
415 PlatformInput::ScrollWheel(event) => Some(event),
416 PlatformInput::FileDrop(event) => Some(event),
417 }
418 }
419
420 pub(crate) fn keyboard_event(&self) -> Option<&dyn Any> {
421 match self {
422 PlatformInput::KeyDown(event) => Some(event),
423 PlatformInput::KeyUp(event) => Some(event),
424 PlatformInput::ModifiersChanged(event) => Some(event),
425 PlatformInput::MouseDown(_) => None,
426 PlatformInput::MouseUp(_) => None,
427 PlatformInput::MouseMove(_) => None,
428 PlatformInput::MouseExited(_) => None,
429 PlatformInput::ScrollWheel(_) => None,
430 PlatformInput::FileDrop(_) => None,
431 }
432 }
433}
434
435#[cfg(test)]
436mod test {
437 use crate::{
438 self as gpui, div, Element, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
439 Keystroke, ParentElement, Render, TestAppContext, VisualContext,
440 };
441
442 struct TestView {
443 saw_key_down: bool,
444 saw_action: bool,
445 focus_handle: FocusHandle,
446 }
447
448 actions!(test, [TestAction]);
449
450 impl Render for TestView {
451 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl Element {
452 div().id("testview").child(
453 div()
454 .key_context("parent")
455 .on_key_down(cx.listener(|this, _, cx| {
456 cx.stop_propagation();
457 this.saw_key_down = true
458 }))
459 .on_action(
460 cx.listener(|this: &mut TestView, _: &TestAction, _| {
461 this.saw_action = true
462 }),
463 )
464 .child(
465 div()
466 .key_context("nested")
467 .track_focus(&self.focus_handle)
468 .into_element(),
469 ),
470 )
471 }
472 }
473
474 #[gpui::test]
475 fn test_on_events(cx: &mut TestAppContext) {
476 let window = cx.update(|cx| {
477 cx.open_window(Default::default(), |cx| {
478 cx.new_view(|cx| TestView {
479 saw_key_down: false,
480 saw_action: false,
481 focus_handle: cx.focus_handle(),
482 })
483 })
484 });
485
486 cx.update(|cx| {
487 cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
488 });
489
490 window
491 .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
492 .unwrap();
493
494 cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap());
495 cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap());
496
497 window
498 .update(cx, |test_view, _| {
499 assert!(test_view.saw_key_down || test_view.saw_action);
500 assert!(test_view.saw_key_down);
501 assert!(test_view.saw_action);
502 })
503 .unwrap();
504 }
505}