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