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