1use crate::{
2 div, point, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
3};
4use smallvec::SmallVec;
5use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
6
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct KeyDownEvent {
9 pub keystroke: Keystroke,
10 pub is_held: bool,
11}
12
13#[derive(Clone, Debug)]
14pub struct KeyUpEvent {
15 pub keystroke: Keystroke,
16}
17
18#[derive(Clone, Debug, Default)]
19pub struct ModifiersChangedEvent {
20 pub modifiers: Modifiers,
21}
22
23impl Deref for ModifiersChangedEvent {
24 type Target = Modifiers;
25
26 fn deref(&self) -> &Self::Target {
27 &self.modifiers
28 }
29}
30
31/// The phase of a touch motion event.
32/// Based on the winit enum of the same name.
33#[derive(Clone, Copy, Debug)]
34pub enum TouchPhase {
35 Started,
36 Moved,
37 Ended,
38}
39
40#[derive(Clone, Debug, Default)]
41pub struct MouseDownEvent {
42 pub button: MouseButton,
43 pub position: Point<Pixels>,
44 pub modifiers: Modifiers,
45 pub click_count: usize,
46}
47
48#[derive(Clone, Debug, Default)]
49pub struct MouseUpEvent {
50 pub button: MouseButton,
51 pub position: Point<Pixels>,
52 pub modifiers: Modifiers,
53 pub click_count: usize,
54}
55
56#[derive(Clone, Debug, Default)]
57pub struct ClickEvent {
58 pub down: MouseDownEvent,
59 pub up: MouseUpEvent,
60}
61
62#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
63pub enum MouseButton {
64 Left,
65 Right,
66 Middle,
67 Navigate(NavigationDirection),
68}
69
70impl MouseButton {
71 pub fn all() -> Vec<Self> {
72 vec![
73 MouseButton::Left,
74 MouseButton::Right,
75 MouseButton::Middle,
76 MouseButton::Navigate(NavigationDirection::Back),
77 MouseButton::Navigate(NavigationDirection::Forward),
78 ]
79 }
80}
81
82impl Default for MouseButton {
83 fn default() -> Self {
84 Self::Left
85 }
86}
87
88#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
89pub enum NavigationDirection {
90 Back,
91 Forward,
92}
93
94impl Default for NavigationDirection {
95 fn default() -> Self {
96 Self::Back
97 }
98}
99
100#[derive(Clone, Debug, Default)]
101pub struct MouseMoveEvent {
102 pub position: Point<Pixels>,
103 pub pressed_button: Option<MouseButton>,
104 pub modifiers: Modifiers,
105}
106
107#[derive(Clone, Debug)]
108pub struct ScrollWheelEvent {
109 pub position: Point<Pixels>,
110 pub delta: ScrollDelta,
111 pub modifiers: Modifiers,
112 pub touch_phase: TouchPhase,
113}
114
115impl Deref for ScrollWheelEvent {
116 type Target = Modifiers;
117
118 fn deref(&self) -> &Self::Target {
119 &self.modifiers
120 }
121}
122
123#[derive(Clone, Copy, Debug)]
124pub enum ScrollDelta {
125 Pixels(Point<Pixels>),
126 Lines(Point<f32>),
127}
128
129impl Default for ScrollDelta {
130 fn default() -> Self {
131 Self::Lines(Default::default())
132 }
133}
134
135impl ScrollDelta {
136 pub fn precise(&self) -> bool {
137 match self {
138 ScrollDelta::Pixels(_) => true,
139 ScrollDelta::Lines(_) => false,
140 }
141 }
142
143 pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
144 match self {
145 ScrollDelta::Pixels(delta) => *delta,
146 ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
147 }
148 }
149}
150
151#[derive(Clone, Debug, Default)]
152pub struct MouseExitEvent {
153 pub position: Point<Pixels>,
154 pub pressed_button: Option<MouseButton>,
155 pub modifiers: Modifiers,
156}
157
158impl Deref for MouseExitEvent {
159 type Target = Modifiers;
160
161 fn deref(&self) -> &Self::Target {
162 &self.modifiers
163 }
164}
165
166#[derive(Debug, Clone, Default)]
167pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
168
169impl Render for ExternalPaths {
170 type Element = Div;
171
172 fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
173 div() // Intentionally left empty because the platform will render icons for the dragged files
174 }
175}
176
177#[derive(Debug, Clone)]
178pub enum FileDropEvent {
179 Entered {
180 position: Point<Pixels>,
181 files: ExternalPaths,
182 },
183 Pending {
184 position: Point<Pixels>,
185 },
186 Submit {
187 position: Point<Pixels>,
188 },
189 Exited,
190}
191
192#[derive(Clone, Debug)]
193pub enum InputEvent {
194 KeyDown(KeyDownEvent),
195 KeyUp(KeyUpEvent),
196 ModifiersChanged(ModifiersChangedEvent),
197 MouseDown(MouseDownEvent),
198 MouseUp(MouseUpEvent),
199 MouseMove(MouseMoveEvent),
200 MouseExited(MouseExitEvent),
201 ScrollWheel(ScrollWheelEvent),
202 FileDrop(FileDropEvent),
203}
204
205impl InputEvent {
206 pub fn position(&self) -> Option<Point<Pixels>> {
207 match self {
208 InputEvent::KeyDown { .. } => None,
209 InputEvent::KeyUp { .. } => None,
210 InputEvent::ModifiersChanged { .. } => None,
211 InputEvent::MouseDown(event) => Some(event.position),
212 InputEvent::MouseUp(event) => Some(event.position),
213 InputEvent::MouseMove(event) => Some(event.position),
214 InputEvent::MouseExited(event) => Some(event.position),
215 InputEvent::ScrollWheel(event) => Some(event.position),
216 InputEvent::FileDrop(FileDropEvent::Exited) => None,
217 InputEvent::FileDrop(
218 FileDropEvent::Entered { position, .. }
219 | FileDropEvent::Pending { position, .. }
220 | FileDropEvent::Submit { position, .. },
221 ) => Some(*position),
222 }
223 }
224
225 pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
226 match self {
227 InputEvent::KeyDown { .. } => None,
228 InputEvent::KeyUp { .. } => None,
229 InputEvent::ModifiersChanged { .. } => None,
230 InputEvent::MouseDown(event) => Some(event),
231 InputEvent::MouseUp(event) => Some(event),
232 InputEvent::MouseMove(event) => Some(event),
233 InputEvent::MouseExited(event) => Some(event),
234 InputEvent::ScrollWheel(event) => Some(event),
235 InputEvent::FileDrop(event) => Some(event),
236 }
237 }
238
239 pub fn keyboard_event<'a>(&'a self) -> Option<&'a dyn Any> {
240 match self {
241 InputEvent::KeyDown(event) => Some(event),
242 InputEvent::KeyUp(event) => Some(event),
243 InputEvent::ModifiersChanged(event) => Some(event),
244 InputEvent::MouseDown(_) => None,
245 InputEvent::MouseUp(_) => None,
246 InputEvent::MouseMove(_) => None,
247 InputEvent::MouseExited(_) => None,
248 InputEvent::ScrollWheel(_) => None,
249 InputEvent::FileDrop(_) => None,
250 }
251 }
252}
253
254pub struct FocusEvent {
255 pub blurred: Option<FocusHandle>,
256 pub focused: Option<FocusHandle>,
257}
258
259#[cfg(test)]
260mod test {
261 use crate::{
262 self as gpui, div, Component, Div, FocusHandle, InteractiveComponent, KeyBinding,
263 Keystroke, ParentComponent, Render, Stateful, TestAppContext, VisualContext,
264 };
265
266 struct TestView {
267 saw_key_down: bool,
268 saw_action: bool,
269 focus_handle: FocusHandle,
270 }
271
272 actions!(TestAction);
273
274 impl Render for TestView {
275 type Element = Stateful<Div>;
276
277 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
278 div().id("testview").child(
279 div()
280 .key_context("parent")
281 .on_key_down(cx.callback(|this, _, _| this.saw_key_down = true))
282 .on_action(
283 cx.callback(|this: &mut TestView, _: &TestAction, _| {
284 this.saw_action = true
285 }),
286 )
287 .child(
288 div()
289 .key_context("nested")
290 .track_focus(&self.focus_handle)
291 .render(),
292 ),
293 )
294 }
295 }
296
297 #[gpui::test]
298 fn test_on_events(cx: &mut TestAppContext) {
299 let window = cx.update(|cx| {
300 cx.open_window(Default::default(), |cx| {
301 cx.build_view(|cx| TestView {
302 saw_key_down: false,
303 saw_action: false,
304 focus_handle: cx.focus_handle(),
305 })
306 })
307 });
308
309 cx.update(|cx| {
310 cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
311 });
312
313 window
314 .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
315 .unwrap();
316
317 cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
318
319 window
320 .update(cx, |test_view, _| {
321 assert!(test_view.saw_key_down || test_view.saw_action);
322 assert!(test_view.saw_key_down);
323 assert!(test_view.saw_action);
324 })
325 .unwrap();
326 }
327}