Skip to main content

ferroui_base/input/gesture_recognizers/
swipe_gesture_recognizer.rs

1use super::{GestureRecognizer, GestureRecognizerImpl};
2use crate::input::{
3    IPointer, PointerEventArgs, PointerPressedEventArgs, PointerReleasedEventArgs, PointerType,
4    SwipeGestureEndedEventArgs, SwipeGestureEventArgs,
5};
6use crate::platform::IPlatformSettings;
7use crate::{
8    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroLocator, FerroObjectImpl, FerroProperty,
9    LocatorExtensions, Point, Ref, StyledElementImpl, StyledProperty, Vector,
10};
11use std::cell::{Cell, RefCell};
12use std::rc::Rc;
13
14const DEFAULT_TAP_SIZE: f64 = 10.0;
15
16/// A gesture recognizer that detects swipe gestures and raises the swipe
17/// gesture event continuously as the pointer moves.
18#[repr(C)]
19pub struct SwipeGestureRecognizer {
20    base: GestureRecognizer,
21    swiping: Cell<bool>,
22    tracked_root_point: Cell<Point>,
23    tracking: RefCell<Option<Rc<dyn IPointer>>>,
24    id: Cell<i32>,
25    velocity: Cell<Vector>,
26    /// The platform time stamp of the last move, in milliseconds.
27    last_timestamp: Cell<u64>,
28}
29
30ferro_class!(SwipeGestureRecognizer: GestureRecognizer);
31crate::ferro_class_info!(SwipeGestureRecognizer { new: SwipeGestureRecognizer::new });
32ferro_impl_classes!(SwipeGestureRecognizer: FerroObjectImpl, StyledElementImpl);
33
34impl GestureRecognizerImpl for SwipeGestureRecognizer {
35    fn pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
36        if !this.is_enabled() {
37            return;
38        }
39
40        let point = e.get_current_point(None);
41        let type_ = e.pointer().type_();
42
43        if (matches!(type_, PointerType::Touch | PointerType::Pen)
44            || (this.is_mouse_enabled() && type_ == PointerType::Mouse))
45            && point.properties.is_left_button_pressed
46        {
47            this.end_gesture();
48            drop(this.tracking.replace(Some(e.pointer().clone())));
49            this.id.set(SwipeGestureEventArgs::get_next_free_id());
50            this.tracked_root_point.set(point.position);
51            this.velocity.set(Vector::default());
52            this.last_timestamp.set(0);
53        }
54    }
55
56    fn pointer_moved(this: &Self, e: &PointerEventArgs) {
57        if !this.is_tracking(e.pointer()) {
58            return;
59        }
60
61        let root_point = e.get_position(None);
62        let threshold = this.get_effective_threshold();
63        let tracked = this.tracked_root_point.get();
64
65        if !this.swiping.get() {
66            let horizontal_triggered = this.can_horizontally_swipe() && (tracked.x - root_point.x).abs() > threshold;
67            let vertical_triggered = this.can_vertically_swipe() && (tracked.y - root_point.y).abs() > threshold;
68
69            if horizontal_triggered || vertical_triggered {
70                this.swiping.set(true);
71
72                this.tracked_root_point.set(Point::new(
73                    if horizontal_triggered {
74                        tracked.x - if tracked.x >= root_point.x { threshold } else { -threshold }
75                    } else {
76                        root_point.x
77                    },
78                    if vertical_triggered {
79                        tracked.y - if tracked.y >= root_point.y { threshold } else { -threshold }
80                    } else {
81                        root_point.y
82                    },
83                ));
84
85                this.capture(e.pointer());
86            }
87        }
88
89        if this.swiping.get() {
90            let delta = Vector::from(this.tracked_root_point.get() - root_point);
91
92            let now = e.timestamp();
93            if this.last_timestamp.get() > 0 {
94                let elapsed_seconds = now.wrapping_sub(this.last_timestamp.get()) as i64 as f64 / 1000.0;
95                if elapsed_seconds > 0.0 {
96                    let instant_velocity = delta / elapsed_seconds;
97                    this.velocity.set(this.velocity.get() * 0.5 + instant_velocity * 0.5);
98                }
99            }
100            this.last_timestamp.set(now);
101
102            if let Some(target) = this.target() {
103                target.raise_event(&SwipeGestureEventArgs::new(this.id.get(), delta, this.velocity.get()));
104            }
105            this.tracked_root_point.set(root_point);
106            e.set_handled(true);
107        }
108    }
109
110    fn pointer_capture_lost(this: &Self, pointer: &Rc<dyn IPointer>) {
111        if this.is_tracking(pointer) {
112            this.end_gesture();
113        }
114    }
115
116    fn pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
117        if this.is_tracking(e.pointer()) && this.swiping.get() {
118            e.set_handled(true);
119            this.end_gesture();
120        }
121    }
122}
123
124crate::ferro_properties! { impl SwipeGestureRecognizer {
125    ferro_property!(
126        /// Defines the `CanHorizontallySwipe` property.
127        pub fn can_horizontally_swipe_property() -> StyledProperty<bool> {
128            FerroProperty::register::<SwipeGestureRecognizer, _>("CanHorizontallySwipe", false)
129        }
130    );
131
132    ferro_property!(
133        /// Defines the `CanVerticallySwipe` property.
134        pub fn can_vertically_swipe_property() -> StyledProperty<bool> {
135            FerroProperty::register::<SwipeGestureRecognizer, _>("CanVerticallySwipe", false)
136        }
137    );
138
139    ferro_property!(
140        /// Defines the `Threshold` property.
141        pub fn threshold_property() -> StyledProperty<f64> {
142            FerroProperty::register::<SwipeGestureRecognizer, _>("Threshold", 0.0)
143        }
144    );
145
146    ferro_property!(
147        /// Defines the `IsMouseEnabled` property.
148        pub fn is_mouse_enabled_property() -> StyledProperty<bool> {
149            FerroProperty::register::<SwipeGestureRecognizer, _>("IsMouseEnabled", false)
150        }
151    );
152
153    ferro_property!(
154        /// Defines the `IsEnabled` property.
155        pub fn is_enabled_property() -> StyledProperty<bool> {
156            FerroProperty::register::<SwipeGestureRecognizer, _>("IsEnabled", true)
157        }
158    );
159} }
160
161impl SwipeGestureRecognizer {
162    pub fn new() -> Ref<Self> {
163        instantiate(Self {
164            base: GestureRecognizer::construct(),
165            swiping: Cell::new(false),
166            tracked_root_point: Cell::new(Point::default()),
167            tracking: RefCell::new(None),
168            id: Cell::new(0),
169            velocity: Cell::new(Vector::default()),
170            last_timestamp: Cell::new(0),
171        })
172    }
173
174    /// Whether horizontal swipes are tracked.
175    pub fn can_horizontally_swipe(&self) -> bool {
176        self.get_value(Self::can_horizontally_swipe_property())
177    }
178
179    pub fn set_can_horizontally_swipe(&self, value: bool) {
180        self.set_value(Self::can_horizontally_swipe_property(), value)
181    }
182
183    /// Whether vertical swipes are tracked.
184    pub fn can_vertically_swipe(&self) -> bool {
185        self.get_value(Self::can_vertically_swipe_property())
186    }
187
188    pub fn set_can_vertically_swipe(&self, value: bool) {
189        self.set_value(Self::can_vertically_swipe_property(), value)
190    }
191
192    /// The minimum pointer movement in pixels before a swipe is recognized.
193    /// A value of 0 (the default) uses half of the platform's touch tap
194    /// size.
195    pub fn threshold(&self) -> f64 {
196        self.get_value(Self::threshold_property())
197    }
198
199    pub fn set_threshold(&self, value: f64) {
200        self.set_value(Self::threshold_property(), value)
201    }
202
203    /// Whether mouse pointer events trigger swipe gestures. Defaults to
204    /// false; touch and pen are always enabled.
205    pub fn is_mouse_enabled(&self) -> bool {
206        self.get_value(Self::is_mouse_enabled_property())
207    }
208
209    pub fn set_is_mouse_enabled(&self, value: bool) {
210        self.set_value(Self::is_mouse_enabled_property(), value)
211    }
212
213    /// Whether this recognizer responds to pointer events. Defaults to
214    /// true.
215    pub fn is_enabled(&self) -> bool {
216        self.get_value(Self::is_enabled_property())
217    }
218
219    pub fn set_is_enabled(&self, value: bool) {
220        self.set_value(Self::is_enabled_property(), value)
221    }
222
223    fn is_tracking(&self, pointer: &Rc<dyn IPointer>) -> bool {
224        self.tracking.borrow().as_ref().is_some_and(|t| std::ptr::addr_eq(Rc::as_ptr(t), Rc::as_ptr(pointer)))
225    }
226
227    fn end_gesture(&self) {
228        drop(self.tracking.replace(None));
229        if self.swiping.get() {
230            self.swiping.set(false);
231            let ended_args = SwipeGestureEndedEventArgs::new(self.id.get(), self.velocity.get());
232            self.velocity.set(Vector::default());
233            self.last_timestamp.set(0);
234            self.id.set(0);
235            if let Some(target) = self.target() {
236                target.raise_event(&ended_args);
237            }
238        }
239    }
240
241    fn get_effective_threshold(&self) -> f64 {
242        let configured = self.threshold();
243        if configured > 0.0 {
244            return configured;
245        }
246
247        let tap_size = FerroLocator::current()
248            .get_service::<dyn IPlatformSettings>()
249            .map_or(DEFAULT_TAP_SIZE, |settings| settings.get_tap_size(PointerType::Touch).height);
250
251        tap_size / 2.0
252    }
253}