1use crate::core::{
3 Drag, DragBounds, DragEvent, GesturePhase, GestureTargets, InView, Interaction, LayoutRect,
4 Motion, PinchGesture, Target,
5};
6
7use crate::{MotionAnchor, MotionState};
8
9use gpui_kit::{
10 AnyElement, App, Bounds, DispatchPhase, Div, Element, ElementId, EntityId, FocusHandle,
11 GlobalElementId, Hitbox, HitboxBehavior, InspectorElementId, IntoElement, KeyDownEvent,
12 KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
13 MouseUpEvent, PinchEvent, Pixels, Stateful, TouchPhase, Window, prelude::*, px,
14};
15
16use std::{
17 cell::RefCell,
18 rc::Rc,
19 time::{Duration, Instant},
20};
21
22type DragCallback = Rc<dyn Fn(DragEvent, &mut Window, &mut App)>;
23type PinchCallback = Rc<dyn Fn(PinchGesture, &mut Window, &mut App)>;
24
25#[derive(Default)]
26struct Data {
27 interaction: Interaction,
28 motion: MotionState,
29 drag: Option<Drag>,
30 focus: Option<FocusHandle>,
31 visibility: Option<InView>,
32 epoch: Option<Instant>,
33 owner: Option<EntityId>,
34 pointer: bool,
35 key: Option<String>,
36 presented: Target,
37 constraints: Option<MotionAnchor>,
38 view_root: Option<MotionAnchor>,
39 view_margins: [f64; 4],
40 enabled: bool,
41 touch: bool,
42 touch_enabled: bool,
43 pointer_origin: [f64; 2],
44 last_pointer: [f64; 2],
45 last_event: Duration,
46 drag_callback: Option<DragCallback>,
47 pending_drag: Option<DragEvent>,
48 pinch_callback: Option<PinchCallback>,
49 pinch_active: bool,
50 pinch_scale: f64,
51 pinch_center: [f64; 2],
52 pending_pinch: Option<PinchGesture>,
53}
54
55impl Data {
56 fn now(&self, cx: &App) -> Duration {
57 self.epoch.map_or(Duration::ZERO, |epoch| {
58 cx.background_executor()
59 .now()
60 .saturating_duration_since(epoch)
61 })
62 }
63
64 fn cancel(&mut self, cx: &App) {
65 let now = self.now(cx);
66
67 if self.pointer {
68 self.record(GesturePhase::Cancelled, self.last_pointer, now);
69 }
70
71 if self.pinch_active {
72 self.pending_pinch = Some(PinchGesture {
73 phase: GesturePhase::Cancelled,
74 center: self.pinch_center,
75 scale: self.pinch_scale,
76 delta: 0.0,
77 });
78 self.pinch_active = false;
79 }
80
81 self.touch = false;
82
83 if let Some(drag) = &mut self.drag {
84 drag.cancel(now);
85 }
86
87 self.pointer = false;
88 self.key = None;
89 self.interaction.pressed = false;
90 self.interaction.dragging = false;
91 }
92
93 fn record(&mut self, phase: GesturePhase, point: [f64; 2], now: Duration) {
94 if self.drag_callback.is_none() {
95 return;
96 }
97
98 let dt = now.saturating_sub(self.last_event).as_secs_f64();
99 let delta = std::array::from_fn(|i| point[i] - self.last_pointer[i]);
100
101 self.pending_drag = Some(DragEvent {
102 phase,
103 point,
104 delta,
105 offset: std::array::from_fn(|i| point[i] - self.pointer_origin[i]),
106 velocity: delta.map(|v| if dt > 0.0 { v / dt } else { 0.0 }),
107 locked_axis: self.drag.as_ref().and_then(Drag::locked_axis),
108 });
109 self.last_pointer = point;
110 self.last_event = now;
111 }
112
113 fn begin(&mut self, pointer: [f64; 2], window: &mut Window, cx: &mut App) -> bool {
114 if !self.enabled || self.pointer || !window.is_window_active() {
115 return false;
116 }
117
118 self.pointer = true;
119 self.touch = false;
120 self.key = None;
121 self.interaction.pressed = true;
122 let now = self.now(cx);
123
124 self.pointer_origin = pointer;
125 self.last_pointer = pointer;
126 self.last_event = now;
127
128 if let Some(drag) = &mut self.drag {
129 drag.begin(pointer, now);
130 self.interaction.dragging = true;
131 }
132
133 self.record(GesturePhase::Started, pointer, now);
134
135 if let Some(focus) = &self.focus {
136 focus.focus(window, cx);
137 }
138
139 self.notify(cx);
140
141 true
142 }
143
144 fn notify(&self, cx: &mut App) {
145 if let Some(owner) = self.owner {
146 cx.notify(owner);
147 }
148 }
149}
150
151#[derive(Clone, Default)]
156pub struct GestureState(Rc<RefCell<Data>>);
157
158impl GestureState {
159 pub fn with_drag(self, drag: Drag) -> Self {
161 self.0.borrow_mut().drag = Some(drag);
162
163 self
164 }
165
166 pub fn touch_drag(self, enabled: bool) -> Self {
170 self.0.borrow_mut().touch_enabled = enabled;
171
172 self
173 }
174
175 pub fn constraints(self, container: MotionAnchor) -> Self {
178 self.0.borrow_mut().constraints = Some(container);
179
180 self
181 }
182
183 pub fn on_drag(self, callback: impl Fn(DragEvent, &mut Window, &mut App) + 'static) -> Self {
186 self.0.borrow_mut().drag_callback = Some(Rc::new(callback));
187
188 self
189 }
190
191 pub fn on_pinch(
193 self,
194 callback: impl Fn(PinchGesture, &mut Window, &mut App) + 'static,
195 ) -> Self {
196 self.0.borrow_mut().pinch_callback = Some(Rc::new(callback));
197
198 self
199 }
200
201 pub fn start_drag(&self, pointer: [f64; 2], window: &mut Window, cx: &mut App) -> bool {
204 assert!(pointer.iter().all(|v| v.is_finite()), "invalid pointer");
205 let started = self.0.borrow_mut().begin(pointer, window, cx);
206
207 emit_pending(&self.0, window, cx);
208
209 started
210 }
211
212 pub fn view_root(self, root: MotionAnchor) -> Self {
215 self.0.borrow_mut().view_root = Some(root);
216
217 self
218 }
219
220 pub fn view_margins(self, margins: [f64; 4]) -> Self {
222 assert!(
223 margins.iter().all(|v| v.is_finite() && v.abs() <= 1e12),
224 "invalid viewport margins"
225 );
226 self.0.borrow_mut().view_margins = margins;
227
228 self
229 }
230
231 pub fn in_view(self, amount: f64, once: bool) -> Self {
233 self.0.borrow_mut().visibility = Some(InView::new(amount, once));
234
235 self
236 }
237
238 pub fn interaction(&self) -> Interaction {
240 self.0.borrow().interaction
241 }
242
243 pub fn presented(&self) -> Target {
245 self.0.borrow().presented
246 }
247
248 pub fn focus_handle(&self) -> Option<FocusHandle> {
250 self.0.borrow().focus.clone()
251 }
252
253 pub fn cancel(&self, cx: &mut App) {
255 let mut data = self.0.borrow_mut();
256
257 data.cancel(cx);
258 data.notify(cx);
259 }
260
261 pub fn bind<E: IntoElement + StatefulInteractiveElement + Styled + 'static>(
275 &self,
276 id: impl Into<ElementId>,
277 child: E,
278 motion: Motion,
279 targets: GestureTargets,
280 ) -> GestureElement<E> {
281 GestureElement {
282 id: id.into(),
283 child: Some(child),
284 state: self.clone(),
285 motion,
286 targets,
287 enabled: true,
288 drag_listener: true,
289 }
290 }
291}
292
293pub struct GestureElement<E = Stateful<Div>> {
295 id: ElementId,
296 child: Option<E>,
297 state: GestureState,
298 motion: Motion,
299 targets: GestureTargets,
300 enabled: bool,
301 drag_listener: bool,
302}
303
304impl<E> GestureElement<E> {
305 pub fn drag_listener(mut self, enabled: bool) -> Self {
307 self.drag_listener = enabled;
308
309 self
310 }
311
312 pub fn enabled(mut self, enabled: bool) -> Self {
317 self.enabled = enabled;
318
319 self
320 }
321}
322
323impl<E: IntoElement + StatefulInteractiveElement + Styled + 'static> IntoElement
324 for GestureElement<E>
325{
326 type Element = Self;
327
328 fn into_element(self) -> Self {
329 self
330 }
331}
332
333impl<E: IntoElement + StatefulInteractiveElement + Styled + 'static> Element for GestureElement<E> {
334 type RequestLayoutState = AnyElement;
335 type PrepaintState = Hitbox;
336
337 fn id(&self) -> Option<ElementId> {
338 Some(self.id.clone())
339 }
340
341 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
342 None
343 }
344
345 fn request_layout(
346 &mut self,
347 _: Option<&GlobalElementId>,
348 _: Option<&InspectorElementId>,
349 w: &mut Window,
350 cx: &mut App,
351 ) -> (LayoutId, AnyElement) {
352 let mut data = self.state.0.borrow_mut();
353
354 data.owner = Some(w.current_view());
355 data.enabled = self.enabled;
356 data.epoch
357 .get_or_insert_with(|| cx.background_executor().now());
358 let focus = data
359 .focus
360 .get_or_insert_with(|| cx.focus_handle().tab_stop(true))
361 .clone()
362 .tab_stop(self.enabled);
363
364 let focused = self.enabled && focus.is_focused(w);
365
366 if !self.enabled
367 || !w.is_window_active()
368 || (!focused && (data.key.is_some() || data.pointer || data.pinch_active))
369 {
370 data.cancel(cx);
371 }
372
373 if !self.enabled || !w.is_window_active() {
374 data.interaction.hovered = false;
375 }
376
377 data.interaction.focused = focused;
378 let now = data.now(cx);
379 let target = self.targets.resolve(self.motion.target(), data.interaction);
380
381 let frame = data
382 .motion
383 .sample(self.motion.with_target(target), now, cx.reduce_motion());
384
385 let mut target = frame.target();
386 let mut active = frame.is_animating();
387
388 if let Some(drag) = &mut data.drag {
389 let offset = drag.sample(now, cx.reduce_motion());
390
391 active |= offset.is_animating();
392 let finite = |a: f32, b: f32| {
393 (f64::from(a) + f64::from(b)).clamp(f64::from(f32::MIN), f64::from(f32::MAX)) as f32
394 };
395
396 target = target
397 .x(finite(target.x_px(), offset.target().x_px()))
398 .y(finite(target.y_px(), offset.target().y_px()));
399 }
400
401 data.presented = target;
402
403 if active {
404 w.request_animation_frame();
405 }
406
407 let child = frame
408 .apply(
409 self.child
410 .take()
411 .expect("one layout")
412 .track_focus(&focus)
413 .tab_index(0),
414 )
415 .left(px(target.x_px()))
416 .top(px(target.y_px()));
417
418 let pending = data.pending_drag.is_some() || data.pending_pinch.is_some();
419
420 drop(data);
421
422 if pending {
423 let shared = self.state.0.clone();
424
425 w.defer(cx, move |w, cx| emit_pending(&shared, w, cx));
426 }
427
428 let mut child = child.into_any_element();
429 let layout = child.request_layout(w, cx);
430
431 (layout, child)
432 }
433
434 fn prepaint(
435 &mut self,
436 _: Option<&GlobalElementId>,
437 _: Option<&InspectorElementId>,
438 bounds: Bounds<Pixels>,
439 child: &mut AnyElement,
440 w: &mut Window,
441 cx: &mut App,
442 ) -> Hitbox {
443 let hitbox = w.insert_hitbox(bounds, HitboxBehavior::Normal);
444 let mut data = self.state.0.borrow_mut();
445
446 let rect = |r: Bounds<Pixels>| {
447 LayoutRect::new(
448 [f64::from(r.origin.x), f64::from(r.origin.y)],
449 [f64::from(r.size.width), f64::from(r.size.height)],
450 )
451 .expect("finite native bounds")
452 };
453
454 let now = data.now(cx);
455
456 if let Some(container) = data.constraints.as_ref().and_then(MotionAnchor::rect) {
457 let presented = data.presented;
458
459 let base = LayoutRect::new(
460 [
461 f64::from(bounds.origin.x) - f64::from(presented.x_px()),
462 f64::from(bounds.origin.y) - f64::from(presented.y_px()),
463 ],
464 [f64::from(bounds.size.width), f64::from(bounds.size.height)],
465 )
466 .expect("finite bounds");
467
468 let limits = DragBounds::within(container, base);
469
470 if let Some(drag) = &mut data.drag {
471 let previous = drag.presentation().0;
472
473 drag.set_bounds(limits.min(), limits.max(), now);
474 let frame = drag.sample(now, cx.reduce_motion());
475
476 if frame.is_animating() || previous != drag.presentation().0 {
477 w.request_animation_frame();
478 }
479 }
480 }
481
482 let clip = rect(w.content_mask().bounds);
483
484 let viewport = data
485 .view_root
486 .as_ref()
487 .map_or(Some(clip), MotionAnchor::rect)
488 .map(|root| root.inset(data.view_margins).intersection(clip));
489
490 if let (Some(visibility), Some(viewport)) = (&mut data.visibility, viewport) {
491 let viewport = Bounds {
492 origin: gpui_kit::point(
493 px(viewport.origin()[0] as f32),
494 px(viewport.origin()[1] as f32),
495 ),
496 size: gpui_kit::size(px(viewport.size()[0] as f32), px(viewport.size()[1] as f32)),
497 };
498
499 let rect = |r: Bounds<Pixels>| {
500 [
501 f64::from(r.origin.x),
502 f64::from(r.origin.y),
503 f64::from(r.size.width),
504 f64::from(r.size.height),
505 ]
506 };
507
508 if visibility.update(rect(viewport), rect(bounds)) {
509 data.interaction.in_view = visibility.is_visible();
510 let owner = data.owner.expect("owner");
511
512 w.defer(cx, move |_, cx| cx.notify(owner));
513 }
514 }
515
516 drop(data);
517 child.prepaint(w, cx);
518
519 hitbox
520 }
521
522 fn paint(
523 &mut self,
524 _: Option<&GlobalElementId>,
525 _: Option<&InspectorElementId>,
526 _: Bounds<Pixels>,
527 child: &mut AnyElement,
528 hitbox: &mut Hitbox,
529 w: &mut Window,
530 cx: &mut App,
531 ) {
532 if !self.enabled {
533 let region = hitbox.clone();
534
535 w.on_mouse_event(move |_: &MouseDownEvent, phase, w, cx| {
536 if phase == DispatchPhase::Capture && region.is_hovered(w) {
537 cx.stop_propagation();
538 }
539 });
540 let region = hitbox.clone();
541
542 w.on_mouse_event(move |_: &MouseUpEvent, phase, w, cx| {
543 if phase == DispatchPhase::Capture && region.is_hovered(w) {
544 cx.stop_propagation();
545 }
546 });
547 let region = hitbox.clone();
548
549 w.on_mouse_event(move |_: &MouseMoveEvent, phase, w, cx| {
550 if phase == DispatchPhase::Capture && region.is_hovered(w) {
551 cx.stop_propagation();
552 }
553 });
554 let focus = self.state.focus_handle();
555
556 w.on_key_event(move |_: &KeyDownEvent, phase, w, cx| {
557 if phase == DispatchPhase::Capture
558 && focus.as_ref().is_some_and(|f| f.is_focused(w))
559 {
560 cx.stop_propagation();
561 }
562 });
563 let focus = self.state.focus_handle();
564
565 w.on_key_event(move |_: &KeyUpEvent, phase, w, cx| {
566 if phase == DispatchPhase::Capture
567 && focus.as_ref().is_some_and(|f| f.is_focused(w))
568 {
569 cx.stop_propagation();
570 }
571 });
572 child.paint(w, cx);
573
574 return;
575 }
576
577 let shared = self.state.0.clone();
578 let region = hitbox.clone();
579
580 let automatic_drag = self.drag_listener;
581
582 w.on_mouse_event(move |event: &MouseDownEvent, phase, w, cx| {
583 if phase == DispatchPhase::Bubble
584 && event.button == MouseButton::Left
585 && region.is_hovered(w)
586 && automatic_drag
587 {
588 let mut data = shared.borrow_mut();
589
590 if data.begin(
591 [f64::from(event.position.x), f64::from(event.position.y)],
592 w,
593 cx,
594 ) && (data.drag.is_some() || data.drag_callback.is_some())
595 {
596 cx.stop_propagation();
597 }
598
599 drop(data);
600 emit_pending(&shared, w, cx);
601 }
602 });
603 let shared = self.state.0.clone();
604 let region = hitbox.clone();
605
606 w.on_mouse_event(move |event: &MouseMoveEvent, phase, w, cx| {
607 if phase != DispatchPhase::Capture {
608 return;
609 }
610
611 let mut data = shared.borrow_mut();
612 let hover = region.is_hovered(w);
613 let changed = data.interaction.hovered != hover;
614
615 data.interaction.hovered = hover;
616
617 if data.pointer && !data.touch {
618 let now = data.now(cx);
619
620 if event.pressed_button != Some(MouseButton::Left) {
621 data.cancel(cx);
622 } else if let Some(drag) = &mut data.drag {
623 drag.move_to(
624 [f64::from(event.position.x), f64::from(event.position.y)],
625 now,
626 );
627 }
628
629 if data.pointer {
630 data.record(
631 GesturePhase::Moved,
632 [f64::from(event.position.x), f64::from(event.position.y)],
633 now,
634 );
635 }
636
637 data.notify(cx);
638 } else if changed {
639 data.notify(cx);
640 }
641
642 drop(data);
643 emit_pending(&shared, w, cx);
644 });
645 let shared = self.state.0.clone();
646
647 w.on_mouse_event(move |event: &MouseUpEvent, phase, w, cx| {
648 if phase == DispatchPhase::Capture && event.button == MouseButton::Left {
649 let mut data = shared.borrow_mut();
650
651 if !data.pointer || data.touch {
652 return;
653 }
654
655 let now = data.now(cx);
656
657 if let Some(drag) = &mut data.drag {
658 drag.end(now);
659 }
660
661 data.record(
662 GesturePhase::Ended,
663 [f64::from(event.position.x), f64::from(event.position.y)],
664 now,
665 );
666 data.pointer = false;
667 data.interaction.pressed = false;
668 data.interaction.dragging = false;
669 data.notify(cx);
670 drop(data);
671 emit_pending(&shared, w, cx);
672 }
673 });
674 let shared = self.state.0.clone();
675
676 w.on_mouse_event(move |_: &MouseExitEvent, phase, _, cx| {
677 if phase == DispatchPhase::Capture {
678 let mut data = shared.borrow_mut();
679
680 if data.interaction.hovered {
681 data.interaction.hovered = false;
682 data.notify(cx);
683 }
684 }
685 });
686 let shared = self.state.0.clone();
687
688 w.on_key_event(move |event: &KeyDownEvent, phase, w, cx| {
689 if phase != DispatchPhase::Capture {
690 return;
691 }
692
693 let mut data = shared.borrow_mut();
694
695 if !data.focus.as_ref().is_some_and(|f| f.is_focused(w)) {
696 return;
697 }
698
699 let key = event.keystroke.key.as_str();
700 let now = data.now(cx);
701
702 if key == "escape" {
703 data.cancel(cx);
704 data.notify(cx);
705 drop(data);
706 emit_pending(&shared, w, cx);
707 } else if ["enter", "space"].contains(&key) && !event.is_held && !data.pointer {
708 data.key = Some(key.to_owned());
709 data.interaction.pressed = true;
710 data.notify(cx);
711 } else if let Some(delta) = match key {
712 "left" => Some([-10., 0.]),
713 "right" => Some([10., 0.]),
714 "up" => Some([0., -10.]),
715 "down" => Some([0., 10.]),
716 _ => None,
717 } && let Some(drag) = &mut data.drag
718 {
719 drag.move_by(delta, now);
720 data.notify(cx);
721 cx.stop_propagation();
722 }
723 });
724 let shared = self.state.0.clone();
725
726 w.on_key_event(move |event: &KeyUpEvent, phase, _, cx| {
727 if phase == DispatchPhase::Capture {
728 let mut data = shared.borrow_mut();
729
730 if data.key.as_deref() == Some(event.keystroke.key.as_str()) {
731 data.key = None;
732 data.interaction.pressed = data.pointer;
733 data.notify(cx);
734 }
735 }
736 });
737 let shared = self.state.0.clone();
738 let region = hitbox.clone();
739
740 w.on_mouse_event(move |event: &PinchEvent, phase, w, cx| {
741 if phase != DispatchPhase::Bubble || !event.delta.is_finite() {
742 return;
743 }
744
745 let mut data = shared.borrow_mut();
746
747 if data.pinch_callback.is_none() {
748 return;
749 }
750
751 if event.phase == TouchPhase::Started {
752 if !region.is_hovered(w) {
753 return;
754 }
755
756 data.pinch_active = true;
757 data.pinch_scale = 1.0;
758
759 if let Some(focus) = &data.focus {
760 focus.focus(w, cx);
761 }
762 }
763
764 if !data.pinch_active {
765 return;
766 }
767
768 data.pinch_scale = (data.pinch_scale * (1.0 + f64::from(event.delta))).clamp(0.0, 1e6);
769 data.pinch_center = [f64::from(event.position.x), f64::from(event.position.y)];
770 let phase = match event.phase {
771 TouchPhase::Started => GesturePhase::Started,
772 TouchPhase::Moved => GesturePhase::Moved,
773 TouchPhase::Ended => GesturePhase::Ended,
774 TouchPhase::Cancelled => GesturePhase::Cancelled,
775 };
776
777 data.pending_pinch = Some(PinchGesture {
778 phase,
779 center: data.pinch_center,
780 scale: data.pinch_scale,
781 delta: f64::from(event.delta),
782 });
783
784 if matches!(event.phase, TouchPhase::Ended | TouchPhase::Cancelled) {
785 data.pinch_active = false;
786 }
787
788 data.notify(cx);
789 drop(data);
790 emit_pending(&shared, w, cx);
791 cx.stop_propagation();
792 });
793 let shared = self.state.0.clone();
794 let region = hitbox.clone();
795 let automatic_drag = self.drag_listener;
796
797 w.on_mouse_event(move |event: &gpui_kit::TouchDragEvent, phase, w, cx| {
798 if phase != DispatchPhase::Bubble {
799 return;
800 }
801
802 let mut data = shared.borrow_mut();
803
804 if !data.enabled || !data.touch_enabled {
805 return;
806 }
807
808 let point = [f64::from(event.position.x), f64::from(event.position.y)];
809 let now = data.now(cx);
810
811 match event.phase {
812 TouchPhase::Started => {
813 if !automatic_drag
814 || !region.is_hovered(w)
815 || (data.drag.is_none() && data.drag_callback.is_none())
816 {
817 return;
818 }
819
820 if data.begin(point, w, cx) {
821 data.touch = true;
822 w.prevent_default();
823 }
824 }
825 TouchPhase::Moved if data.touch => {
826 if let Some(drag) = &mut data.drag {
827 drag.move_to(point, now);
828 }
829
830 data.record(GesturePhase::Moved, point, now);
831 data.notify(cx);
832 }
833 TouchPhase::Ended if data.touch => {
834 if let Some(drag) = &mut data.drag {
835 drag.end(now);
836 }
837
838 data.record(GesturePhase::Ended, point, now);
839 data.touch = false;
840 data.pointer = false;
841 data.interaction.pressed = false;
842 data.interaction.dragging = false;
843 data.notify(cx);
844 }
845 TouchPhase::Cancelled if data.touch => {
846 data.cancel(cx);
847 data.notify(cx);
848 }
849 _ => return,
850 }
851
852 drop(data);
853 emit_pending(&shared, w, cx);
854 cx.stop_propagation();
855 });
856 child.paint(w, cx);
858 }
859}
860
861fn emit_pending(shared: &Rc<RefCell<Data>>, window: &mut Window, cx: &mut App) {
862 let (drag, drag_callback, pinch, pinch_callback) = {
863 let mut data = shared.borrow_mut();
864
865 (
866 data.pending_drag.take(),
867 data.drag_callback.clone(),
868 data.pending_pinch.take(),
869 data.pinch_callback.clone(),
870 )
871 };
872
873 if let (Some(event), Some(callback)) = (drag, drag_callback) {
874 callback(event, window, cx);
875 }
876
877 if let (Some(event), Some(callback)) = (pinch, pinch_callback) {
878 callback(event, window, cx);
879 }
880}