ferroui_base/input/gesture_recognizers/
swipe_gesture_recognizer.rs1use 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#[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 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 pub fn can_horizontally_swipe_property() -> StyledProperty<bool> {
128 FerroProperty::register::<SwipeGestureRecognizer, _>("CanHorizontallySwipe", false)
129 }
130 );
131
132 ferro_property!(
133 pub fn can_vertically_swipe_property() -> StyledProperty<bool> {
135 FerroProperty::register::<SwipeGestureRecognizer, _>("CanVerticallySwipe", false)
136 }
137 );
138
139 ferro_property!(
140 pub fn threshold_property() -> StyledProperty<f64> {
142 FerroProperty::register::<SwipeGestureRecognizer, _>("Threshold", 0.0)
143 }
144 );
145
146 ferro_property!(
147 pub fn is_mouse_enabled_property() -> StyledProperty<bool> {
149 FerroProperty::register::<SwipeGestureRecognizer, _>("IsMouseEnabled", false)
150 }
151 );
152
153 ferro_property!(
154 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 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 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 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 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 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}