makepad_widgets/
touch_gesture.rs1use crate::{
2 Area,
3 Cx,
4 Event,
5 Hit,
6 MouseCursor,
7 NextFrame,
8};
9
10#[derive(Clone, Copy, Debug)]
11struct ScrollSample{
12 abs: f64,
13 time: f64,
14}
15
16#[derive(Default, Clone, Debug)]
17pub enum ScrollMode {
18 #[default]
19 DragAndDrop,
20 Swipe,
21}
22
23#[derive(Default, Clone, Debug)]
24enum ScrollState {
25 #[default]
26 Stopped,
27 Drag{samples:Vec<ScrollSample>},
28 Flick {delta: f64, next_frame: NextFrame},
29 Pulldown {next_frame: NextFrame},
30}
31
32#[derive(Default, PartialEq)]
33pub enum TouchMotionChange {
34 #[default]
35 None,
36 ScrollStateChanged,
37 ScrolledAtChanged,
38}
39
40#[derive(Default, Clone)]
41pub struct TouchGesture {
42 flick_scroll_minimum: f64,
43 flick_scroll_maximum: f64,
44 flick_scroll_scaling: f64,
45 flick_scroll_decay: f64,
46
47 scroll_mode: ScrollMode,
48 scroll_state: ScrollState,
49
50 min_scrolled_at: f64,
51 max_scrolled_at: f64,
52 pulldown_maximum: f64,
53
54 pub scrolled_at: f64,
55}
56
57impl TouchGesture {
58 pub fn new() -> Self {
59 Self {
60 flick_scroll_minimum: 0.2,
61 flick_scroll_maximum: 80.0,
62 flick_scroll_scaling: 0.005,
63 flick_scroll_decay: 0.98,
64
65 scroll_state: ScrollState::Stopped,
66 scroll_mode: ScrollMode::DragAndDrop,
67
68 scrolled_at: 0.0,
69 min_scrolled_at: f64::MIN,
70 max_scrolled_at: f64::MAX,
71 pulldown_maximum: 60.0,
72 }
73 }
74
75 pub fn reset_scrolled_at(&mut self) {
76 self.scrolled_at = 0.0;
77 }
78
79 pub fn set_mode(&mut self, scroll_mode: ScrollMode) {
80 self.scroll_mode = scroll_mode;
81 }
82
83 pub fn set_range(&mut self, min_offset: f64, max_offset: f64) {
84 self.min_scrolled_at = min_offset;
85 self.max_scrolled_at = max_offset;
86 self.scrolled_at = self.scrolled_at.clamp(
87 self.min_scrolled_at - self.pulldown_maximum,
88 self.max_scrolled_at + self.pulldown_maximum
89 );
90 }
91
92 pub fn stop(&mut self) {
93 self.scrolled_at = 0.0;
94 self.scroll_state = ScrollState::Stopped;
95 }
96
97 pub fn is_stopped(&self) -> bool {
98 match self.scroll_state {
99 ScrollState::Stopped => true,
100 _ => false
101 }
102 }
103
104 pub fn is_dragging(&self) -> bool {
105 match self.scroll_state {
106 ScrollState::Drag {..} => true,
107 _ => false
108 }
109 }
110
111 pub fn handle_event(&mut self, cx: &mut Cx, event: &Event, area: Area) -> TouchMotionChange {
112 let needs_pulldown_when_flicking = self.needs_pulldown_when_flicking();
113 let needs_pulldown = self.needs_pulldown();
114
115 match &mut self.scroll_state {
116 ScrollState::Flick {delta, next_frame} => {
117 if let Some(_) = next_frame.is_event(event) {
118 *delta = *delta * self.flick_scroll_decay;
119 if needs_pulldown_when_flicking {
120 self.scroll_state = ScrollState::Pulldown {next_frame: cx.new_next_frame()};
121 return TouchMotionChange::ScrollStateChanged
122 } else if delta.abs() > self.flick_scroll_minimum {
123 *next_frame = cx.new_next_frame();
124 let delta = *delta;
125
126 let new_offset = self.scrolled_at - delta;
127 self.scrolled_at = new_offset.clamp(
128 self.min_scrolled_at - self.pulldown_maximum,
129 self.max_scrolled_at + self.pulldown_maximum
130 );
131
132 return TouchMotionChange::ScrolledAtChanged
133 } else {
134 if needs_pulldown {
135 self.scroll_state = ScrollState::Pulldown {next_frame: cx.new_next_frame()};
136 } else {
137 self.scroll_state = ScrollState::Stopped;
138 }
139
140 return TouchMotionChange::ScrollStateChanged
141 }
142 }
143 }
144 ScrollState::Pulldown {next_frame} => {
145 if let Some(_) = next_frame.is_event(event) {
146 if self.scrolled_at < self.min_scrolled_at {
147 self.scrolled_at += (self.min_scrolled_at - self.scrolled_at) * 0.1;
148 if self.min_scrolled_at - self.scrolled_at < 1.0 {
149 self.scrolled_at = self.min_scrolled_at + 0.5;
150 }
151 else {
152 *next_frame = cx.new_next_frame();
153 }
154
155 return TouchMotionChange::ScrolledAtChanged
156 }
157 else if self.scrolled_at > self.max_scrolled_at {
158 self.scrolled_at -= (self.scrolled_at - self.max_scrolled_at) * 0.1;
159 if self.scrolled_at - self.max_scrolled_at < 1.0 {
160 self.scrolled_at = self.max_scrolled_at - 0.5;
161
162 return TouchMotionChange::ScrolledAtChanged
163 }
164 else {
165 *next_frame = cx.new_next_frame();
166 }
167
168 return TouchMotionChange::ScrolledAtChanged
169 }
170 else {
171 self.scroll_state = ScrollState::Stopped;
172 return TouchMotionChange::ScrollStateChanged
173 }
174 }
175 }
176 _=>()
177 }
178
179 match event.hits_with_capture_overload(cx, area, true) {
180 Hit::FingerDown(e) => {
181 self.scroll_state = ScrollState::Drag {
182 samples: vec![ScrollSample{abs: e.abs.y, time: e.time}]
183 };
184
185 return TouchMotionChange::ScrollStateChanged
186 }
187 Hit::FingerMove(e) => {
188 cx.set_cursor(MouseCursor::Default);
189 match &mut self.scroll_state {
190 ScrollState::Drag {samples}=>{
191 let new_abs = e.abs.y;
192 let old_sample = *samples.last().unwrap();
193 samples.push(ScrollSample{abs: new_abs, time: e.time});
194 if samples.len() > 4 {
195 samples.remove(0);
196 }
197 let new_offset = self.scrolled_at + old_sample.abs - new_abs;
198 self.scrolled_at = new_offset.clamp(
199 self.min_scrolled_at - self.pulldown_maximum,
200 self.max_scrolled_at + self.pulldown_maximum
201 );
202
203 return TouchMotionChange::ScrolledAtChanged
204 }
205 _=>()
206 }
207 }
208 Hit::FingerUp(_e) => {
209 match &mut self.scroll_state {
210 ScrollState::Drag {samples} => {
211 match self.scroll_mode {
212 ScrollMode::Swipe => {
213 let mut last = None;
214 let mut scaled_delta = 0.0;
215 let mut total_delta = 0.0;
216 for sample in samples.iter().rev() {
217 if last.is_none() {
218 last = Some(sample);
219 }
220 else {
221 total_delta += last.unwrap().abs - sample.abs;
222 scaled_delta += (last.unwrap().abs - sample.abs)/ (last.unwrap().time - sample.time)
223 }
224 }
225 scaled_delta *= self.flick_scroll_scaling;
226
227 if self.needs_pulldown() {
228 self.scroll_state = ScrollState::Pulldown {next_frame: cx.new_next_frame()};
229 }
230 else if total_delta.abs() > 10.0 && scaled_delta.abs() > self.flick_scroll_minimum {
231 self.scroll_state = ScrollState::Flick {
232 delta: scaled_delta.min(self.flick_scroll_maximum).max(-self.flick_scroll_maximum),
233 next_frame: cx.new_next_frame()
234 };
235 } else {
236 self.scroll_state = ScrollState::Stopped;
237 }
238
239 return TouchMotionChange::ScrollStateChanged
240 }
241 ScrollMode::DragAndDrop => {
242 self.scroll_state = ScrollState::Stopped;
243 return TouchMotionChange::ScrollStateChanged
244 }
245 }
246 }
247 _=>()
248 }
249 }
250 _ => ()
251 }
252
253 TouchMotionChange::None
254 }
255
256 fn needs_pulldown(&self) -> bool {
257 self.scrolled_at < self.min_scrolled_at || self.scrolled_at > self.max_scrolled_at
258 }
259
260 fn needs_pulldown_when_flicking(&self) -> bool {
261 self.scrolled_at - 0.5 < self.min_scrolled_at - self.pulldown_maximum ||
262 self.scrolled_at + 0.5 > self.max_scrolled_at + self.pulldown_maximum
263 }
264}
265
266impl TouchMotionChange {
267 pub fn has_changed(&self) -> bool {
268 match self {
269 TouchMotionChange::None => false,
270 _ => true
271 }
272 }
273}