1use crate::{
2 Bounds, Capslock, Context, Empty, IntoElement, Keystroke, Modifiers, Pixels, Point, Render,
3 Window, point, 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 /// The new state of the capslock key
59 pub capslock: Capslock,
60}
61
62impl Sealed for ModifiersChangedEvent {}
63impl InputEvent for ModifiersChangedEvent {
64 fn to_platform_input(self) -> PlatformInput {
65 PlatformInput::ModifiersChanged(self)
66 }
67}
68impl KeyEvent for ModifiersChangedEvent {}
69
70impl Deref for ModifiersChangedEvent {
71 type Target = Modifiers;
72
73 fn deref(&self) -> &Self::Target {
74 &self.modifiers
75 }
76}
77
78/// The phase of a touch motion event.
79/// Based on the winit enum of the same name.
80#[derive(Clone, Copy, Debug, Default)]
81pub enum TouchPhase {
82 /// The touch started.
83 Started,
84 /// The touch event is moving.
85 #[default]
86 Moved,
87 /// The touch phase has ended
88 Ended,
89}
90
91/// A mouse down event from the platform
92#[derive(Clone, Debug, Default)]
93pub struct MouseDownEvent {
94 /// Which mouse button was pressed.
95 pub button: MouseButton,
96
97 /// The position of the mouse on the window.
98 pub position: Point<Pixels>,
99
100 /// The modifiers that were held down when the mouse was pressed.
101 pub modifiers: Modifiers,
102
103 /// The number of times the button has been clicked.
104 pub click_count: usize,
105
106 /// Whether this is the first, focusing click.
107 pub first_mouse: bool,
108}
109
110impl Sealed for MouseDownEvent {}
111impl InputEvent for MouseDownEvent {
112 fn to_platform_input(self) -> PlatformInput {
113 PlatformInput::MouseDown(self)
114 }
115}
116impl MouseEvent for MouseDownEvent {}
117
118impl MouseDownEvent {
119 /// Returns true if this mouse up event should focus the element.
120 pub fn is_focusing(&self) -> bool {
121 match self.button {
122 MouseButton::Left => true,
123 _ => false,
124 }
125 }
126}
127
128/// A mouse up event from the platform
129#[derive(Clone, Debug, Default)]
130pub struct MouseUpEvent {
131 /// Which mouse button was released.
132 pub button: MouseButton,
133
134 /// The position of the mouse on the window.
135 pub position: Point<Pixels>,
136
137 /// The modifiers that were held down when the mouse was released.
138 pub modifiers: Modifiers,
139
140 /// The number of times the button has been clicked.
141 pub click_count: usize,
142}
143
144impl Sealed for MouseUpEvent {}
145impl InputEvent for MouseUpEvent {
146 fn to_platform_input(self) -> PlatformInput {
147 PlatformInput::MouseUp(self)
148 }
149}
150
151impl MouseEvent for MouseUpEvent {}
152
153impl MouseUpEvent {
154 /// Returns true if this mouse up event should focus the element.
155 pub fn is_focusing(&self) -> bool {
156 match self.button {
157 MouseButton::Left => true,
158 _ => false,
159 }
160 }
161}
162
163/// A click event, generated when a mouse button is pressed and released.
164#[derive(Clone, Debug, Default)]
165pub struct MouseClickEvent {
166 /// The mouse event when the button was pressed.
167 pub down: MouseDownEvent,
168
169 /// The mouse event when the button was released.
170 pub up: MouseUpEvent,
171}
172
173/// A click event that was generated by a keyboard button being pressed and released.
174#[derive(Clone, Debug, Default)]
175pub struct KeyboardClickEvent {
176 /// The keyboard button that was pressed to trigger the click.
177 pub button: KeyboardButton,
178
179 /// The bounds of the element that was clicked.
180 pub bounds: Bounds<Pixels>,
181}
182
183/// A click event, generated when a mouse button or keyboard button is pressed and released.
184#[derive(Clone, Debug)]
185pub enum ClickEvent {
186 /// A click event trigger by a mouse button being pressed and released.
187 Mouse(MouseClickEvent),
188 /// A click event trigger by a keyboard button being pressed and released.
189 Keyboard(KeyboardClickEvent),
190}
191
192impl Default for ClickEvent {
193 fn default() -> Self {
194 ClickEvent::Keyboard(KeyboardClickEvent::default())
195 }
196}
197
198impl ClickEvent {
199 /// Returns the modifiers that were held during the click event
200 ///
201 /// `Keyboard`: The keyboard click events never have modifiers.
202 /// `Mouse`: Modifiers that were held during the mouse key up event.
203 pub fn modifiers(&self) -> Modifiers {
204 match self {
205 // Click events are only generated from keyboard events _without any modifiers_, so we know the modifiers are always Default
206 ClickEvent::Keyboard(_) => Modifiers::default(),
207 // Click events on the web only reflect the modifiers for the keyup event,
208 // tested via observing the behavior of the `ClickEvent.shiftKey` field in Chrome 138
209 // under various combinations of modifiers and keyUp / keyDown events.
210 ClickEvent::Mouse(event) => event.up.modifiers,
211 }
212 }
213
214 /// Returns the position of the click event
215 ///
216 /// `Keyboard`: The bottom left corner of the clicked hitbox
217 /// `Mouse`: The position of the mouse when the button was released.
218 pub fn position(&self) -> Point<Pixels> {
219 match self {
220 ClickEvent::Keyboard(event) => event.bounds.bottom_left(),
221 ClickEvent::Mouse(event) => event.up.position,
222 }
223 }
224
225 /// Returns the mouse position of the click event
226 ///
227 /// `Keyboard`: None
228 /// `Mouse`: The position of the mouse when the button was released.
229 pub fn mouse_position(&self) -> Option<Point<Pixels>> {
230 match self {
231 ClickEvent::Keyboard(_) => None,
232 ClickEvent::Mouse(event) => Some(event.up.position),
233 }
234 }
235
236 /// Returns if this was a right click
237 ///
238 /// `Keyboard`: false
239 /// `Mouse`: Whether the right button was pressed and released
240 pub fn is_right_click(&self) -> bool {
241 match self {
242 ClickEvent::Keyboard(_) => false,
243 ClickEvent::Mouse(event) => {
244 event.down.button == MouseButton::Right && event.up.button == MouseButton::Right
245 }
246 }
247 }
248
249 /// Returns whether the click was a standard click
250 ///
251 /// `Keyboard`: Always true
252 /// `Mouse`: Left button pressed and released
253 pub fn standard_click(&self) -> bool {
254 match self {
255 ClickEvent::Keyboard(_) => true,
256 ClickEvent::Mouse(event) => {
257 event.down.button == MouseButton::Left && event.up.button == MouseButton::Left
258 }
259 }
260 }
261
262 /// Returns whether the click focused the element
263 ///
264 /// `Keyboard`: false, keyboard clicks only work if an element is already focused
265 /// `Mouse`: Whether this was the first focusing click
266 pub fn first_focus(&self) -> bool {
267 match self {
268 ClickEvent::Keyboard(_) => false,
269 ClickEvent::Mouse(event) => event.down.first_mouse,
270 }
271 }
272
273 /// Returns the click count of the click event
274 ///
275 /// `Keyboard`: Always 1
276 /// `Mouse`: Count of clicks from MouseUpEvent
277 pub fn click_count(&self) -> usize {
278 match self {
279 ClickEvent::Keyboard(_) => 1,
280 ClickEvent::Mouse(event) => event.up.click_count,
281 }
282 }
283
284 /// Returns whether the click event is generated by a keyboard event
285 pub fn is_keyboard(&self) -> bool {
286 match self {
287 ClickEvent::Mouse(_) => false,
288 ClickEvent::Keyboard(_) => true,
289 }
290 }
291}
292
293/// An enum representing the keyboard button that was pressed for a click event.
294#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug, Default)]
295pub enum KeyboardButton {
296 /// Enter key was clicked
297 #[default]
298 Enter,
299 /// Space key was clicked
300 Space,
301}
302
303/// An enum representing the mouse button that was pressed.
304#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
305pub enum MouseButton {
306 /// The left mouse button.
307 Left,
308
309 /// The right mouse button.
310 Right,
311
312 /// The middle mouse button.
313 Middle,
314
315 /// A navigation button, such as back or forward.
316 Navigate(NavigationDirection),
317}
318
319impl MouseButton {
320 /// Get all the mouse buttons in a list.
321 pub fn all() -> Vec<Self> {
322 vec![
323 MouseButton::Left,
324 MouseButton::Right,
325 MouseButton::Middle,
326 MouseButton::Navigate(NavigationDirection::Back),
327 MouseButton::Navigate(NavigationDirection::Forward),
328 ]
329 }
330}
331
332impl Default for MouseButton {
333 fn default() -> Self {
334 Self::Left
335 }
336}
337
338/// A navigation direction, such as back or forward.
339#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
340pub enum NavigationDirection {
341 /// The back button.
342 Back,
343
344 /// The forward button.
345 Forward,
346}
347
348impl Default for NavigationDirection {
349 fn default() -> Self {
350 Self::Back
351 }
352}
353
354/// A mouse move event from the platform
355#[derive(Clone, Debug, Default)]
356pub struct MouseMoveEvent {
357 /// The position of the mouse on the window.
358 pub position: Point<Pixels>,
359
360 /// The mouse button that was pressed, if any.
361 pub pressed_button: Option<MouseButton>,
362
363 /// The modifiers that were held down when the mouse was moved.
364 pub modifiers: Modifiers,
365}
366
367impl Sealed for MouseMoveEvent {}
368impl InputEvent for MouseMoveEvent {
369 fn to_platform_input(self) -> PlatformInput {
370 PlatformInput::MouseMove(self)
371 }
372}
373impl MouseEvent for MouseMoveEvent {}
374
375impl MouseMoveEvent {
376 /// Returns true if the left mouse button is currently held down.
377 pub fn dragging(&self) -> bool {
378 self.pressed_button == Some(MouseButton::Left)
379 }
380}
381
382/// A mouse wheel event from the platform
383#[derive(Clone, Debug, Default)]
384pub struct ScrollWheelEvent {
385 /// The position of the mouse on the window.
386 pub position: Point<Pixels>,
387
388 /// The change in scroll wheel position for this event.
389 pub delta: ScrollDelta,
390
391 /// The modifiers that were held down when the mouse was moved.
392 pub modifiers: Modifiers,
393
394 /// The phase of the touch event.
395 pub touch_phase: TouchPhase,
396}
397
398impl Sealed for ScrollWheelEvent {}
399impl InputEvent for ScrollWheelEvent {
400 fn to_platform_input(self) -> PlatformInput {
401 PlatformInput::ScrollWheel(self)
402 }
403}
404impl MouseEvent for ScrollWheelEvent {}
405
406impl Deref for ScrollWheelEvent {
407 type Target = Modifiers;
408
409 fn deref(&self) -> &Self::Target {
410 &self.modifiers
411 }
412}
413
414/// The scroll delta for a scroll wheel event.
415#[derive(Clone, Copy, Debug)]
416pub enum ScrollDelta {
417 /// An exact scroll delta in pixels.
418 Pixels(Point<Pixels>),
419 /// An inexact scroll delta in lines.
420 Lines(Point<f32>),
421}
422
423impl Default for ScrollDelta {
424 fn default() -> Self {
425 Self::Lines(Default::default())
426 }
427}
428
429impl ScrollDelta {
430 /// Returns true if this is a precise scroll delta in pixels.
431 pub fn precise(&self) -> bool {
432 match self {
433 ScrollDelta::Pixels(_) => true,
434 ScrollDelta::Lines(_) => false,
435 }
436 }
437
438 /// Converts this scroll event into exact pixels.
439 pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
440 match self {
441 ScrollDelta::Pixels(delta) => *delta,
442 ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
443 }
444 }
445
446 /// Combines two scroll deltas into one.
447 /// If the signs of the deltas are the same (both positive or both negative),
448 /// the deltas are added together. If the signs are opposite, the second delta
449 /// (other) is used, effectively overriding the first delta.
450 pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
451 match (self, other) {
452 (ScrollDelta::Pixels(a), ScrollDelta::Pixels(b)) => {
453 let x = if a.x.signum() == b.x.signum() {
454 a.x + b.x
455 } else {
456 b.x
457 };
458
459 let y = if a.y.signum() == b.y.signum() {
460 a.y + b.y
461 } else {
462 b.y
463 };
464
465 ScrollDelta::Pixels(point(x, y))
466 }
467
468 (ScrollDelta::Lines(a), ScrollDelta::Lines(b)) => {
469 let x = if a.x.signum() == b.x.signum() {
470 a.x + b.x
471 } else {
472 b.x
473 };
474
475 let y = if a.y.signum() == b.y.signum() {
476 a.y + b.y
477 } else {
478 b.y
479 };
480
481 ScrollDelta::Lines(point(x, y))
482 }
483
484 _ => other,
485 }
486 }
487}
488
489/// A mouse exit event from the platform, generated when the mouse leaves the window.
490#[derive(Clone, Debug, Default)]
491pub struct MouseExitEvent {
492 /// The position of the mouse relative to the window.
493 pub position: Point<Pixels>,
494 /// The mouse button that was pressed, if any.
495 pub pressed_button: Option<MouseButton>,
496 /// The modifiers that were held down when the mouse was moved.
497 pub modifiers: Modifiers,
498}
499
500impl Sealed for MouseExitEvent {}
501impl InputEvent for MouseExitEvent {
502 fn to_platform_input(self) -> PlatformInput {
503 PlatformInput::MouseExited(self)
504 }
505}
506
507impl MouseEvent for MouseExitEvent {}
508
509impl Deref for MouseExitEvent {
510 type Target = Modifiers;
511
512 fn deref(&self) -> &Self::Target {
513 &self.modifiers
514 }
515}
516
517/// A collection of paths from the platform, such as from a file drop.
518#[derive(Debug, Clone, Default)]
519pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
520
521impl ExternalPaths {
522 /// Convert this collection of paths into a slice.
523 pub fn paths(&self) -> &[PathBuf] {
524 &self.0
525 }
526}
527
528impl Render for ExternalPaths {
529 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
530 // the platform will render icons for the dragged files
531 Empty
532 }
533}
534
535/// A file drop event from the platform, generated when files are dragged and dropped onto the window.
536#[derive(Debug, Clone)]
537pub enum FileDropEvent {
538 /// The files have entered the window.
539 Entered {
540 /// The position of the mouse relative to the window.
541 position: Point<Pixels>,
542 /// The paths of the files that are being dragged.
543 paths: ExternalPaths,
544 },
545 /// The files are being dragged over the window
546 Pending {
547 /// The position of the mouse relative to the window.
548 position: Point<Pixels>,
549 },
550 /// The files have been dropped onto the window.
551 Submit {
552 /// The position of the mouse relative to the window.
553 position: Point<Pixels>,
554 },
555 /// The user has stopped dragging the files over the window.
556 Exited,
557}
558
559impl Sealed for FileDropEvent {}
560impl InputEvent for FileDropEvent {
561 fn to_platform_input(self) -> PlatformInput {
562 PlatformInput::FileDrop(self)
563 }
564}
565impl MouseEvent for FileDropEvent {}
566
567/// An enum corresponding to all kinds of platform input events.
568#[derive(Clone, Debug)]
569pub enum PlatformInput {
570 /// A key was pressed.
571 KeyDown(KeyDownEvent),
572 /// A key was released.
573 KeyUp(KeyUpEvent),
574 /// The keyboard modifiers were changed.
575 ModifiersChanged(ModifiersChangedEvent),
576 /// The mouse was pressed.
577 MouseDown(MouseDownEvent),
578 /// The mouse was released.
579 MouseUp(MouseUpEvent),
580 /// The mouse was moved.
581 MouseMove(MouseMoveEvent),
582 /// The mouse exited the window.
583 MouseExited(MouseExitEvent),
584 /// The scroll wheel was used.
585 ScrollWheel(ScrollWheelEvent),
586 /// Files were dragged and dropped onto the window.
587 FileDrop(FileDropEvent),
588}
589
590impl PlatformInput {
591 pub(crate) fn mouse_event(&self) -> Option<&dyn Any> {
592 match self {
593 PlatformInput::KeyDown { .. } => None,
594 PlatformInput::KeyUp { .. } => None,
595 PlatformInput::ModifiersChanged { .. } => None,
596 PlatformInput::MouseDown(event) => Some(event),
597 PlatformInput::MouseUp(event) => Some(event),
598 PlatformInput::MouseMove(event) => Some(event),
599 PlatformInput::MouseExited(event) => Some(event),
600 PlatformInput::ScrollWheel(event) => Some(event),
601 PlatformInput::FileDrop(event) => Some(event),
602 }
603 }
604
605 pub(crate) fn keyboard_event(&self) -> Option<&dyn Any> {
606 match self {
607 PlatformInput::KeyDown(event) => Some(event),
608 PlatformInput::KeyUp(event) => Some(event),
609 PlatformInput::ModifiersChanged(event) => Some(event),
610 PlatformInput::MouseDown(_) => None,
611 PlatformInput::MouseUp(_) => None,
612 PlatformInput::MouseMove(_) => None,
613 PlatformInput::MouseExited(_) => None,
614 PlatformInput::ScrollWheel(_) => None,
615 PlatformInput::FileDrop(_) => None,
616 }
617 }
618}
619
620#[cfg(test)]
621mod test {
622
623 use crate::{
624 self as gpui, AppContext as _, Context, FocusHandle, InteractiveElement, IntoElement,
625 KeyBinding, Keystroke, ParentElement, Render, TestAppContext, Window, div,
626 };
627
628 struct TestView {
629 saw_key_down: bool,
630 saw_action: bool,
631 focus_handle: FocusHandle,
632 }
633
634 actions!(test_only, [TestAction]);
635
636 impl Render for TestView {
637 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
638 div().id("testview").child(
639 div()
640 .key_context("parent")
641 .on_key_down(cx.listener(|this, _, _, cx| {
642 cx.stop_propagation();
643 this.saw_key_down = true
644 }))
645 .on_action(cx.listener(|this: &mut TestView, _: &TestAction, _, _| {
646 this.saw_action = true
647 }))
648 .child(
649 div()
650 .key_context("nested")
651 .track_focus(&self.focus_handle)
652 .into_element(),
653 ),
654 )
655 }
656 }
657
658 #[gpui::test]
659 fn test_on_events(cx: &mut TestAppContext) {
660 let window = cx.update(|cx| {
661 cx.open_window(Default::default(), |_, cx| {
662 cx.new(|cx| TestView {
663 saw_key_down: false,
664 saw_action: false,
665 focus_handle: cx.focus_handle(),
666 })
667 })
668 .unwrap()
669 });
670
671 cx.update(|cx| {
672 cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
673 });
674
675 window
676 .update(cx, |test_view, window, _cx| {
677 window.focus(&test_view.focus_handle)
678 })
679 .unwrap();
680
681 cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap());
682 cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap());
683
684 window
685 .update(cx, |test_view, _, _| {
686 assert!(test_view.saw_key_down || test_view.saw_action);
687 assert!(test_view.saw_key_down);
688 assert!(test_view.saw_action);
689 })
690 .unwrap();
691 }
692}