gpui_platform/gestures.rs
1//! Platform gesture vocabulary shared by `gpui` and its platform backends.
2
3use gpui_types::{Pixels, px};
4use std::time::Duration;
5
6/// Feel constants consumed by gesture recognizers. Provided on a best-effort
7/// basis, depending on each platform's support, defaulting to GPUI's own
8/// (iOS flavored) values
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub struct GestureTuning {
11 /// Distance a touch may travel before it stops being a potential tap and
12 /// becomes a pan/drag.
13 pub touch_slop: Pixels,
14 /// Maximum interval between taps for them to accumulate a tap count.
15 pub multi_tap_interval: Duration,
16 /// Maximum distance between taps for them to accumulate a tap count.
17 pub multi_tap_slop: Pixels,
18 /// How long a touch must remain within [`Self::touch_slop`] to be
19 /// recognized as a long press.
20 pub long_press_duration: Duration,
21 /// How scroll momentum decelerates after a fling.
22 pub scroll_physics: ScrollPhysics,
23 /// Minimum release velocity, in pixels per second, required to start
24 /// scroll momentum.
25 pub min_fling_velocity: f32,
26}
27
28impl Default for GestureTuning {
29 fn default() -> Self {
30 Self {
31 touch_slop: px(8.),
32 multi_tap_interval: Duration::from_millis(400),
33 multi_tap_slop: px(16.),
34 long_press_duration: Duration::from_millis(500),
35 scroll_physics: ScrollPhysics::ios(),
36 min_fling_velocity: 50.,
37 }
38 }
39}
40
41/// How free scrolling decelerates after a fling.
42///
43/// This models deceleration only. Boundary behavior — bouncing, edge glow,
44/// clamping — is the scroll container's policy: the container is the one that
45/// knows its extents.
46#[derive(Clone, Copy, Debug, PartialEq)]
47pub enum ScrollPhysics {
48 /// Exponential velocity decay, the `UIScrollView` model:
49 /// `velocity(t) = v₀ · decay_per_msᵐˢ`.
50 Exponential {
51 /// Per-millisecond velocity decay factor. `UIScrollView`'s normal
52 /// deceleration rate is `0.998`.
53 decay_per_ms: f32,
54 },
55 /// The friction spline of Android's `OverScroller`: fling duration and
56 /// distance follow a logarithmic deceleration law, and progress along
57 /// the fling follows a cubic-Bezier ease-out curve. Transcribed from
58 /// AOSP's `SplineOverScroller` (Apache-2.0).
59 FrictionSpline {
60 /// The scroll friction coefficient;
61 /// `ViewConfiguration.getScrollFriction()` is `0.015` on Android.
62 friction: f32,
63 /// Pixels per physical inch of the display, in the coordinate space
64 /// the fling runs in. Android folds display density into its
65 /// deceleration coefficient, so the same finger speed flings
66 /// further in pixels on a denser screen.
67 pixels_per_inch: f32,
68 },
69}
70
71impl ScrollPhysics {
72 /// iOS scroll feel: `UIScrollView`'s normal deceleration rate.
73 pub fn ios() -> Self {
74 Self::Exponential {
75 decay_per_ms: 0.998,
76 }
77 }
78
79 /// Android scroll feel: `OverScroller` with stock friction, at Android's
80 /// nominal density of 160 density-independent pixels per inch — the
81 /// right pairing when fling distances are in logical pixels. Platforms
82 /// that fling in physical pixels, or know the display's true density in
83 /// their logical space, should construct
84 /// [`ScrollPhysics::FrictionSpline`] directly.
85 pub fn android() -> Self {
86 Self::FrictionSpline {
87 friction: 0.015,
88 pixels_per_inch: 160.,
89 }
90 }
91}
92
93/// The gesture kinds a platform recognizes natively, rather than leaving to
94/// gpui core's portable recognizers.
95#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
96pub struct GestureKinds {
97 /// Tap (and multi-tap), surfaced as `ClickEvent::Touch`.
98 pub tap: bool,
99 /// Long press, surfaced as `LongPressEvent`.
100 pub long_press: bool,
101 /// Pan/scroll (including fling momentum), surfaced as `ScrollWheelEvent`s.
102 pub pan: bool,
103 /// Pinch to zoom, surfaced as `PinchEvent`s.
104 pub pinch: bool,
105}
106
107impl GestureKinds {
108 /// No gestures; gpui core's portable recognizers handle everything.
109 pub const NONE: Self = Self {
110 tap: false,
111 long_press: false,
112 pan: false,
113 pinch: false,
114 };
115
116 /// All gesture kinds.
117 pub const ALL: Self = Self {
118 tap: true,
119 long_press: true,
120 pan: true,
121 pinch: true,
122 };
123}
124
125/// Platform gesture recognition services.
126///
127/// If your mobile platform supports native gesture recognition, use this
128/// to share it with GPUI.
129pub trait PlatformGestures {
130 /// Feel constants for the portable recognizers on this platform.
131 fn tuning(&self) -> GestureTuning {
132 GestureTuning::default()
133 }
134
135 /// The gesture kinds this platform recognizes natively.
136 fn native_recognizers(&self) -> GestureKinds {
137 GestureKinds::NONE
138 }
139}
140
141/// A no-op [`PlatformGestures`] implementation: no native recognizers and
142/// default tuning. Suitable for desktop platforms and tests.
143pub struct NullPlatformGestures;
144
145impl PlatformGestures for NullPlatformGestures {}