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