Skip to main content

gpui_motion_kit/
gesture_native.rs

1//! Native input adapter with a persistent owner and frame-scoped listeners.
2use 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/// Retain one handle per logical control. Clones share input/motion state.
152/// Mouse release outside the hitbox, lost button, Escape, blur and deactivation
153/// clear ownership. Pinch and supported native touch gestures are opt-in; raw
154/// multi-touch availability depends on the backend/platform.
155#[derive(Clone, Default)]
156pub struct GestureState(Rc<RefCell<Data>>);
157
158impl GestureState {
159    /// Adds bounded native pointer/arrow-key dragging. Configure once in the view.
160    pub fn with_drag(self, drag: Drag) -> Self {
161        self.0.borrow_mut().drag = Some(drag);
162
163        self
164    }
165
166    /// Enables stock Kit touch-drag ownership. Claiming a touch prevents the
167    /// containing scroll view from also handling that gesture. Platform delivery
168    /// remains native; this does not synthesize touch input on desktop macOS.
169    pub fn touch_drag(self, enabled: bool) -> Self {
170        self.0.borrow_mut().touch_enabled = enabled;
171
172        self
173    }
174
175    /// Measures drag bounds against a retained native container anchor. Resize
176    /// changes retarget from the current presentation rather than resetting it.
177    pub fn constraints(self, container: MotionAnchor) -> Self {
178        self.0.borrow_mut().constraints = Some(container);
179
180        self
181    }
182
183    /// Reports structured pointer start/move/end/cancel events outside state borrows.
184    /// Also enables a host-controlled pan surface without `with_drag`.
185    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    /// Reports native pinch gestures without assuming ordinary elements can scale.
192    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    /// Starts from a separate handle or scrubber. Available after the target's
202    /// first layout; focus, cancellation and outside release remain target-owned.
203    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    /// Uses another measured viewport for in-view checks, intersected with normal
213    /// ancestor clipping. Missing measurements preserve the previous visibility.
214    pub fn view_root(self, root: MotionAnchor) -> Self {
215        self.0.borrow_mut().view_root = Some(root);
216
217        self
218    }
219
220    /// Insets top/right/bottom/left visibility edges; negative values expand them.
221    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    /// Enables automatically measured clipped viewport visibility. Configure once.
232    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    /// Current resolved interaction flags, supplied automatically by the element.
239    pub fn interaction(&self) -> Interaction {
240        self.0.borrow().interaction
241    }
242
243    /// Last presented target, including drag displacement.
244    pub fn presented(&self) -> Target {
245        self.0.borrow().presented
246    }
247
248    /// The stable focus handle, available after the first layout.
249    pub fn focus_handle(&self) -> Option<FocusHandle> {
250        self.0.borrow().focus.clone()
251    }
252
253    /// Cancels pointer/key state and bounded dragging, notifying the owning view.
254    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    /// Wraps a styled native control and automatically binds hover/press/focus,
262    /// viewport and optional drag. Supply role, label and click behavior on the
263    /// child. The adapter supplies a stable tab-stop focus handle. Configure
264    /// layout styles before binding; x/y are owned by this adapter, not a second
265    /// `LayoutGroup` on the same control. Native activation remains Kit's behavior.
266    ///
267    /// ```
268    /// use gpui_kit::{div, prelude::*, Role};
269    /// use gpui_motion_kit::{GestureState, core::{GestureTargets, Motion, Target}};
270    /// let state = GestureState::default(); // persistent view field
271    /// let control = state.bind("motion-button", div().id("button").role(Role::Button).child("Press"),
272    ///     Motion::new(Target::default()), GestureTargets::default().while_press(Target::default().opacity(0.5)));
273    /// ```
274    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
293/// Frame-scoped input/motion wrapper produced by `GestureState::bind`.
294pub 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    /// Disables automatic target dragging so a separate handle can call `start_drag`.
306    pub fn drag_listener(mut self, enabled: bool) -> Self {
307        self.drag_listener = enabled;
308
309        self
310    }
311
312    /// Disables native input bindings and keyboard tab stops. Cancels a held
313    /// gesture on the next render; the owner restores focus if disabling the
314    /// focused control and supplies disabled accessibility metadata on the child
315    /// using its Kit version’s APIs. Visibility observation remains available.
316    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        // Register key listeners on this wrapper before child paint selects a descendant node.
857        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}