ferroui_base/input/gesture_recognizers/
pull_gesture_recognizer.rs1use 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
13pub(crate) const MIN_PULL_DETECTION_SIZE: f64 = 50.0;
15
16#[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 pub fn pull_direction_property() -> StyledProperty<PullDirection> {
120 FerroProperty::register::<PullGestureRecognizer, _>("PullDirection", PullDirection::TopToBottom)
121 }
122 );
123} }
124
125impl PullGestureRecognizer {
126 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 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 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}