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
7pub trait InputEvent: Sealed + 'static {
8 fn to_platform_input(self) -> PlatformInput;
9}
10pub trait KeyEvent: InputEvent {}
11pub trait MouseEvent: InputEvent {}
12
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct KeyDownEvent {
15 pub keystroke: Keystroke,
16 pub is_held: bool,
17}
18
19impl Sealed for KeyDownEvent {}
20impl InputEvent for KeyDownEvent {
21 fn to_platform_input(self) -> PlatformInput {
22 PlatformInput::KeyDown(self)
23 }
24}
25impl KeyEvent for KeyDownEvent {}
26
27#[derive(Clone, Debug)]
28pub struct KeyUpEvent {
29 pub keystroke: Keystroke,
30}
31
32impl Sealed for KeyUpEvent {}
33impl InputEvent for KeyUpEvent {
34 fn to_platform_input(self) -> PlatformInput {
35 PlatformInput::KeyUp(self)
36 }
37}
38impl KeyEvent for KeyUpEvent {}
39
40#[derive(Clone, Debug, Default)]
41pub struct ModifiersChangedEvent {
42 pub modifiers: Modifiers,
43}
44
45impl Sealed for ModifiersChangedEvent {}
46impl InputEvent for ModifiersChangedEvent {
47 fn to_platform_input(self) -> PlatformInput {
48 PlatformInput::ModifiersChanged(self)
49 }
50}
51impl KeyEvent for ModifiersChangedEvent {}
52
53impl Deref for ModifiersChangedEvent {
54 type Target = Modifiers;
55
56 fn deref(&self) -> &Self::Target {
57 &self.modifiers
58 }
59}
60
61/// The phase of a touch motion event.
62/// Based on the winit enum of the same name.
63#[derive(Clone, Copy, Debug, Default)]
64pub enum TouchPhase {
65 Started,
66 #[default]
67 Moved,
68 Ended,
69}
70
71#[derive(Clone, Debug, Default)]
72pub struct MouseDownEvent {
73 pub button: MouseButton,
74 pub position: Point<Pixels>,
75 pub modifiers: Modifiers,
76 pub click_count: usize,
77}
78
79impl Sealed for MouseDownEvent {}
80impl InputEvent for MouseDownEvent {
81 fn to_platform_input(self) -> PlatformInput {
82 PlatformInput::MouseDown(self)
83 }
84}
85impl MouseEvent for MouseDownEvent {}
86
87#[derive(Clone, Debug, Default)]
88pub struct MouseUpEvent {
89 pub button: MouseButton,
90 pub position: Point<Pixels>,
91 pub modifiers: Modifiers,
92 pub click_count: usize,
93}
94
95impl Sealed for MouseUpEvent {}
96impl InputEvent for MouseUpEvent {
97 fn to_platform_input(self) -> PlatformInput {
98 PlatformInput::MouseUp(self)
99 }
100}
101impl MouseEvent for MouseUpEvent {}
102
103#[derive(Clone, Debug, Default)]
104pub struct ClickEvent {
105 pub down: MouseDownEvent,
106 pub up: MouseUpEvent,
107}
108
109#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
110pub enum MouseButton {
111 Left,
112 Right,
113 Middle,
114 Navigate(NavigationDirection),
115}
116
117impl MouseButton {
118 pub fn all() -> Vec<Self> {
119 vec![
120 MouseButton::Left,
121 MouseButton::Right,
122 MouseButton::Middle,
123 MouseButton::Navigate(NavigationDirection::Back),
124 MouseButton::Navigate(NavigationDirection::Forward),
125 ]
126 }
127}
128
129impl Default for MouseButton {
130 fn default() -> Self {
131 Self::Left
132 }
133}
134
135#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
136pub enum NavigationDirection {
137 Back,
138 Forward,
139}
140
141impl Default for NavigationDirection {
142 fn default() -> Self {
143 Self::Back
144 }
145}
146
147#[derive(Clone, Debug, Default)]
148pub struct MouseMoveEvent {
149 pub position: Point<Pixels>,
150 pub pressed_button: Option<MouseButton>,
151 pub modifiers: Modifiers,
152}
153
154impl Sealed for MouseMoveEvent {}
155impl InputEvent for MouseMoveEvent {
156 fn to_platform_input(self) -> PlatformInput {
157 PlatformInput::MouseMove(self)
158 }
159}
160impl MouseEvent for MouseMoveEvent {}
161
162impl MouseMoveEvent {
163 pub fn dragging(&self) -> bool {
164 self.pressed_button == Some(MouseButton::Left)
165 }
166}
167
168#[derive(Clone, Debug, Default)]
169pub struct ScrollWheelEvent {
170 pub position: Point<Pixels>,
171 pub delta: ScrollDelta,
172 pub modifiers: Modifiers,
173 pub touch_phase: TouchPhase,
174}
175
176impl Sealed for ScrollWheelEvent {}
177impl InputEvent for ScrollWheelEvent {
178 fn to_platform_input(self) -> PlatformInput {
179 PlatformInput::ScrollWheel(self)
180 }
181}
182impl MouseEvent for ScrollWheelEvent {}
183
184impl Deref for ScrollWheelEvent {
185 type Target = Modifiers;
186
187 fn deref(&self) -> &Self::Target {
188 &self.modifiers
189 }
190}
191
192#[derive(Clone, Copy, Debug)]
193pub enum ScrollDelta {
194 Pixels(Point<Pixels>),
195 Lines(Point<f32>),
196}
197
198impl Default for ScrollDelta {
199 fn default() -> Self {
200 Self::Lines(Default::default())
201 }
202}
203
204impl ScrollDelta {
205 pub fn precise(&self) -> bool {
206 match self {
207 ScrollDelta::Pixels(_) => true,
208 ScrollDelta::Lines(_) => false,
209 }
210 }
211
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 pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
220 match (self, other) {
221 (ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
222 ScrollDelta::Pixels(px_a + px_b)
223 }
224
225 (ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => {
226 ScrollDelta::Lines(lines_a + lines_b)
227 }
228
229 _ => other,
230 }
231 }
232}
233
234#[derive(Clone, Debug, Default)]
235pub struct MouseExitEvent {
236 pub position: Point<Pixels>,
237 pub pressed_button: Option<MouseButton>,
238 pub modifiers: Modifiers,
239}
240
241impl Sealed for MouseExitEvent {}
242impl InputEvent for MouseExitEvent {
243 fn to_platform_input(self) -> PlatformInput {
244 PlatformInput::MouseExited(self)
245 }
246}
247impl MouseEvent for MouseExitEvent {}
248
249impl Deref for MouseExitEvent {
250 type Target = Modifiers;
251
252 fn deref(&self) -> &Self::Target {
253 &self.modifiers
254 }
255}
256
257#[derive(Debug, Clone, Default)]
258pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
259
260impl ExternalPaths {
261 pub fn paths(&self) -> &[PathBuf] {
262 &self.0
263 }
264}
265
266impl Render for ExternalPaths {
267 fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
268 () // Intentionally left empty because the platform will render icons for the dragged files
269 }
270}
271
272#[derive(Debug, Clone)]
273pub enum FileDropEvent {
274 Entered {
275 position: Point<Pixels>,
276 paths: ExternalPaths,
277 },
278 Pending {
279 position: Point<Pixels>,
280 },
281 Submit {
282 position: Point<Pixels>,
283 },
284 Exited,
285}
286
287impl Sealed for FileDropEvent {}
288impl InputEvent for FileDropEvent {
289 fn to_platform_input(self) -> PlatformInput {
290 PlatformInput::FileDrop(self)
291 }
292}
293impl MouseEvent for FileDropEvent {}
294
295#[derive(Clone, Debug)]
296pub enum PlatformInput {
297 KeyDown(KeyDownEvent),
298 KeyUp(KeyUpEvent),
299 ModifiersChanged(ModifiersChangedEvent),
300 MouseDown(MouseDownEvent),
301 MouseUp(MouseUpEvent),
302 MouseMove(MouseMoveEvent),
303 MouseExited(MouseExitEvent),
304 ScrollWheel(ScrollWheelEvent),
305 FileDrop(FileDropEvent),
306}
307
308impl PlatformInput {
309 pub fn position(&self) -> Option<Point<Pixels>> {
310 match self {
311 PlatformInput::KeyDown { .. } => None,
312 PlatformInput::KeyUp { .. } => None,
313 PlatformInput::ModifiersChanged { .. } => None,
314 PlatformInput::MouseDown(event) => Some(event.position),
315 PlatformInput::MouseUp(event) => Some(event.position),
316 PlatformInput::MouseMove(event) => Some(event.position),
317 PlatformInput::MouseExited(event) => Some(event.position),
318 PlatformInput::ScrollWheel(event) => Some(event.position),
319 PlatformInput::FileDrop(FileDropEvent::Exited) => None,
320 PlatformInput::FileDrop(
321 FileDropEvent::Entered { position, .. }
322 | FileDropEvent::Pending { position, .. }
323 | FileDropEvent::Submit { position, .. },
324 ) => Some(*position),
325 }
326 }
327
328 pub fn mouse_event(&self) -> Option<&dyn Any> {
329 match self {
330 PlatformInput::KeyDown { .. } => None,
331 PlatformInput::KeyUp { .. } => None,
332 PlatformInput::ModifiersChanged { .. } => None,
333 PlatformInput::MouseDown(event) => Some(event),
334 PlatformInput::MouseUp(event) => Some(event),
335 PlatformInput::MouseMove(event) => Some(event),
336 PlatformInput::MouseExited(event) => Some(event),
337 PlatformInput::ScrollWheel(event) => Some(event),
338 PlatformInput::FileDrop(event) => Some(event),
339 }
340 }
341
342 pub fn keyboard_event(&self) -> Option<&dyn Any> {
343 match self {
344 PlatformInput::KeyDown(event) => Some(event),
345 PlatformInput::KeyUp(event) => Some(event),
346 PlatformInput::ModifiersChanged(event) => Some(event),
347 PlatformInput::MouseDown(_) => None,
348 PlatformInput::MouseUp(_) => None,
349 PlatformInput::MouseMove(_) => None,
350 PlatformInput::MouseExited(_) => None,
351 PlatformInput::ScrollWheel(_) => None,
352 PlatformInput::FileDrop(_) => None,
353 }
354 }
355}
356
357#[cfg(test)]
358mod test {
359 use crate::{
360 self as gpui, div, Element, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
361 Keystroke, ParentElement, Render, TestAppContext, VisualContext,
362 };
363
364 struct TestView {
365 saw_key_down: bool,
366 saw_action: bool,
367 focus_handle: FocusHandle,
368 }
369
370 actions!(test, [TestAction]);
371
372 impl Render for TestView {
373 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl Element {
374 div().id("testview").child(
375 div()
376 .key_context("parent")
377 .on_key_down(cx.listener(|this, _, cx| {
378 cx.stop_propagation();
379 this.saw_key_down = true
380 }))
381 .on_action(
382 cx.listener(|this: &mut TestView, _: &TestAction, _| {
383 this.saw_action = true
384 }),
385 )
386 .child(
387 div()
388 .key_context("nested")
389 .track_focus(&self.focus_handle)
390 .into_element(),
391 ),
392 )
393 }
394 }
395
396 #[gpui::test]
397 fn test_on_events(cx: &mut TestAppContext) {
398 let window = cx.update(|cx| {
399 cx.open_window(Default::default(), |cx| {
400 cx.new_view(|cx| TestView {
401 saw_key_down: false,
402 saw_action: false,
403 focus_handle: cx.focus_handle(),
404 })
405 })
406 });
407
408 cx.update(|cx| {
409 cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
410 });
411
412 window
413 .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
414 .unwrap();
415
416 cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap(), false);
417 cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
418
419 window
420 .update(cx, |test_view, _| {
421 assert!(test_view.saw_key_down || test_view.saw_action);
422 assert!(test_view.saw_key_down);
423 assert!(test_view.saw_action);
424 })
425 .unwrap();
426 }
427}