1use embedder_traits::{TouchId, TouchSequenceId};
6use euclid::{Point2D, Scale, Vector2D};
7use log::{debug, error, warn};
8use rustc_hash::FxHashMap;
9use webrender_api::units::{DeviceIntPoint, DevicePixel, DevicePoint, LayoutVector2D};
10
11use self::TouchSequenceState::*;
12
13const TOUCH_PAN_MIN_SCREEN_PX: f32 = 20.0;
18const FLING_SCALING_FACTOR: f32 = 0.95;
20const FLING_MIN_SCREEN_PX: f32 = 3.0;
22const FLING_MAX_SCREEN_PX: f32 = 4000.0;
24
25pub struct TouchHandler {
26 pub current_sequence_id: TouchSequenceId,
27 touch_sequence_map: FxHashMap<TouchSequenceId, TouchSequenceInfo>,
29}
30
31#[derive(Debug, Eq, PartialEq)]
33pub enum TouchMoveAllowed {
34 Prevented,
36 Allowed,
38 Pending,
40}
41
42pub struct TouchSequenceInfo {
43 pub(crate) state: TouchSequenceState,
45 active_touch_points: Vec<TouchPoint>,
47 handling_touch_move: bool,
52 pub prevent_click: bool,
59 pub prevent_move: TouchMoveAllowed,
63 pending_touch_move_action: Option<TouchMoveAction>,
71}
72
73impl TouchSequenceInfo {
74 fn touch_count(&self) -> usize {
75 self.active_touch_points.len()
76 }
77
78 fn pinch_distance_and_center(&self) -> (f32, Point2D<f32, DevicePixel>) {
79 debug_assert_eq!(self.touch_count(), 2);
80 let p0 = self.active_touch_points[0].point;
81 let p1 = self.active_touch_points[1].point;
82 let center = p0.lerp(p1, 0.5);
83 let distance = (p0 - p1).length();
84
85 (distance, center)
86 }
87
88 fn update_pending_touch_move_action(&mut self, action: TouchMoveAction) {
89 debug_assert!(self.prevent_move == TouchMoveAllowed::Pending);
90
91 if let Some(pre_action) = self.pending_touch_move_action {
92 let combine_action = match (pre_action, action) {
93 (TouchMoveAction::NoAction, _) | (_, TouchMoveAction::NoAction) => action,
94 (TouchMoveAction::Scroll(delta, point), TouchMoveAction::Scroll(delta_new, _)) => {
96 TouchMoveAction::Scroll(delta + delta_new, point)
97 },
98 (
99 TouchMoveAction::Scroll(delta, _),
100 TouchMoveAction::Zoom(magnification, scroll_delta),
101 ) |
102 (
103 TouchMoveAction::Zoom(magnification, scroll_delta),
104 TouchMoveAction::Scroll(delta, _),
105 ) => {
106 TouchMoveAction::Zoom(magnification, delta + scroll_delta)
109 },
110 (
111 TouchMoveAction::Zoom(magnification, scroll_delta),
112 TouchMoveAction::Zoom(magnification_new, scroll_delta_new),
113 ) => TouchMoveAction::Zoom(
114 magnification * magnification_new,
115 scroll_delta + scroll_delta_new,
116 ),
117 };
118 self.pending_touch_move_action = Some(combine_action);
119 } else {
120 self.pending_touch_move_action = Some(action);
121 }
122 }
123
124 fn is_finished(&self) -> bool {
127 matches!(
128 self.state,
129 Finished | Flinging { .. } | PendingFling { .. } | PendingClick(_)
130 )
131 }
132}
133
134#[derive(Clone, Copy, Debug, PartialEq)]
137pub enum TouchMoveAction {
138 Scroll(Vector2D<f32, DevicePixel>, DevicePoint),
140 Zoom(f32, Vector2D<f32, DevicePixel>),
142 NoAction,
144}
145
146#[derive(Clone, Copy, Debug)]
147pub struct TouchPoint {
148 pub id: TouchId,
149 pub point: Point2D<f32, DevicePixel>,
150}
151
152impl TouchPoint {
153 pub fn new(id: TouchId, point: Point2D<f32, DevicePixel>) -> Self {
154 TouchPoint { id, point }
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq)]
160pub(crate) enum TouchSequenceState {
161 Touching,
163 Panning {
165 velocity: Vector2D<f32, DevicePixel>,
166 },
167 Pinching,
169 MultiTouch,
171 PendingFling {
176 velocity: Vector2D<f32, DevicePixel>,
177 cursor: DeviceIntPoint,
178 },
179 Flinging {
181 velocity: Vector2D<f32, DevicePixel>,
182 cursor: DeviceIntPoint,
183 },
184 PendingClick(DevicePoint),
186 Finished,
188}
189
190pub(crate) struct FlingAction {
191 pub delta: LayoutVector2D,
192 pub cursor: DeviceIntPoint,
193}
194
195impl TouchHandler {
196 pub fn new() -> Self {
197 let finished_info = TouchSequenceInfo {
198 state: TouchSequenceState::Finished,
199 active_touch_points: vec![],
200 handling_touch_move: false,
201 prevent_click: false,
202 prevent_move: TouchMoveAllowed::Pending,
203 pending_touch_move_action: None,
204 };
205 let mut touch_sequence_map = FxHashMap::default();
209 touch_sequence_map.insert(TouchSequenceId::new(), finished_info);
210 TouchHandler {
211 current_sequence_id: TouchSequenceId::new(),
212 touch_sequence_map,
213 }
214 }
215
216 pub(crate) fn set_handling_touch_move(&mut self, sequence_id: TouchSequenceId, flag: bool) {
217 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
218 sequence.handling_touch_move = flag;
219 }
220 }
221
222 pub(crate) fn is_handling_touch_move(&self, sequence_id: TouchSequenceId) -> bool {
223 if let Some(sequence) = self.touch_sequence_map.get(&sequence_id) {
224 sequence.handling_touch_move
225 } else {
226 false
227 }
228 }
229
230 pub(crate) fn prevent_click(&mut self, sequence_id: TouchSequenceId) {
231 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
232 sequence.prevent_click = true;
233 } else {
234 warn!("TouchSequenceInfo corresponding to the sequence number has been deleted.");
235 }
236 }
237
238 pub(crate) fn prevent_move(&mut self, sequence_id: TouchSequenceId) {
239 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
240 sequence.prevent_move = TouchMoveAllowed::Prevented;
241 } else {
242 warn!("TouchSequenceInfo corresponding to the sequence number has been deleted.");
243 }
244 }
245
246 pub(crate) fn move_allowed(&mut self, sequence_id: TouchSequenceId) -> bool {
249 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
250 sequence.prevent_move == TouchMoveAllowed::Allowed
251 } else {
252 true
253 }
254 }
255
256 pub(crate) fn pending_touch_move_action(
257 &mut self,
258 sequence_id: TouchSequenceId,
259 ) -> Option<TouchMoveAction> {
260 match self.touch_sequence_map.get(&sequence_id) {
261 Some(sequence) => sequence.pending_touch_move_action,
262 None => None,
263 }
264 }
265
266 pub(crate) fn remove_pending_touch_move_action(&mut self, sequence_id: TouchSequenceId) {
267 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
268 sequence.pending_touch_move_action = None;
269 }
270 }
271
272 pub(crate) fn try_remove_touch_sequence(&mut self, sequence_id: TouchSequenceId) {
274 if let Some(sequence) = self.touch_sequence_map.get(&sequence_id) {
275 if sequence.pending_touch_move_action.is_none() && sequence.state == Finished {
276 self.touch_sequence_map.remove(&sequence_id);
277 }
278 }
279 }
280
281 pub(crate) fn remove_touch_sequence(&mut self, sequence_id: TouchSequenceId) {
282 let old = self.touch_sequence_map.remove(&sequence_id);
283 debug_assert!(old.is_some(), "Sequence already removed?");
284 }
285
286 pub fn try_get_current_touch_sequence(&self) -> Option<&TouchSequenceInfo> {
287 self.touch_sequence_map.get(&self.current_sequence_id)
288 }
289
290 pub fn get_current_touch_sequence_mut(&mut self) -> &mut TouchSequenceInfo {
291 self.touch_sequence_map
292 .get_mut(&self.current_sequence_id)
293 .expect("Current Touch sequence does not exist")
294 }
295
296 fn try_get_current_touch_sequence_mut(&mut self) -> Option<&mut TouchSequenceInfo> {
297 self.touch_sequence_map.get_mut(&self.current_sequence_id)
298 }
299
300 pub(crate) fn get_touch_sequence(&self, sequence_id: TouchSequenceId) -> &TouchSequenceInfo {
301 self.touch_sequence_map
302 .get(&sequence_id)
303 .expect("Touch sequence not found.")
304 }
305 pub(crate) fn get_touch_sequence_mut(
306 &mut self,
307 sequence_id: TouchSequenceId,
308 ) -> Option<&mut TouchSequenceInfo> {
309 self.touch_sequence_map.get_mut(&sequence_id)
310 }
311
312 pub fn on_touch_down(&mut self, id: TouchId, point: Point2D<f32, DevicePixel>) {
313 if !self
315 .touch_sequence_map
316 .contains_key(&self.current_sequence_id) ||
317 self.get_touch_sequence(self.current_sequence_id)
318 .is_finished()
319 {
320 self.current_sequence_id.next();
321 debug!("Entered new touch sequence: {:?}", self.current_sequence_id);
322 let active_touch_points = vec![TouchPoint::new(id, point)];
323 self.touch_sequence_map.insert(
324 self.current_sequence_id,
325 TouchSequenceInfo {
326 state: Touching,
327 active_touch_points,
328 handling_touch_move: false,
329 prevent_click: false,
330 prevent_move: TouchMoveAllowed::Pending,
331 pending_touch_move_action: None,
332 },
333 );
334 } else {
335 debug!("Touch down in sequence {:?}.", self.current_sequence_id);
336 let touch_sequence = self.get_current_touch_sequence_mut();
337 touch_sequence
338 .active_touch_points
339 .push(TouchPoint::new(id, point));
340 match touch_sequence.active_touch_points.len() {
341 2.. => {
342 touch_sequence.state = MultiTouch;
343 },
344 0..2 => {
345 unreachable!("Secondary touch_down event with less than 2 fingers active?");
346 },
347 }
348 touch_sequence.prevent_click = true;
350 }
351 }
352
353 pub fn on_vsync(&mut self) -> Option<FlingAction> {
354 let touch_sequence = self.touch_sequence_map.get_mut(&self.current_sequence_id)?;
355
356 let Flinging { velocity, cursor } = &mut touch_sequence.state else {
357 return None;
358 };
359 if velocity.length().abs() < FLING_MIN_SCREEN_PX {
360 touch_sequence.state = Finished;
361 self.try_remove_touch_sequence(self.current_sequence_id);
364 None
365 } else {
366 *velocity *= FLING_SCALING_FACTOR;
369 debug_assert!(velocity.length() <= FLING_MAX_SCREEN_PX);
370 Some(FlingAction {
371 delta: LayoutVector2D::new(velocity.x, velocity.y),
372 cursor: *cursor,
373 })
374 }
375 }
376
377 pub fn on_touch_move(
378 &mut self,
379 id: TouchId,
380 point: Point2D<f32, DevicePixel>,
381 ) -> TouchMoveAction {
382 let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
386 return TouchMoveAction::NoAction;
387 };
388 let idx = match touch_sequence
389 .active_touch_points
390 .iter_mut()
391 .position(|t| t.id == id)
392 {
393 Some(i) => i,
394 None => {
395 error!("Got a touchmove event for a non-active touch point");
396 return TouchMoveAction::NoAction;
397 },
398 };
399 let old_point = touch_sequence.active_touch_points[idx].point;
400 let delta = point - old_point;
401
402 let action = match touch_sequence.touch_count() {
403 1 => {
404 if let Panning { ref mut velocity } = touch_sequence.state {
405 *velocity += delta;
407 *velocity /= 2.0;
408 touch_sequence.active_touch_points[idx].point = point;
410
411 TouchMoveAction::Scroll(-delta, point)
413 } else if delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX ||
414 delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX
415 {
416 touch_sequence.state = Panning {
417 velocity: Vector2D::new(delta.x, delta.y),
418 };
419 touch_sequence.prevent_click = true;
421 touch_sequence.active_touch_points[idx].point = point;
423
424 TouchMoveAction::Scroll(-delta, point)
426 } else {
427 TouchMoveAction::NoAction
430 }
431 },
432 2 => {
433 if touch_sequence.state == Pinching ||
434 delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX ||
435 delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX
436 {
437 touch_sequence.state = Pinching;
438 let (d0, c0) = touch_sequence.pinch_distance_and_center();
439 touch_sequence.active_touch_points[idx].point = point;
441 let (d1, c1) = touch_sequence.pinch_distance_and_center();
442 let magnification = d1 / d0;
443
444 let scroll_delta = c1 - c0 * Scale::new(magnification);
445
446 TouchMoveAction::Zoom(magnification, -scroll_delta)
448 } else {
449 TouchMoveAction::NoAction
452 }
453 },
454 _ => {
455 touch_sequence.active_touch_points[idx].point = point;
456 touch_sequence.state = MultiTouch;
457 TouchMoveAction::NoAction
458 },
459 };
460 if TouchMoveAction::NoAction != action &&
463 touch_sequence.prevent_move == TouchMoveAllowed::Pending
464 {
465 touch_sequence.update_pending_touch_move_action(action);
466 }
467
468 action
469 }
470
471 pub fn on_touch_up(&mut self, id: TouchId, point: Point2D<f32, DevicePixel>) {
472 let touch_sequence = self.get_current_touch_sequence_mut();
473 let old = match touch_sequence
474 .active_touch_points
475 .iter()
476 .position(|t| t.id == id)
477 {
478 Some(i) => Some(touch_sequence.active_touch_points.swap_remove(i).point),
479 None => {
480 warn!("Got a touch up event for a non-active touch point");
481 None
482 },
483 };
484 match touch_sequence.state {
485 Touching => {
486 if touch_sequence.prevent_click {
487 touch_sequence.state = Finished;
488 } else {
489 touch_sequence.state = PendingClick(point);
490 }
491 },
492 Panning { velocity } => {
493 if velocity.length().abs() >= FLING_MIN_SCREEN_PX {
494 debug!(
496 "Transitioning to Fling. Cursor is {point:?}. Old cursor was {old:?}. \
497 Raw velocity is {velocity:?}."
498 );
499 debug_assert!((point.x as i64) < (i32::MAX as i64));
500 debug_assert!((point.y as i64) < (i32::MAX as i64));
501 let cursor = DeviceIntPoint::new(point.x as i32, point.y as i32);
502 let velocity = (velocity * 2.0).with_max_length(FLING_MAX_SCREEN_PX);
505 match touch_sequence.prevent_move {
506 TouchMoveAllowed::Allowed => {
507 touch_sequence.state = Flinging { velocity, cursor }
508 },
511 TouchMoveAllowed::Pending => {
512 touch_sequence.state = PendingFling { velocity, cursor }
513 },
514 TouchMoveAllowed::Prevented => touch_sequence.state = Finished,
515 }
516 } else {
517 touch_sequence.state = Finished;
518 }
519 },
520 Pinching => {
521 touch_sequence.state = Touching;
522 },
523 MultiTouch => {
524 if touch_sequence.active_touch_points.is_empty() {
526 touch_sequence.state = Finished;
527 }
528 },
529 PendingFling { .. } | Flinging { .. } | PendingClick(_) | Finished => {
530 error!("Touch-up received, but touch handler already in post-touchup state.")
531 },
532 }
533 #[cfg(debug_assertions)]
534 if touch_sequence.active_touch_points.is_empty() {
535 debug_assert!(
536 touch_sequence.is_finished(),
537 "Did not transition to a finished state: {:?}",
538 touch_sequence.state
539 );
540 }
541 debug!(
542 "Touch up with remaining active touchpoints: {:?}, in sequence {:?}",
543 touch_sequence.active_touch_points.len(),
544 self.current_sequence_id
545 );
546 }
547
548 pub fn on_touch_cancel(&mut self, id: TouchId, _point: Point2D<f32, DevicePixel>) {
549 let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
551 return;
552 };
553 match touch_sequence
554 .active_touch_points
555 .iter()
556 .position(|t| t.id == id)
557 {
558 Some(i) => {
559 touch_sequence.active_touch_points.swap_remove(i);
560 },
561 None => {
562 warn!("Got a touchcancel event for a non-active touch point");
563 return;
564 },
565 }
566 if touch_sequence.active_touch_points.is_empty() {
567 touch_sequence.state = Finished;
568 }
569 }
570}