1use crate::{
2 div, point, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
3};
4use smallvec::SmallVec;
5use std::{any::Any, fmt::Debug, marker::PhantomData, 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
62pub struct Drag<S, R, V, E>
63where
64 R: Fn(&mut V, &mut ViewContext<V>) -> E,
65 V: 'static,
66 E: IntoElement,
67{
68 pub state: S,
69 pub render_drag_handle: R,
70 view_element_types: PhantomData<(V, E)>,
71}
72
73impl<S, R, V, E> Drag<S, R, V, E>
74where
75 R: Fn(&mut V, &mut ViewContext<V>) -> E,
76 V: 'static,
77 E: Element,
78{
79 pub fn new(state: S, render_drag_handle: R) -> Self {
80 Drag {
81 state,
82 render_drag_handle,
83 view_element_types: Default::default(),
84 }
85 }
86}
87
88#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
89pub enum MouseButton {
90 Left,
91 Right,
92 Middle,
93 Navigate(NavigationDirection),
94}
95
96impl MouseButton {
97 pub fn all() -> Vec<Self> {
98 vec![
99 MouseButton::Left,
100 MouseButton::Right,
101 MouseButton::Middle,
102 MouseButton::Navigate(NavigationDirection::Back),
103 MouseButton::Navigate(NavigationDirection::Forward),
104 ]
105 }
106}
107
108impl Default for MouseButton {
109 fn default() -> Self {
110 Self::Left
111 }
112}
113
114#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
115pub enum NavigationDirection {
116 Back,
117 Forward,
118}
119
120impl Default for NavigationDirection {
121 fn default() -> Self {
122 Self::Back
123 }
124}
125
126#[derive(Clone, Debug, Default)]
127pub struct MouseMoveEvent {
128 pub position: Point<Pixels>,
129 pub pressed_button: Option<MouseButton>,
130 pub modifiers: Modifiers,
131}
132
133impl MouseMoveEvent {
134 pub fn dragging(&self) -> bool {
135 self.pressed_button == Some(MouseButton::Left)
136 }
137}
138
139#[derive(Clone, Debug)]
140pub struct ScrollWheelEvent {
141 pub position: Point<Pixels>,
142 pub delta: ScrollDelta,
143 pub modifiers: Modifiers,
144 pub touch_phase: TouchPhase,
145}
146
147impl Deref for ScrollWheelEvent {
148 type Target = Modifiers;
149
150 fn deref(&self) -> &Self::Target {
151 &self.modifiers
152 }
153}
154
155#[derive(Clone, Copy, Debug)]
156pub enum ScrollDelta {
157 Pixels(Point<Pixels>),
158 Lines(Point<f32>),
159}
160
161impl Default for ScrollDelta {
162 fn default() -> Self {
163 Self::Lines(Default::default())
164 }
165}
166
167impl ScrollDelta {
168 pub fn precise(&self) -> bool {
169 match self {
170 ScrollDelta::Pixels(_) => true,
171 ScrollDelta::Lines(_) => false,
172 }
173 }
174
175 pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
176 match self {
177 ScrollDelta::Pixels(delta) => *delta,
178 ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
179 }
180 }
181
182 pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
183 match (self, other) {
184 (ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
185 ScrollDelta::Pixels(px_a + px_b)
186 }
187
188 (ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => {
189 ScrollDelta::Lines(lines_a + lines_b)
190 }
191
192 _ => other,
193 }
194 }
195}
196
197#[derive(Clone, Debug, Default)]
198pub struct MouseExitEvent {
199 pub position: Point<Pixels>,
200 pub pressed_button: Option<MouseButton>,
201 pub modifiers: Modifiers,
202}
203
204impl Deref for MouseExitEvent {
205 type Target = Modifiers;
206
207 fn deref(&self) -> &Self::Target {
208 &self.modifiers
209 }
210}
211
212#[derive(Debug, Clone, Default)]
213pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
214
215impl ExternalPaths {
216 pub fn paths(&self) -> &[PathBuf] {
217 &self.0
218 }
219}
220
221impl Render for ExternalPaths {
222 fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
223 div() // Intentionally left empty because the platform will render icons for the dragged files
224 }
225}
226
227#[derive(Debug, Clone)]
228pub enum FileDropEvent {
229 Entered {
230 position: Point<Pixels>,
231 paths: ExternalPaths,
232 },
233 Pending {
234 position: Point<Pixels>,
235 },
236 Submit {
237 position: Point<Pixels>,
238 },
239 Exited,
240}
241
242#[derive(Clone, Debug)]
243pub enum InputEvent {
244 KeyDown(KeyDownEvent),
245 KeyUp(KeyUpEvent),
246 ModifiersChanged(ModifiersChangedEvent),
247 MouseDown(MouseDownEvent),
248 MouseUp(MouseUpEvent),
249 MouseMove(MouseMoveEvent),
250 MouseExited(MouseExitEvent),
251 ScrollWheel(ScrollWheelEvent),
252 FileDrop(FileDropEvent),
253}
254
255impl InputEvent {
256 pub fn position(&self) -> Option<Point<Pixels>> {
257 match self {
258 InputEvent::KeyDown { .. } => None,
259 InputEvent::KeyUp { .. } => None,
260 InputEvent::ModifiersChanged { .. } => None,
261 InputEvent::MouseDown(event) => Some(event.position),
262 InputEvent::MouseUp(event) => Some(event.position),
263 InputEvent::MouseMove(event) => Some(event.position),
264 InputEvent::MouseExited(event) => Some(event.position),
265 InputEvent::ScrollWheel(event) => Some(event.position),
266 InputEvent::FileDrop(FileDropEvent::Exited) => None,
267 InputEvent::FileDrop(
268 FileDropEvent::Entered { position, .. }
269 | FileDropEvent::Pending { position, .. }
270 | FileDropEvent::Submit { position, .. },
271 ) => Some(*position),
272 }
273 }
274
275 pub fn mouse_event(&self) -> Option<&dyn Any> {
276 match self {
277 InputEvent::KeyDown { .. } => None,
278 InputEvent::KeyUp { .. } => None,
279 InputEvent::ModifiersChanged { .. } => None,
280 InputEvent::MouseDown(event) => Some(event),
281 InputEvent::MouseUp(event) => Some(event),
282 InputEvent::MouseMove(event) => Some(event),
283 InputEvent::MouseExited(event) => Some(event),
284 InputEvent::ScrollWheel(event) => Some(event),
285 InputEvent::FileDrop(event) => Some(event),
286 }
287 }
288
289 pub fn keyboard_event(&self) -> Option<&dyn Any> {
290 match self {
291 InputEvent::KeyDown(event) => Some(event),
292 InputEvent::KeyUp(event) => Some(event),
293 InputEvent::ModifiersChanged(event) => Some(event),
294 InputEvent::MouseDown(_) => None,
295 InputEvent::MouseUp(_) => None,
296 InputEvent::MouseMove(_) => None,
297 InputEvent::MouseExited(_) => None,
298 InputEvent::ScrollWheel(_) => None,
299 InputEvent::FileDrop(_) => None,
300 }
301 }
302}
303
304#[cfg(test)]
305mod test {
306 use crate::{
307 self as gpui, div, Element, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
308 Keystroke, ParentElement, Render, TestAppContext, VisualContext,
309 };
310
311 struct TestView {
312 saw_key_down: bool,
313 saw_action: bool,
314 focus_handle: FocusHandle,
315 }
316
317 actions!(test, [TestAction]);
318
319 impl Render for TestView {
320 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl Element {
321 div().id("testview").child(
322 div()
323 .key_context("parent")
324 .on_key_down(cx.listener(|this, _, cx| {
325 cx.stop_propagation();
326 this.saw_key_down = true
327 }))
328 .on_action(
329 cx.listener(|this: &mut TestView, _: &TestAction, _| {
330 this.saw_action = true
331 }),
332 )
333 .child(
334 div()
335 .key_context("nested")
336 .track_focus(&self.focus_handle)
337 .into_element(),
338 ),
339 )
340 }
341 }
342
343 #[gpui::test]
344 fn test_on_events(cx: &mut TestAppContext) {
345 let window = cx.update(|cx| {
346 cx.open_window(Default::default(), |cx| {
347 cx.new_view(|cx| TestView {
348 saw_key_down: false,
349 saw_action: false,
350 focus_handle: cx.focus_handle(),
351 })
352 })
353 });
354
355 cx.update(|cx| {
356 cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, Some("parent"))]);
357 });
358
359 window
360 .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
361 .unwrap();
362
363 cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap(), false);
364 cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
365
366 window
367 .update(cx, |test_view, _| {
368 assert!(test_view.saw_key_down || test_view.saw_action);
369 assert!(test_view.saw_key_down);
370 assert!(test_view.saw_action);
371 })
372 .unwrap();
373 }
374}