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