Skip to main content

ferroui_base/input/
gestures.rs

1use super::{
2    HoldingRoutedEventArgs, HoldingState, IPointer, InputElement, MouseButton, PointerEventArgs,
3    PointerPressedEventArgs, PointerReleasedEventArgs, PointerType, TappedEventArgs,
4};
5use crate::interactivity::{IRoutedEventArgs, Interactive, RoutingStrategies};
6use crate::threading::{DispatcherPriority, DispatcherTimer};
7use crate::utilities::HandlerList;
8use crate::{FerroObject, Point, Rect, Ref, Size, Thickness, Visual, WeakRef};
9use std::cell::{Cell, RefCell};
10use std::rc::Rc;
11
12#[derive(Clone, Copy, PartialEq, Eq)]
13enum GestureStateType {
14    Pending,
15    Holding,
16    DoubleTapped,
17}
18
19#[derive(Clone)]
20struct GestureState {
21    type_: GestureStateType,
22    pointer: Rc<dyn IPointer>,
23}
24
25type HoldingHandler = dyn Fn(&Interactive, &HoldingRoutedEventArgs);
26type TappedHandler = dyn Fn(&Interactive, &TappedEventArgs);
27
28struct GesturesData {
29    // These events are only used internally and not propagated through a
30    // route. They have routed event args as their target type because their
31    // args are generated from pointer event args.
32    holding: HandlerList<HoldingHandler>,
33    tapped: HandlerList<TappedHandler>,
34    right_tapped: HandlerList<TappedHandler>,
35    double_tapped: HandlerList<TappedHandler>,
36
37    gesture_state: RefCell<Option<GestureState>>,
38    last_press: RefCell<Option<WeakRef<FerroObject>>>,
39    last_press_point: Cell<Point>,
40    hold_cancellation_token: RefCell<Option<Rc<Cell<bool>>>>,
41}
42
43crate::ferro_thread_local! {
44    static DATA: &'static GesturesData = Box::leak(Box::new(GesturesData {
45        holding: HandlerList::new(),
46        tapped: HandlerList::new(),
47        right_tapped: HandlerList::new(),
48        double_tapped: HandlerList::new(),
49        gesture_state: RefCell::new(None),
50        last_press: RefCell::new(None),
51        last_press_point: Cell::new(Point::new(0.0, 0.0)),
52        hold_cancellation_token: RefCell::new(None),
53    }));
54}
55
56fn data() -> &'static GesturesData {
57    DATA.with(|data| *data)
58}
59
60/// Synthesizes the tapped, double-tapped, right-tapped and holding gestures
61/// from pointer events.
62pub struct Gestures;
63
64crate::ferro_static_type!(Gestures);
65
66impl Gestures {
67    /// Subscribes the gesture recognition to the pointer events.
68    fn static_constructor() {
69        InputElement::pointer_pressed_event().route_finished().subscribe(Self::pointer_pressed);
70        InputElement::pointer_released_event().route_finished().subscribe(Self::pointer_released);
71        InputElement::pointer_moved_event().route_finished().subscribe(Self::pointer_moved);
72    }
73
74    pub(crate) fn add_holding(handler: impl Fn(&Interactive, &HoldingRoutedEventArgs) + 'static) {
75        data().holding.add(Rc::new(handler));
76    }
77
78    pub(crate) fn add_tapped(handler: impl Fn(&Interactive, &TappedEventArgs) + 'static) {
79        data().tapped.add(Rc::new(handler));
80    }
81
82    pub(crate) fn add_right_tapped(handler: impl Fn(&Interactive, &TappedEventArgs) + 'static) {
83        data().right_tapped.add(Rc::new(handler));
84    }
85
86    pub(crate) fn add_double_tapped(handler: impl Fn(&Interactive, &TappedEventArgs) + 'static) {
87        data().double_tapped.add(Rc::new(handler));
88    }
89
90    fn invoke_holding(sender: &Interactive, e: &HoldingRoutedEventArgs) {
91        for (_, handler) in data().holding.snapshot().iter() {
92            handler(sender, e);
93        }
94    }
95
96    fn invoke_tapped(list: &HandlerList<TappedHandler>, sender: &Interactive, e: &TappedEventArgs) {
97        for (_, handler) in list.snapshot().iter() {
98            handler(sender, e);
99        }
100    }
101
102    fn gesture_state() -> Option<GestureState> {
103        data().gesture_state.borrow().clone()
104    }
105
106    fn set_gesture_state(value: Option<GestureState>) {
107        let old = data().gesture_state.replace(value);
108        drop(old);
109    }
110
111    fn last_press() -> Option<Ref<FerroObject>> {
112        data().last_press.borrow().as_ref().and_then(WeakRef::upgrade)
113    }
114
115    fn cancel_hold() {
116        let token = data().hold_cancellation_token.borrow_mut().take();
117        if let Some(token) = token {
118            token.set(true);
119        }
120    }
121
122    fn get_captured(args: &dyn IRoutedEventArgs) -> Option<Ref<FerroObject>> {
123        let pointer_event_args = args.downcast_ref::<PointerEventArgs>()?;
124
125        match pointer_event_args.pointer().captured() {
126            Some(captured) => Some(captured.upcast()),
127            None => pointer_event_args.source(),
128        }
129    }
130
131    fn rect_around(point: Point, size: Size) -> Rect {
132        Rect::from_position_size(point, Size::default())
133            .inflate_thickness(Thickness::symmetric(size.width, size.height))
134    }
135
136    fn pointer_pressed(ev: &dyn IRoutedEventArgs) {
137        let Some(source) = Self::get_captured(ev) else { return };
138
139        if ev.route() != RoutingStrategies::BUBBLE {
140            return;
141        }
142
143        let Some(e) = ev.downcast_ref::<PointerPressedEventArgs>() else { return };
144        let Ok(visual) = source.clone().downcast::<Visual>() else { return };
145        let interactive = source.cast::<Interactive>();
146        let data = data();
147
148        if let Some(state) = Self::gesture_state() {
149            if state.type_ == GestureStateType::Holding {
150                if let Some(i) = &interactive {
151                    Self::invoke_holding(
152                        i,
153                        &HoldingRoutedEventArgs::new(
154                            HoldingState::Canceled,
155                            data.last_press_point.get(),
156                            state.pointer.type_(),
157                            e,
158                        ),
159                    );
160                }
161            }
162
163            Self::cancel_hold();
164            Self::set_gesture_state(None);
165        }
166
167        if e.click_count() % 2 == 1 {
168            Self::set_gesture_state(Some(GestureState { type_: GestureStateType::Pending, pointer: e.pointer().clone() }));
169            drop(data.last_press.replace(Some(source.downgrade())));
170            data.last_press_point.set(e.get_position(Some(&visual)));
171
172            let token = Rc::new(Cell::new(false));
173            drop(data.hold_cancellation_token.replace(Some(token.clone())));
174
175            if let Some(settings) = visual.get_platform_settings() {
176                let e = e.clone();
177                let source = source.clone();
178
179                DispatcherTimer::run_once(
180                    move || {
181                        let Some(state) = Self::gesture_state() else { return };
182                        if token.get() {
183                            return;
184                        }
185                        let Some(i) = source.cast::<InputElement>() else { return };
186
187                        if InputElement::get_is_holding_enabled(&i)
188                            && (e.pointer().type_() != PointerType::Mouse
189                                || InputElement::get_is_hold_with_mouse_enabled(&i))
190                        {
191                            let pointer_type = state.pointer.type_();
192                            Self::set_gesture_state(Some(GestureState {
193                                type_: GestureStateType::Holding,
194                                pointer: state.pointer,
195                            }));
196                            Self::invoke_holding(
197                                &i,
198                                &HoldingRoutedEventArgs::new(
199                                    HoldingState::Started,
200                                    self::data().last_press_point.get(),
201                                    pointer_type,
202                                    &e,
203                                ),
204                            );
205                        }
206                    },
207                    settings.hold_wait_duration(),
208                    DispatcherPriority::default(),
209                );
210            }
211        } else if e.click_count() % 2 == 0 && e.get_current_point(Some(&visual)).properties.is_left_button_pressed {
212            if let Some(i) = &interactive {
213                if Self::last_press().is_some_and(|target| target == source) {
214                    Self::set_gesture_state(Some(GestureState {
215                        type_: GestureStateType::DoubleTapped,
216                        pointer: e.pointer().clone(),
217                    }));
218                    Self::invoke_tapped(
219                        &data.double_tapped,
220                        i,
221                        &TappedEventArgs::new(Some(InputElement::double_tapped_event()), e),
222                    );
223                }
224            }
225        }
226    }
227
228    fn pointer_released(ev: &dyn IRoutedEventArgs) {
229        if ev.route() != RoutingStrategies::BUBBLE {
230            return;
231        }
232
233        let Some(e) = ev.downcast_ref::<PointerReleasedEventArgs>() else { return };
234        let source = Self::get_captured(ev);
235        let data = data();
236
237        if let (Some(target), Some(source)) = (Self::last_press(), &source) {
238            let button = e.initial_press_mouse_button();
239
240            if target == *source && matches!(button, MouseButton::Left | MouseButton::Right) {
241                if let (Some(i), Some(target_visual)) = (source.cast::<Interactive>(), target.cast::<Visual>()) {
242                    let point = e.get_current_point(Some(&target_visual));
243                    let tap_size = i
244                        .get_platform_settings()
245                        .map(|settings| settings.get_tap_size(point.pointer.type_()))
246                        .unwrap_or(Size::new(4.0, 4.0));
247                    let tap_rect = Self::rect_around(data.last_press_point.get(), tap_size);
248
249                    if tap_rect.contains_exclusive(point.position) {
250                        let state = Self::gesture_state();
251
252                        if let Some(holding) = state.as_ref().filter(|s| s.type_ == GestureStateType::Holding) {
253                            Self::invoke_holding(
254                                &i,
255                                &HoldingRoutedEventArgs::new(
256                                    HoldingState::Completed,
257                                    data.last_press_point.get(),
258                                    holding.pointer.type_(),
259                                    e,
260                                ),
261                            );
262                            Self::invoke_tapped(
263                                &data.right_tapped,
264                                &i,
265                                &TappedEventArgs::new(Some(InputElement::right_tapped_event()), e),
266                            );
267                        } else if button == MouseButton::Right {
268                            Self::invoke_tapped(
269                                &data.right_tapped,
270                                &i,
271                                &TappedEventArgs::new(Some(InputElement::right_tapped_event()), e),
272                            );
273                        }
274                        // The double-tapped state is needed here to prevent
275                        // invoking the tapped event when double-tapped is
276                        // called.
277                        else if state.as_ref().map(|s| s.type_) != Some(GestureStateType::DoubleTapped) {
278                            Self::invoke_tapped(
279                                &data.tapped,
280                                &i,
281                                &TappedEventArgs::new(Some(InputElement::tapped_event()), e),
282                            );
283                        }
284                    }
285
286                    Self::set_gesture_state(None);
287                }
288            }
289        }
290
291        Self::cancel_hold();
292    }
293
294    fn pointer_moved(ev: &dyn IRoutedEventArgs) {
295        if ev.route() != RoutingStrategies::BUBBLE {
296            return;
297        }
298
299        let Some(e) = ev.downcast_ref::<PointerEventArgs>() else { return };
300        let source = Self::get_captured(ev);
301
302        let Some(target) = Self::last_press() else { return };
303        let Some(state) = Self::gesture_state() else { return };
304
305        if !std::ptr::addr_eq(Rc::as_ptr(e.pointer()), Rc::as_ptr(&state.pointer)) {
306            return;
307        }
308
309        let Some(i) = source.and_then(|source| source.downcast::<Interactive>().ok()) else { return };
310        let Some(target_visual) = target.cast::<Visual>() else { return };
311
312        let point = e.get_current_point(Some(&target_visual));
313        let last_press_point = data().last_press_point.get();
314        let hold_rect = Self::rect_around(last_press_point, Size::new(4.0, 4.0));
315
316        if hold_rect.contains_exclusive(point.position) {
317            return;
318        }
319
320        if state.type_ == GestureStateType::Holding {
321            Self::invoke_holding(
322                &i,
323                &HoldingRoutedEventArgs::new(HoldingState::Canceled, last_press_point, state.pointer.type_(), e),
324            );
325        }
326
327        Self::cancel_hold();
328        Self::set_gesture_state(None);
329    }
330}