1use crate::{
2 Bounds, Context, Empty, ExternalPaths, FileDropEvent, IntoElement, KeyDownEvent, KeyUpEvent,
3 Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
4 MousePressureEvent, MouseUpEvent, PinchEvent, Pixels, PlatformInput, Point, Render,
5 ScrollWheelEvent, TouchEvent, Window, seal::Sealed,
6};
7use std::fmt::Debug;
8
9pub trait InputEvent: Sealed + 'static {
11 fn to_platform_input(self) -> PlatformInput;
13}
14
15pub trait KeyEvent: InputEvent {}
17
18pub trait MouseEvent: InputEvent {}
20
21pub trait GestureEvent: InputEvent {}
23
24impl Sealed for KeyDownEvent {}
25impl InputEvent for KeyDownEvent {
26 fn to_platform_input(self) -> PlatformInput {
27 PlatformInput::KeyDown(self)
28 }
29}
30impl KeyEvent for KeyDownEvent {}
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
40impl Sealed for ModifiersChangedEvent {}
41impl InputEvent for ModifiersChangedEvent {
42 fn to_platform_input(self) -> PlatformInput {
43 PlatformInput::ModifiersChanged(self)
44 }
45}
46impl KeyEvent for ModifiersChangedEvent {}
47
48impl Sealed for TouchEvent {}
49impl InputEvent for TouchEvent {
50 fn to_platform_input(self) -> PlatformInput {
51 PlatformInput::Touch(self)
52 }
53}
54
55impl Sealed for MouseDownEvent {}
56impl InputEvent for MouseDownEvent {
57 fn to_platform_input(self) -> PlatformInput {
58 PlatformInput::MouseDown(self)
59 }
60}
61impl MouseEvent for MouseDownEvent {}
62
63impl Sealed for MouseUpEvent {}
64impl InputEvent for MouseUpEvent {
65 fn to_platform_input(self) -> PlatformInput {
66 PlatformInput::MouseUp(self)
67 }
68}
69
70impl MouseEvent for MouseUpEvent {}
71
72#[derive(Clone, Debug, Default)]
74pub struct MouseClickEvent {
75 pub down: MouseDownEvent,
77
78 pub up: MouseUpEvent,
80}
81
82impl Sealed for MousePressureEvent {}
83impl InputEvent for MousePressureEvent {
84 fn to_platform_input(self) -> PlatformInput {
85 PlatformInput::MousePressure(self)
86 }
87}
88impl MouseEvent for MousePressureEvent {}
89
90#[derive(Clone, Debug, Default)]
92pub struct KeyboardClickEvent {
93 pub button: KeyboardButton,
95
96 pub bounds: Bounds<Pixels>,
98}
99
100#[derive(Clone, Debug, Default)]
103pub struct TouchClickEvent {
104 pub position: Point<Pixels>,
106 pub tap_count: usize,
109 pub long_press: bool,
113}
114
115#[derive(Clone, Debug)]
118pub enum ClickEvent {
119 Mouse(MouseClickEvent),
121 Keyboard(KeyboardClickEvent),
123 Touch(TouchClickEvent),
125}
126
127impl Default for ClickEvent {
128 fn default() -> Self {
129 ClickEvent::Keyboard(KeyboardClickEvent::default())
130 }
131}
132
133impl ClickEvent {
134 pub fn modifiers(&self) -> Modifiers {
139 match self {
140 ClickEvent::Keyboard(_) => Modifiers::default(),
142 ClickEvent::Mouse(event) => event.up.modifiers,
146 ClickEvent::Touch(_) => Modifiers::default(),
148 }
149 }
150
151 pub fn position(&self) -> Point<Pixels> {
157 match self {
158 ClickEvent::Keyboard(event) => event.bounds.bottom_left(),
159 ClickEvent::Mouse(event) => event.up.position,
160 ClickEvent::Touch(event) => event.position,
161 }
162 }
163
164 pub fn mouse_position(&self) -> Option<Point<Pixels>> {
170 match self {
171 ClickEvent::Keyboard(_) => None,
172 ClickEvent::Mouse(event) => Some(event.up.position),
173 ClickEvent::Touch(_) => None,
174 }
175 }
176
177 pub fn is_right_click(&self) -> bool {
182 match self {
183 ClickEvent::Keyboard(_) => false,
184 ClickEvent::Mouse(event) => {
185 event.down.button == MouseButton::Right && event.up.button == MouseButton::Right
186 }
187 ClickEvent::Touch(_) => false,
188 }
189 }
190
191 pub fn is_middle_click(&self) -> bool {
196 match self {
197 ClickEvent::Keyboard(_) => false,
198 ClickEvent::Mouse(event) => {
199 event.down.button == MouseButton::Middle && event.up.button == MouseButton::Middle
200 }
201 ClickEvent::Touch(_) => false,
202 }
203 }
204
205 pub fn is_secondary(&self) -> bool {
210 match self {
211 ClickEvent::Keyboard(_) => false,
212 ClickEvent::Mouse(event) => {
213 event.down.button == MouseButton::Right && event.up.button == MouseButton::Right
214 }
215 ClickEvent::Touch(event) => event.long_press,
216 }
217 }
218
219 pub fn standard_click(&self) -> bool {
225 match self {
226 ClickEvent::Keyboard(_) => true,
227 ClickEvent::Mouse(event) => {
228 event.down.button == MouseButton::Left && event.up.button == MouseButton::Left
229 }
230 ClickEvent::Touch(event) => !event.long_press,
231 }
232 }
233
234 pub fn first_focus(&self) -> bool {
240 match self {
241 ClickEvent::Keyboard(_) => false,
242 ClickEvent::Mouse(event) => event.down.first_mouse,
243 ClickEvent::Touch(_) => false,
244 }
245 }
246
247 pub fn click_count(&self) -> usize {
253 match self {
254 ClickEvent::Keyboard(_) => 1,
255 ClickEvent::Mouse(event) => event.up.click_count,
256 ClickEvent::Touch(event) => event.tap_count,
257 }
258 }
259
260 pub fn is_keyboard(&self) -> bool {
262 match self {
263 ClickEvent::Mouse(_) | ClickEvent::Touch(_) => false,
264 ClickEvent::Keyboard(_) => true,
265 }
266 }
267}
268
269#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug, Default)]
271pub enum KeyboardButton {
272 #[default]
274 Enter,
275 Space,
277}
278
279impl Sealed for MouseMoveEvent {}
280impl InputEvent for MouseMoveEvent {
281 fn to_platform_input(self) -> PlatformInput {
282 PlatformInput::MouseMove(self)
283 }
284}
285impl MouseEvent for MouseMoveEvent {}
286
287impl Sealed for ScrollWheelEvent {}
288impl InputEvent for ScrollWheelEvent {
289 fn to_platform_input(self) -> PlatformInput {
290 PlatformInput::ScrollWheel(self)
291 }
292}
293impl MouseEvent for ScrollWheelEvent {}
294
295impl Sealed for PinchEvent {}
296impl InputEvent for PinchEvent {
297 fn to_platform_input(self) -> PlatformInput {
298 PlatformInput::Pinch(self)
299 }
300}
301impl GestureEvent for PinchEvent {}
302impl MouseEvent for PinchEvent {}
303
304impl Sealed for MouseExitEvent {}
305impl InputEvent for MouseExitEvent {
306 fn to_platform_input(self) -> PlatformInput {
307 PlatformInput::MouseExited(self)
308 }
309}
310
311impl MouseEvent for MouseExitEvent {}
312
313impl Render for ExternalPaths {
314 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
315 Empty
317 }
318}
319
320impl Sealed for FileDropEvent {}
321impl InputEvent for FileDropEvent {
322 fn to_platform_input(self) -> PlatformInput {
323 PlatformInput::FileDrop(self)
324 }
325}
326impl MouseEvent for FileDropEvent {}
327
328#[cfg(test)]
329mod test {
330
331 use crate::{
332 self as gpui, AppContext as _, Context, FocusHandle, InteractiveElement, IntoElement,
333 KeyBinding, Keystroke, Modifiers, ParentElement, Render, TestAppContext, Window, div,
334 };
335
336 struct TestView {
337 saw_key_down: bool,
338 saw_action: bool,
339 focus_handle: FocusHandle,
340 }
341
342 actions!(test_only, [TestAction]);
343
344 impl Render for TestView {
345 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
346 div().id("testview").child(
347 div()
348 .key_context("parent")
349 .on_key_down(cx.listener(|this, _, _, cx| {
350 cx.stop_propagation();
351 this.saw_key_down = true
352 }))
353 .on_action(cx.listener(|this: &mut TestView, _: &TestAction, _, _| {
354 this.saw_action = true
355 }))
356 .child(
357 div()
358 .key_context("nested")
359 .track_focus(&self.focus_handle)
360 .into_element(),
361 ),
362 )
363 }
364 }
365
366 #[gpui::test]
367 fn test_on_events(cx: &mut TestAppContext) {
368 let window = cx.update(|cx| {
369 cx.open_window(Default::default(), |_, cx| {
370 cx.new(|cx| TestView {
371 saw_key_down: false,
372 saw_action: false,
373 focus_handle: cx.focus_handle(),
374 })
375 })
376 .unwrap()
377 });
378
379 cx.update(|cx| {
380 cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
381 });
382
383 window
384 .update(cx, |test_view, window, cx| {
385 window.focus(&test_view.focus_handle, cx)
386 })
387 .unwrap();
388
389 cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap());
390 cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap());
391
392 window
393 .update(cx, |test_view, _, _| {
394 assert!(test_view.saw_key_down || test_view.saw_action);
395 assert!(test_view.saw_key_down);
396 assert!(test_view.saw_action);
397 })
398 .unwrap();
399 }
400
401 #[gpui::test]
402 fn test_multi_modifier_gesture_does_not_dispatch_standalone_modifier_binding(
403 cx: &mut TestAppContext,
404 ) {
405 let (test_view, cx) = cx.add_window_view(|_, cx| TestView {
406 saw_key_down: false,
407 saw_action: false,
408 focus_handle: cx.focus_handle(),
409 });
410
411 cx.update(|_, cx| {
412 cx.bind_keys(vec![KeyBinding::new("shift", TestAction, None)]);
413 });
414 test_view.update_in(cx, |test_view, window, cx| {
415 window.focus(&test_view.focus_handle, cx);
416 });
417
418 cx.simulate_modifiers_change(Modifiers::alt());
419 cx.simulate_modifiers_change(Modifiers::alt() | Modifiers::shift());
420 cx.simulate_modifiers_change(Modifiers::shift());
421 cx.simulate_modifiers_change(Modifiers::none());
422 assert!(!test_view.read_with(cx, |test_view, _| test_view.saw_action));
423
424 cx.simulate_modifiers_change(Modifiers::shift());
425 cx.simulate_modifiers_change(Modifiers::none());
426 assert!(test_view.read_with(cx, |test_view, _| test_view.saw_action));
427 }
428}