Skip to main content

ferroui_base/input/gesture_recognizers/
pull_gesture_recognizer.rs

1use super::{GestureRecognizer, GestureRecognizerImpl};
2use crate::input::{
3    IPointer, PointerEventArgs, PointerPressedEventArgs, PointerReleasedEventArgs, PointerType, PullDirection,
4    PullGestureEndedEventArgs, PullGestureEventArgs,
5};
6use crate::{
7    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroProperty, Point, Ref,
8    StyledElementImpl, StyledProperty, Vector,
9};
10use std::cell::{Cell, RefCell};
11use std::rc::Rc;
12
13/// The size of the edge strip a pull must start in.
14pub(crate) const MIN_PULL_DETECTION_SIZE: f64 = 50.0;
15
16/// Recognizes the pull gesture: a drag from an edge of the target, as used
17/// by pull-to-refresh.
18#[repr(C)]
19pub struct PullGestureRecognizer {
20    base: GestureRecognizer,
21    initial_position: Cell<Point>,
22    gesture_id: Cell<i32>,
23    tracking: RefCell<Option<Rc<dyn IPointer>>>,
24    pull_in_progress: Cell<bool>,
25}
26
27ferro_class!(PullGestureRecognizer: GestureRecognizer);
28crate::ferro_class_info!(PullGestureRecognizer { new: PullGestureRecognizer::new });
29ferro_impl_classes!(PullGestureRecognizer: FerroObjectImpl, StyledElementImpl);
30
31impl GestureRecognizerImpl for PullGestureRecognizer {
32    fn pointer_capture_lost(this: &Self, pointer: &Rc<dyn IPointer>) {
33        if this.is_tracking(pointer) {
34            this.end_pull();
35        }
36    }
37
38    fn pointer_moved(this: &Self, e: &PointerEventArgs) {
39        if !this.is_tracking(e.pointer()) {
40            return;
41        }
42        let Some(target) = this.target() else { return };
43
44        let current_position = e.get_position(Some(&target));
45        this.capture(e.pointer());
46
47        let initial_position = this.initial_position.get();
48        let pull_direction = this.pull_direction();
49        let mut delta = Vector::default();
50
51        match pull_direction {
52            PullDirection::TopToBottom => {
53                if current_position.y > initial_position.y {
54                    delta = Vector::new(0.0, current_position.y - initial_position.y);
55                }
56            }
57            PullDirection::BottomToTop => {
58                if current_position.y < initial_position.y {
59                    delta = Vector::new(0.0, initial_position.y - current_position.y);
60                }
61            }
62            PullDirection::LeftToRight => {
63                if current_position.x > initial_position.x {
64                    delta = Vector::new(current_position.x - initial_position.x, 0.0);
65                }
66            }
67            PullDirection::RightToLeft => {
68                if current_position.x < initial_position.x {
69                    delta = Vector::new(initial_position.x - current_position.x, 0.0);
70                }
71            }
72        }
73
74        this.pull_in_progress.set(true);
75        let pull_event_args = PullGestureEventArgs::new(this.gesture_id.get(), delta, pull_direction);
76        target.raise_event(&pull_event_args);
77
78        e.set_handled(pull_event_args.handled());
79    }
80
81    fn pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
82        let Some(target) = this.target() else { return };
83
84        if e.pointer().type_() != PointerType::Touch && e.pointer().type_() != PointerType::Pen {
85            return;
86        }
87
88        let position = e.get_position(Some(&target));
89        let bounds = target.bounds();
90
91        let can_pull = match this.pull_direction() {
92            PullDirection::TopToBottom => position.y < MIN_PULL_DETECTION_SIZE.max(bounds.height * 0.1),
93            PullDirection::BottomToTop => {
94                position.y > (bounds.height - MIN_PULL_DETECTION_SIZE).min(bounds.height - (bounds.height * 0.1))
95            }
96            PullDirection::LeftToRight => position.x < MIN_PULL_DETECTION_SIZE.max(bounds.width * 0.1),
97            PullDirection::RightToLeft => {
98                position.x > (bounds.width - MIN_PULL_DETECTION_SIZE).min(bounds.width - (bounds.width * 0.1))
99            }
100        };
101
102        if can_pull {
103            this.gesture_id.set(PullGestureEventArgs::get_next_free_id());
104            drop(this.tracking.replace(Some(e.pointer().clone())));
105            this.initial_position.set(position);
106        }
107    }
108
109    fn pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
110        if this.is_tracking(e.pointer()) && this.pull_in_progress.get() {
111            this.end_pull();
112        }
113    }
114}
115
116crate::ferro_properties! { impl PullGestureRecognizer {
117    ferro_property!(
118        /// Defines the `PullDirection` property.
119        pub fn pull_direction_property() -> StyledProperty<PullDirection> {
120            FerroProperty::register::<PullGestureRecognizer, _>("PullDirection", PullDirection::TopToBottom)
121        }
122    );
123} }
124
125impl PullGestureRecognizer {
126    /// Creates a recognizer for pulls from the top.
127    pub fn new() -> Ref<Self> {
128        instantiate(Self {
129            base: GestureRecognizer::construct(),
130            initial_position: Cell::new(Point::default()),
131            gesture_id: Cell::new(0),
132            tracking: RefCell::new(None),
133            pull_in_progress: Cell::new(false),
134        })
135    }
136
137    /// Creates a recognizer for pulls in the given direction.
138    pub fn with_direction(pull_direction: PullDirection) -> Ref<Self> {
139        let result = Self::new();
140        result.set_pull_direction(pull_direction);
141        result
142    }
143
144    /// The direction the target is pulled in.
145    pub fn pull_direction(&self) -> PullDirection {
146        self.get_value(Self::pull_direction_property())
147    }
148
149    pub fn set_pull_direction(&self, value: PullDirection) {
150        self.set_value(Self::pull_direction_property(), value)
151    }
152
153    fn is_tracking(&self, pointer: &Rc<dyn IPointer>) -> bool {
154        self.tracking.borrow().as_ref().is_some_and(|t| std::ptr::addr_eq(Rc::as_ptr(t), Rc::as_ptr(pointer)))
155    }
156
157    fn end_pull(&self) {
158        drop(self.tracking.replace(None));
159        self.initial_position.set(Point::default());
160        self.pull_in_progress.set(false);
161
162        if let Some(target) = self.target() {
163            target.raise_event(&PullGestureEndedEventArgs::new(self.gesture_id.get(), self.pull_direction()));
164        }
165    }
166}