Skip to main content

swiftui_native/views/
gestures.rs

1//! Gestures as values, rather than one modifier per gesture type.
2//!
3//! SwiftUI's gesture API is two halves: a `Gesture` you build and compose, and
4//! `.gesture(_:including:)` that attaches one to a view. The bindings that came
5//! first collapsed the two — `drag_gesture(view, distance)` builds a
6//! `DragGesture` and attaches it in one call, with the mask fixed at `.all` and
7//! no way to say what should happen while the drag is in flight. That covers
8//! `.gesture(DragGesture())` and nothing else.
9//!
10//! Here the gesture is a value:
11//!
12//! ```no_run
13//! # use swiftui_native::views::{self, GestureMask};
14//! // The handler's argument follows from the gesture: a drag gives a
15//! // `DragValue`, a long press a `bool`, a tap nothing.
16//! let g = views::drag(0.0)
17//!     .on_changed(|v| println!("moved {:?}", v.translation()))
18//!     .on_ended(|_| println!("done"));
19//! let v = views::gesture(&views::text("drag me"), &g, GestureMask::All);
20//! ```
21//!
22//! `on_changed` is the half that was missing entirely, and it is the half that
23//! makes a gesture useful: `onEnded` is told what happened after the fact,
24//! `onChanged` is told while it is happening, which is what moving something
25//! under the finger needs.
26
27use super::{
28    opaque_result_type, wrap_owned, DragValue, GestureMask, ViewHandle,
29};
30use crate::abi;
31use crate::resolve;
32use core::ffi::c_void;
33
34/// A built `Gesture` — its bytes, its type, and the type its handlers see.
35///
36/// The `Value` is carried because `onChanged` needs it: that extension is
37/// constrained to `Value: Equatable` and takes the witness as an argument, so
38/// a gesture that does not know its own value type cannot be given a changed
39/// handler. `onEnded` does not need it, which is why the first bindings could
40/// get by without.
41///
42/// Dropping one does *not* destroy the Swift value. Every gesture this module
43/// builds is a plain aggregate of scalars — `DragGesture` is a `CGFloat` and a
44/// coordinate space, `TapGesture` an `Int` — and the combinators wrap those
45/// without adding a reference. The closures are a different matter: they are
46/// leaked on purpose, because SwiftUI stores them.
47pub struct GestureValue<V: GestureData = ()> {
48    pub(crate) value: Vec<u8>,
49    pub(crate) meta: *const c_void,
50    /// `Gesture.Value`, or null where the gesture has none worth passing on.
51    pub(crate) value_meta: *const c_void,
52    /// `V` is what handlers are given, not something this stores.
53    marker: core::marker::PhantomData<fn(V)>,
54}
55
56/// What a gesture hands its handlers, as a Rust type.
57///
58/// The gesture's own type decides this — a drag reports a [`DragValue`], a
59/// long press a `bool` — so a handler's argument follows from the gesture it
60/// is attached to rather than being a case anyone has to match on.
61///
62/// Implemented per type rather than derived: reading the bytes SwiftUI passes
63/// is a decision about *that* `Gesture.Value`'s layout.
64pub trait GestureData: Copy + 'static {
65    /// Read the value SwiftUI passed by address.
66    ///
67    /// # Safety
68    /// `at` must point at a live `Gesture.Value` of the matching Swift type.
69    unsafe fn read(at: *const u8) -> Self;
70}
71
72impl GestureData for () {
73    unsafe fn read(_: *const u8) -> Self {}
74}
75
76impl GestureData for bool {
77    /// `LongPressGesture.Value` — one byte, true once the press completes.
78    unsafe fn read(at: *const u8) -> Self {
79        unsafe { *at != 0 }
80    }
81}
82
83impl GestureData for DragValue {
84    unsafe fn read(at: *const u8) -> Self {
85        unsafe { super::read_drag_value(at) }
86    }
87}
88
89impl GestureData for f64 {
90    /// `MagnifyGesture.Value` and `RotateGesture.Value` both lead with the
91    /// scalar the gesture is about — a magnification, or an angle in radians.
92    unsafe fn read(at: *const u8) -> Self {
93        unsafe {
94            f64::from_le_bytes(
95                core::slice::from_raw_parts(at, 8).try_into().expect("8 bytes"),
96            )
97        }
98    }
99}
100
101impl<V: GestureData> GestureValue<V> {
102    /// The `Gesture` witness for this gesture's type.
103    fn wt(&self) -> *const c_void {
104        let wt = unsafe { abi::get_view_wt_for_proto(self.meta, resolve::gesture_proto()) };
105        assert!(!wt.is_null(), "{}: Gesture", crate::diag::type_name(self.meta));
106        wt
107    }
108}
109
110/// `TapGesture(count:)`.
111pub fn tap(count: i64) -> GestureValue<()> {
112    unsafe {
113        let meta = abi::metadata0(resolve::tap_gesture_ma());
114        assert!(!meta.is_null(), "TapGesture metadata");
115        let mut value = vec![0u8; abi::type_bytes(meta)];
116        // `TapGesture.init(count:)` is `(sret, i64)` — no metatype, unlike the
117        // gestures next door whose initialisers take one in x20. Read from the
118        // IR rather than copied from its neighbour.
119        core::arch::asm!("blr {f}", f = in(reg) resolve::tap_gesture_init(),
120            in("x0") count,
121            in("x8") value.as_mut_ptr(),
122            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
123            lateout("x5") _, lateout("x6") _, lateout("x7") _,
124            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
125            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
126            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
127            clobber_abi("C"),
128        );
129        // `TapGesture.Value` is `Void`, which has no accessor and nothing for
130        // a handler to read.
131        GestureValue { value, meta, value_meta: core::ptr::null(), marker: core::marker::PhantomData }
132    }
133}
134
135/// `LongPressGesture(minimumDuration:maximumDistance:)`.
136pub fn long_press(duration: f64, max_distance: f64) -> GestureValue<bool> {
137    unsafe {
138        let meta = abi::metadata0(resolve::long_press_ma());
139        assert!(!meta.is_null(), "LongPressGesture metadata");
140        let mut value = vec![0u8; abi::type_bytes(meta)];
141        core::arch::asm!("blr {f}", f = in(reg) resolve::long_press_init(),
142            in("d0") duration, in("d1") max_distance,
143            in("x8") value.as_mut_ptr(),
144            in("x20") meta,
145            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
146            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
147            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
148            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
149            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
150        );
151        // `LongPressGesture.Value` is `Bool` — a stdlib type, so its metadata
152        // is the one the runtime already has rather than a `…Ma` call.
153        let value_meta = abi::resolve_type_by_mangled_name(b"Sb");
154        GestureValue { value, meta, value_meta, marker: core::marker::PhantomData }
155    }
156}
157
158/// `DragGesture(minimumDistance:coordinateSpace: .local)`.
159pub fn drag(minimum_distance: f64) -> GestureValue<DragValue> {
160    unsafe {
161        let meta = abi::metadata0(resolve::drag_gesture_ma());
162        assert!(!meta.is_null(), "DragGesture metadata");
163        let space_meta = abi::metadata0(resolve::local_space_ma());
164        let mut value = vec![0u8; abi::type_bytes(meta)];
165        // `init<C: CoordinateSpaceProtocol>(minimumDistance:coordinateSpace:)`:
166        // d0 = distance, x0 = @in C (`LocalCoordinateSpace` is zero-size),
167        // x1 = C.Type, x2 = C: CoordinateSpaceProtocol WT, x8 = @out.
168        let mut space = [0u8; 8];
169        core::arch::asm!("blr {f}", f = in(reg) resolve::drag_gesture_init(),
170            in("d0") minimum_distance,
171            in("x0") space.as_mut_ptr(),
172            in("x1") space_meta,
173            in("x2") resolve::local_space_wt(),
174            in("x8") value.as_mut_ptr(),
175            in("x20") meta,
176            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
177            lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
178            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
179            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
180            clobber_abi("C"),
181        );
182        let value_meta = abi::metadata0(resolve::drag_value_ma());
183        GestureValue { value, meta, value_meta, marker: core::marker::PhantomData }
184    }
185}
186
187/// `MagnifyGesture(minimumScaleDelta:)`.
188pub fn magnify(minimum_scale_delta: f64) -> GestureValue<f64> {
189    unsafe {
190        let meta = abi::metadata0(resolve::magnify_gesture_ma());
191        assert!(!meta.is_null(), "MagnifyGesture metadata");
192        let mut value = vec![0u8; abi::type_bytes(meta)];
193        core::arch::asm!("blr {f}", f = in(reg) resolve::magnify_gesture_init(),
194            in("d0") minimum_scale_delta,
195            in("x8") value.as_mut_ptr(),
196            in("x20") meta,
197            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
198            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
199            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
200            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
201            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
202        );
203        let value_meta = abi::metadata0(resolve::magnify_value_ma());
204        GestureValue { value, meta, value_meta, marker: core::marker::PhantomData }
205    }
206}
207
208/// `RotateGesture(minimumAngleDelta:)`, taking degrees.
209///
210/// SwiftUI's parameter is an `Angle`, which stores radians; degrees are
211/// converted here so that the call site reads the way the documentation does.
212pub fn rotate(minimum_degrees: f64) -> GestureValue<f64> {
213    unsafe {
214        let meta = abi::metadata0(resolve::rotate_gesture_ma());
215        assert!(!meta.is_null(), "RotateGesture metadata");
216        let mut value = vec![0u8; abi::type_bytes(meta)];
217        let radians = minimum_degrees * core::f64::consts::PI / 180.0;
218        core::arch::asm!("blr {f}", f = in(reg) resolve::rotate_gesture_init(),
219            in("d0") radians,
220            in("x8") value.as_mut_ptr(),
221            in("x20") meta,
222            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
223            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
224            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
225            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
226            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
227        );
228        let value_meta = abi::metadata0(resolve::rotate_value_ma());
229        GestureValue { value, meta, value_meta, marker: core::marker::PhantomData }
230    }
231}
232
233/// The `(Value) -> ()` body SwiftUI calls, for one value type.
234///
235/// Monomorphised rather than switching on a stored tag: the value's type is
236/// known where the handler is installed, so reading it is a direct call and
237/// not a match nobody can get wrong twice.
238///
239/// # Safety
240/// `payload` must be a leaked `Box<Box<dyn Fn(V)>>` and `value` a pointer to a
241/// live `V` as Swift lays it out.
242unsafe extern "C" fn invoke_handler<V: GestureData>(value: *const u8, payload: *mut c_void) {
243    let f = unsafe { &*(payload as *const Box<dyn Fn(V)>) };
244    f(unsafe { V::read(value) });
245}
246
247/// Leak a handler for SwiftUI to hold. It outlives the call by design.
248fn leak_handler<V: GestureData>(action: impl Fn(V) + 'static) -> *mut c_void {
249    let boxed: Box<dyn Fn(V)> = Box::new(action);
250    Box::into_raw(Box::new(boxed)) as *mut c_void
251}
252
253impl<V: GestureData> GestureValue<V> {
254    /// `Gesture.onEnded(_:)` — run `action` when the gesture completes.
255    ///
256    /// Returns `_EndedGesture<Self>`, which is itself a `Gesture`, so these
257    /// chain the way they do in Swift.
258    pub fn on_ended(self, action: impl Fn(V) + 'static) -> GestureValue<V> {
259        unsafe {
260            let g_wt = self.wt();
261            let ended_meta = abi::generic_metadata2(
262                resolve::ended_gesture_ma(),
263                self.meta,
264                g_wt,
265            );
266            assert!(!ended_meta.is_null(), "_EndedGesture metadata");
267            let mut out = vec![0u8; abi::type_bytes(ended_meta)];
268
269            let ctx = Box::leak(Box::new(abi::ValueClosureCtx::new(
270                invoke_handler::<V>,
271                leak_handler(action),
272            ))) as *mut abi::ValueClosureCtx;
273
274            let mut this = self.value;
275            // x0/x1 = closure, x2 = Self.Type, x3 = Self: Gesture WT,
276            // x8 = @out, x20 = @in_guaranteed self.
277            core::arch::asm!("blr {f}", f = in(reg) resolve::gesture_on_ended(),
278                in("x0") abi::value_closure_thunk as *const () as u64,
279                in("x1") ctx as u64,
280                in("x2") self.meta,
281                in("x3") g_wt,
282                in("x8") out.as_mut_ptr(),
283                in("x20") this.as_mut_ptr(),
284                lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
285                lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
286                lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
287                lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
288            );
289            // `_EndedGesture<G>.Value` is `G.Value`: the wrapper passes the
290            // value through untouched, so the shape carries over.
291            GestureValue {
292                value: out,
293                meta: ended_meta,
294                value_meta: self.value_meta,
295                marker: core::marker::PhantomData,
296            }
297        }
298    }
299
300    /// `Gesture.onChanged(_:)` — run `action` every time the value moves.
301    ///
302    /// This is a *different* extension from [`GestureValue::on_ended`],
303    /// constrained to `Value: Equatable`, so it takes that witness as a sixth
304    /// argument and returns `_ChangedGesture`. A gesture whose value has no
305    /// metadata here — a tap, whose value is `Void` — cannot have one, and
306    /// says so rather than building something that would not fire.
307    pub fn on_changed(self, action: impl Fn(V) + 'static) -> GestureValue<V> {
308        assert!(
309            !self.value_meta.is_null(),
310            "onChanged needs an Equatable Value; TapGesture's is Void — use on_ended"
311        );
312        unsafe {
313            let g_wt = self.wt();
314            let eq_wt = abi::get_view_wt_for_proto(self.value_meta, resolve::equatable_proto());
315            assert!(!eq_wt.is_null(), "gesture Value: Equatable");
316
317            // `_ChangedGesture<Content>` exports no metadata accessor, only its
318            // descriptor. The argument vector is one word per parameter then
319            // one per conformance requirement, in declaration order:
320            // Content, Content: Gesture, Content.Value: Equatable.
321            let changed_meta = abi::generic_metadata_from_descriptor(
322                resolve::changed_gesture_desc(),
323                &[self.meta, g_wt, eq_wt],
324            );
325            assert!(!changed_meta.is_null(), "_ChangedGesture metadata");
326            let mut out = vec![0u8; abi::type_bytes(changed_meta)];
327
328            let ctx = Box::leak(Box::new(abi::ValueClosureCtx::new(
329                invoke_handler::<V>,
330                leak_handler(action),
331            ))) as *mut abi::ValueClosureCtx;
332
333            let mut this = self.value;
334            // x0/x1 = closure, x2 = Self.Type, x3 = Self: Gesture WT,
335            // x4 = Self.Value: Equatable WT, x8 = @out, x20 = self.
336            core::arch::asm!("blr {f}", f = in(reg) resolve::gesture_on_changed(),
337                in("x0") abi::value_closure_thunk as *const () as u64,
338                in("x1") ctx as u64,
339                in("x2") self.meta,
340                in("x3") g_wt,
341                in("x4") eq_wt,
342                in("x8") out.as_mut_ptr(),
343                in("x20") this.as_mut_ptr(),
344                lateout("x5") _, lateout("x6") _, lateout("x7") _,
345                lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
346                lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
347                lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
348            );
349            GestureValue {
350                value: out,
351                meta: changed_meta,
352                value_meta: self.value_meta,
353                marker: core::marker::PhantomData,
354            }
355        }
356    }
357}
358
359impl<V: GestureData> GestureValue<V> {
360    /// `Gesture.sequenced(before:)` — this one, then the other.
361    ///
362    /// A long press that has to complete before a drag begins is the shape
363    /// this exists for: `long_press(0.5, 10.0).sequenced(drag(0.0))`.
364    pub fn sequenced<W: GestureData>(self, other: GestureValue<W>) -> GestureValue<()> {
365        unsafe {
366            combine(
367                self,
368                other,
369                resolve::gesture_sequenced(),
370                resolve::sequence_gesture_desc(),
371            )
372        }
373    }
374
375    /// `Gesture.exclusively(before:)` — whichever recognises first wins.
376    pub fn exclusively<W: GestureData>(self, other: GestureValue<W>) -> GestureValue<()> {
377        unsafe {
378            combine(
379                self,
380                other,
381                resolve::gesture_exclusively(),
382                resolve::exclusive_gesture_desc(),
383            )
384        }
385    }
386
387    /// `Gesture.simultaneously(with:)` — both may recognise at once.
388    pub fn simultaneously<W: GestureData>(self, other: GestureValue<W>) -> GestureValue<()> {
389        unsafe {
390            combine(
391                self,
392                other,
393                resolve::gesture_simultaneously(),
394                resolve::simultaneous_gesture_desc(),
395            )
396        }
397    }
398}
399
400/// The body of all three combinators, which differ only in the symbol called
401/// and the wrapper they build.
402///
403/// `(x0 = @in other, x1 = Self.Type, x2 = Other.Type, x3 = Self: Gesture WT,
404/// x4 = Other: Gesture WT, x8 = @out, x20 = self)` — read from the IR, where
405/// the two metadata arguments arrive in *declaration* order (the receiver
406/// first), not in the order the call site writes them.
407///
408/// # Safety
409/// `f` and `desc` must be a matching combinator and its result descriptor.
410unsafe fn combine<A: GestureData, B: GestureData>(
411    first: GestureValue<A>,
412    second: GestureValue<B>,
413    f: *const c_void,
414    desc: *const c_void,
415) -> GestureValue<()> {
416    unsafe {
417        let a_wt = first.wt();
418        let b_wt = second.wt();
419        let meta = abi::generic_metadata_from_descriptor(
420            desc,
421            &[first.meta, second.meta, a_wt, b_wt],
422        );
423        assert!(!meta.is_null(), "combined gesture metadata");
424        let mut out = vec![0u8; abi::type_bytes(meta)];
425        let mut lhs = first.value;
426        let mut rhs = second.value;
427        core::arch::asm!("blr {f}", f = in(reg) f,
428            in("x0") rhs.as_mut_ptr(),
429            in("x1") first.meta,
430            in("x2") second.meta,
431            in("x3") a_wt,
432            in("x4") b_wt,
433            in("x8") out.as_mut_ptr(),
434            in("x20") lhs.as_mut_ptr(),
435            lateout("x5") _, lateout("x6") _, lateout("x7") _,
436            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
437            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
438            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
439        );
440        // A combined gesture's `Value` is its own type — an enum for
441        // `SequenceGesture` and `ExclusiveGesture`, a pair of optionals for
442        // `SimultaneousGesture` — and none of them has a Rust counterpart
443        // here. So a handler on one still fires and is told nothing, and
444        // `on_changed` refuses rather than reading bytes of a shape it does
445        // not know.
446        GestureValue {
447            value: out,
448            meta,
449            value_meta: core::ptr::null(),
450            marker: core::marker::PhantomData,
451        }
452    }
453}
454
455/// Attach a gesture through one of the three entry points.
456///
457/// `.gesture`, `.highPriorityGesture` and `.simultaneousGesture` take exactly
458/// the same arguments and differ only in how they compete with the gestures
459/// already on the view, so one body serves all three.
460///
461/// # Safety
462/// `f` and `oret` must be a matching modifier and opaque-result descriptor.
463unsafe fn attach<V: GestureData>(
464    view: &ViewHandle,
465    g: &GestureValue<V>,
466    mask: GestureMask,
467    f: *const c_void,
468    oret: *const c_void,
469) -> ViewHandle {
470    unsafe {
471        let g_wt = g.wt();
472        let subst = [view.meta, g.meta, view.wt, g_wt];
473        let (meta, wt, size) = opaque_result_type(oret, &subst);
474        let mut result = vec![0u8; size];
475        let this = view.value_ptr();
476        // `GestureMask` is a frozen `OptionSet` over `UInt32`, so it travels
477        // as one register-sized raw value rather than by address.
478        let raw = mask_raw(mask);
479        let mut value = g.value.clone();
480        core::arch::asm!("blr {f}", f = in(reg) f,
481            in("x0") value.as_mut_ptr(),
482            in("x1") raw,
483            in("x2") view.meta,
484            in("x3") g.meta,
485            in("x4") view.wt,
486            in("x5") g_wt,
487            in("x8") result.as_mut_ptr(),
488            in("x20") this,
489            lateout("x6") _, lateout("x7") _,
490            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
491            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
492            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
493        );
494        wrap_owned(result, meta, wt)
495    }
496}
497
498/// The `UInt32` behind a `GestureMask` case, read from SwiftUI rather than
499/// assumed: `.all` has been spelled `3` by hand throughout this crate, which
500/// happens to be right and is not something the source says.
501fn mask_raw(mask: GestureMask) -> u64 {
502    unsafe {
503        let meta = abi::metadata0(resolve::gesture_mask_ma());
504        assert!(!meta.is_null(), "GestureMask metadata");
505        super::static_getter(mask.resolved(), meta).0
506    }
507}
508
509/// `.gesture(_:including:)`.
510pub fn gesture<V: GestureData>(
511    view: &ViewHandle,
512    g: &GestureValue<V>,
513    mask: GestureMask,
514) -> ViewHandle {
515    unsafe { attach(view, g, mask, resolve::gesture_fn(), resolve::gesture_oret()) }
516}
517
518/// `.highPriorityGesture(_:including:)` — this gesture wins over the view's own.
519pub fn high_priority_gesture_of<V: GestureData>(
520    view: &ViewHandle,
521    g: &GestureValue<V>,
522    mask: GestureMask,
523) -> ViewHandle {
524    unsafe {
525        attach(
526            view,
527            g,
528            mask,
529            resolve::high_priority_gesture_fn(),
530            resolve::high_priority_gesture_oret(),
531        )
532    }
533}
534
535/// `.simultaneousGesture(_:including:)` — both this and the view's own run.
536pub fn simultaneous_gesture_of<V: GestureData>(
537    view: &ViewHandle,
538    g: &GestureValue<V>,
539    mask: GestureMask,
540) -> ViewHandle {
541    unsafe {
542        attach(
543            view,
544            g,
545            mask,
546            resolve::simultaneous_gesture_fn(),
547            resolve::simultaneous_gesture_oret(),
548        )
549    }
550}