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