Skip to main content

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 {}