1use std::cell::{Cell, RefCell};
6use std::rc::Rc;
7
8use base::id::WebViewId;
9use embedder_traits::{CompositorHitTestResult, InputEventId, Scroll, TouchEventType, TouchId};
10use euclid::{Point2D, Scale, Vector2D};
11use log::{debug, error, warn};
12use rustc_hash::FxHashMap;
13use style_traits::CSSPixel;
14use webrender_api::units::{DevicePixel, DevicePoint, DeviceVector2D};
15
16use self::TouchSequenceState::*;
17use crate::compositor::RepaintReason;
18use crate::painter::Painter;
19use crate::refresh_driver::{BaseRefreshDriver, RefreshDriverObserver};
20use crate::webview_renderer::{ScrollEvent, ScrollZoomEvent, WebViewRenderer};
21
22#[repr(transparent)]
25#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
26pub(crate) struct TouchSequenceId(u32);
27
28impl TouchSequenceId {
29 const fn new() -> Self {
30 Self(0)
31 }
32
33 fn next(&mut self) {
39 self.0 = self.0.wrapping_add(1);
40 }
41}
42
43const TOUCH_PAN_MIN_SCREEN_PX: f32 = 20.0;
48const FLING_SCALING_FACTOR: f32 = 0.95;
50const FLING_MIN_SCREEN_PX: f32 = 3.0;
52const FLING_MAX_SCREEN_PX: f32 = 4000.0;
54
55pub struct TouchHandler {
56 webview_id: WebViewId,
58 pub current_sequence_id: TouchSequenceId,
59 touch_sequence_map: FxHashMap<TouchSequenceId, TouchSequenceInfo>,
61 pub(crate) pending_touch_input_events: RefCell<FxHashMap<InputEventId, PendingTouchInputEvent>>,
64 observing_frames_for_fling: Cell<bool>,
66}
67
68#[derive(Debug, Eq, PartialEq)]
70pub enum TouchMoveAllowed {
71 Prevented,
73 Allowed,
75 Pending,
77}
78
79struct HitTestResultCache {
83 value: CompositorHitTestResult,
84 device_pixels_per_page: Scale<f32, CSSPixel, DevicePixel>,
85}
86
87pub struct TouchSequenceInfo {
88 pub(crate) state: TouchSequenceState,
90 active_touch_points: Vec<TouchPoint>,
92 handling_touch_move: bool,
97 pub prevent_click: bool,
104 pub prevent_move: TouchMoveAllowed,
108 pending_touch_move_actions: Vec<ScrollZoomEvent>,
116 hit_test_result_cache: Option<HitTestResultCache>,
118}
119
120impl TouchSequenceInfo {
121 fn touch_count(&self) -> usize {
122 self.active_touch_points.len()
123 }
124
125 fn pinch_distance_and_center(&self) -> (f32, Point2D<f32, DevicePixel>) {
126 debug_assert_eq!(self.touch_count(), 2);
127 let p0 = self.active_touch_points[0].point;
128 let p1 = self.active_touch_points[1].point;
129 let center = p0.lerp(p1, 0.5);
130 let distance = (p0 - p1).length();
131
132 (distance, center)
133 }
134
135 fn add_pending_touch_move_action(&mut self, action: ScrollZoomEvent) {
136 debug_assert!(self.prevent_move == TouchMoveAllowed::Pending);
137 self.pending_touch_move_actions.push(action);
138 }
139
140 fn is_finished(&self) -> bool {
143 matches!(
144 self.state,
145 Finished | Flinging { .. } | PendingFling { .. } | PendingClick(_)
146 )
147 }
148
149 fn update_hit_test_result_cache_pointer(&mut self, delta: Vector2D<f32, DevicePixel>) {
150 if let Some(ref mut hit_test_result_cache) = self.hit_test_result_cache {
151 let scaled_delta = delta / hit_test_result_cache.device_pixels_per_page;
152 hit_test_result_cache.value.point_in_viewport += scaled_delta;
154 }
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq)]
161
162pub struct TouchPoint {
163 pub id: TouchId,
164 pub point: Point2D<f32, DevicePixel>,
165}
166
167impl TouchPoint {
168 pub fn new(id: TouchId, point: Point2D<f32, DevicePixel>) -> Self {
169 TouchPoint { id, point }
170 }
171}
172
173#[derive(Clone, Copy, Debug, PartialEq)]
175pub(crate) enum TouchSequenceState {
176 Touching,
178 Panning {
180 velocity: Vector2D<f32, DevicePixel>,
181 },
182 Pinching,
184 MultiTouch,
186 PendingFling {
191 velocity: Vector2D<f32, DevicePixel>,
192 point: DevicePoint,
193 },
194 Flinging {
196 velocity: Vector2D<f32, DevicePixel>,
197 point: DevicePoint,
198 },
199 PendingClick(DevicePoint),
201 Finished,
203}
204
205pub(crate) struct FlingAction {
206 pub delta: DeviceVector2D,
207 pub cursor: DevicePoint,
208}
209
210impl TouchHandler {
211 pub fn new(webview_id: WebViewId) -> Self {
212 let finished_info = TouchSequenceInfo {
213 state: TouchSequenceState::Finished,
214 active_touch_points: vec![],
215 handling_touch_move: false,
216 prevent_click: false,
217 prevent_move: TouchMoveAllowed::Pending,
218 pending_touch_move_actions: vec![],
219 hit_test_result_cache: None,
220 };
221 let mut touch_sequence_map = FxHashMap::default();
225 touch_sequence_map.insert(TouchSequenceId::new(), finished_info);
226 TouchHandler {
227 webview_id,
228 current_sequence_id: TouchSequenceId::new(),
229 touch_sequence_map,
230 pending_touch_input_events: Default::default(),
231 observing_frames_for_fling: Default::default(),
232 }
233 }
234
235 pub(crate) fn set_handling_touch_move(&mut self, sequence_id: TouchSequenceId, flag: bool) {
236 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
237 sequence.handling_touch_move = flag;
238 }
239 }
240
241 pub(crate) fn is_handling_touch_move(&self, sequence_id: TouchSequenceId) -> bool {
242 if let Some(sequence) = self.touch_sequence_map.get(&sequence_id) {
243 sequence.handling_touch_move
244 } else {
245 false
246 }
247 }
248
249 pub(crate) fn prevent_click(&mut self, sequence_id: TouchSequenceId) {
250 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
251 sequence.prevent_click = true;
252 } else {
253 warn!("TouchSequenceInfo corresponding to the sequence number has been deleted.");
254 }
255 }
256
257 pub(crate) fn prevent_move(&mut self, sequence_id: TouchSequenceId) {
258 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
259 sequence.prevent_move = TouchMoveAllowed::Prevented;
260 } else {
261 warn!("TouchSequenceInfo corresponding to the sequence number has been deleted.");
262 }
263 }
264
265 pub(crate) fn move_allowed(&mut self, sequence_id: TouchSequenceId) -> bool {
268 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
269 sequence.prevent_move == TouchMoveAllowed::Allowed
270 } else {
271 true
272 }
273 }
274
275 pub(crate) fn take_pending_touch_move_actions(
276 &mut self,
277 sequence_id: TouchSequenceId,
278 ) -> Vec<ScrollZoomEvent> {
279 self.touch_sequence_map
280 .get_mut(&sequence_id)
281 .map(|sequence| std::mem::take(&mut sequence.pending_touch_move_actions))
282 .unwrap_or_default()
283 }
284
285 pub(crate) fn remove_pending_touch_move_actions(&mut self, sequence_id: TouchSequenceId) {
286 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
287 sequence.pending_touch_move_actions = Vec::new();
288 }
289 }
290
291 pub(crate) fn try_remove_touch_sequence(&mut self, sequence_id: TouchSequenceId) {
293 if let Some(sequence) = self.touch_sequence_map.get(&sequence_id) {
294 if sequence.pending_touch_move_actions.is_empty() && sequence.state == Finished {
295 self.touch_sequence_map.remove(&sequence_id);
296 }
297 }
298 }
299
300 pub(crate) fn remove_touch_sequence(&mut self, sequence_id: TouchSequenceId) {
301 let old = self.touch_sequence_map.remove(&sequence_id);
302 debug_assert!(old.is_some(), "Sequence already removed?");
303 }
304
305 pub fn get_current_touch_sequence_mut(&mut self) -> &mut TouchSequenceInfo {
306 self.touch_sequence_map
307 .get_mut(&self.current_sequence_id)
308 .expect("Current Touch sequence does not exist")
309 }
310
311 fn try_get_current_touch_sequence(&self) -> Option<&TouchSequenceInfo> {
312 self.touch_sequence_map.get(&self.current_sequence_id)
313 }
314
315 fn try_get_current_touch_sequence_mut(&mut self) -> Option<&mut TouchSequenceInfo> {
316 self.touch_sequence_map.get_mut(&self.current_sequence_id)
317 }
318
319 pub(crate) fn get_touch_sequence(&self, sequence_id: TouchSequenceId) -> &TouchSequenceInfo {
320 self.touch_sequence_map
321 .get(&sequence_id)
322 .expect("Touch sequence not found.")
323 }
324 pub(crate) fn get_touch_sequence_mut(
325 &mut self,
326 sequence_id: TouchSequenceId,
327 ) -> Option<&mut TouchSequenceInfo> {
328 self.touch_sequence_map.get_mut(&sequence_id)
329 }
330
331 pub fn on_touch_down(&mut self, id: TouchId, point: Point2D<f32, DevicePixel>) {
332 if !self
334 .touch_sequence_map
335 .contains_key(&self.current_sequence_id) ||
336 self.get_touch_sequence(self.current_sequence_id)
337 .is_finished()
338 {
339 self.current_sequence_id.next();
340 debug!("Entered new touch sequence: {:?}", self.current_sequence_id);
341 let active_touch_points = vec![TouchPoint::new(id, point)];
342 self.touch_sequence_map.insert(
343 self.current_sequence_id,
344 TouchSequenceInfo {
345 state: Touching,
346 active_touch_points,
347 handling_touch_move: false,
348 prevent_click: false,
349 prevent_move: TouchMoveAllowed::Pending,
350 pending_touch_move_actions: vec![],
351 hit_test_result_cache: None,
352 },
353 );
354 } else {
355 debug!("Touch down in sequence {:?}.", self.current_sequence_id);
356 let touch_sequence = self.get_current_touch_sequence_mut();
357 touch_sequence
358 .active_touch_points
359 .push(TouchPoint::new(id, point));
360 match touch_sequence.active_touch_points.len() {
361 2.. => {
362 touch_sequence.state = MultiTouch;
363 },
364 0..2 => {
365 unreachable!("Secondary touch_down event with less than 2 fingers active?");
366 },
367 }
368 touch_sequence.prevent_click = true;
370 }
371 }
372
373 pub fn notify_new_frame_start(&mut self) -> Option<FlingAction> {
374 let touch_sequence = self.touch_sequence_map.get_mut(&self.current_sequence_id)?;
375
376 let Flinging {
377 velocity,
378 point: cursor,
379 } = &mut touch_sequence.state
380 else {
381 self.observing_frames_for_fling.set(false);
382 return None;
383 };
384
385 if velocity.length().abs() < FLING_MIN_SCREEN_PX {
386 let _span = profile_traits::info_span!("TouchHandler::FlingEnd").entered();
387 touch_sequence.state = Finished;
388 self.try_remove_touch_sequence(self.current_sequence_id);
391 self.observing_frames_for_fling.set(false);
392 None
393 } else {
394 *velocity *= FLING_SCALING_FACTOR;
397 let _span = profile_traits::info_span!(
398 "TouchHandler::Flinging",
399 velocity = ?velocity,
400 )
401 .entered();
402 debug_assert!(velocity.length() <= FLING_MAX_SCREEN_PX);
403 Some(FlingAction {
404 delta: DeviceVector2D::new(velocity.x, velocity.y),
405 cursor: *cursor,
406 })
407 }
408 }
409
410 pub fn on_touch_move(
411 &mut self,
412 id: TouchId,
413 point: Point2D<f32, DevicePixel>,
414 ) -> Option<ScrollZoomEvent> {
415 let touch_sequence = self.try_get_current_touch_sequence_mut()?;
419 let idx = match touch_sequence
420 .active_touch_points
421 .iter_mut()
422 .position(|t| t.id == id)
423 {
424 Some(i) => i,
425 None => {
426 error!("Got a touchmove event for a non-active touch point");
427 return None;
428 },
429 };
430 let old_point = touch_sequence.active_touch_points[idx].point;
431 let delta = point - old_point;
432 touch_sequence.update_hit_test_result_cache_pointer(delta);
433
434 let action = match touch_sequence.touch_count() {
435 1 => {
436 if let Panning { ref mut velocity } = touch_sequence.state {
437 *velocity += delta;
439 *velocity /= 2.0;
440 touch_sequence.active_touch_points[idx].point = point;
442
443 Some(ScrollZoomEvent::Scroll(ScrollEvent {
445 scroll: Scroll::Delta((-delta).into()),
446 point,
447 event_count: 1,
448 }))
449 } else if delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX ||
450 delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX
451 {
452 let _span = profile_traits::info_span!(
453 "TouchHandler::ScrollBegin",
454 delta = ?delta,
455 )
456 .entered();
457 touch_sequence.state = Panning {
458 velocity: Vector2D::new(delta.x, delta.y),
459 };
460 touch_sequence.prevent_click = true;
462 touch_sequence.active_touch_points[idx].point = point;
464
465 Some(ScrollZoomEvent::Scroll(ScrollEvent {
467 scroll: Scroll::Delta((-delta).into()),
468 point,
469 event_count: 1,
470 }))
471 } else {
472 None
475 }
476 },
477 2 => {
478 if touch_sequence.state == Pinching ||
479 delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX ||
480 delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX
481 {
482 touch_sequence.state = Pinching;
483 let (d0, _) = touch_sequence.pinch_distance_and_center();
484
485 touch_sequence.active_touch_points[idx].point = point;
487 let (d1, c1) = touch_sequence.pinch_distance_and_center();
488
489 Some(ScrollZoomEvent::PinchZoom(d1 / d0, c1))
490 } else {
491 None
494 }
495 },
496 _ => {
497 touch_sequence.active_touch_points[idx].point = point;
498 touch_sequence.state = MultiTouch;
499 None
500 },
501 };
502 if let Some(action) = action {
505 if touch_sequence.prevent_move == TouchMoveAllowed::Pending {
506 touch_sequence.add_pending_touch_move_action(action);
507 }
508 }
509
510 action
511 }
512
513 pub fn on_touch_up(&mut self, id: TouchId, point: Point2D<f32, DevicePixel>) {
514 let touch_sequence = self.get_current_touch_sequence_mut();
515 let old = match touch_sequence
516 .active_touch_points
517 .iter()
518 .position(|t| t.id == id)
519 {
520 Some(i) => Some(touch_sequence.active_touch_points.swap_remove(i).point),
521 None => {
522 warn!("Got a touch up event for a non-active touch point");
523 None
524 },
525 };
526 match touch_sequence.state {
527 Touching => {
528 if touch_sequence.prevent_click {
529 touch_sequence.state = Finished;
530 } else {
531 touch_sequence.state = PendingClick(point);
532 }
533 },
534 Panning { velocity } => {
535 if velocity.length().abs() >= FLING_MIN_SCREEN_PX {
536 let _span = profile_traits::info_span!(
537 "TouchHandler::FlingStart",
538 velocity = ?velocity,
539 )
540 .entered();
541 debug!(
543 "Transitioning to Fling. Cursor is {point:?}. Old cursor was {old:?}. \
544 Raw velocity is {velocity:?}."
545 );
546
547 let velocity = (velocity * 2.0).with_max_length(FLING_MAX_SCREEN_PX);
550 match touch_sequence.prevent_move {
551 TouchMoveAllowed::Allowed => {
552 touch_sequence.state = Flinging { velocity, point }
553 },
556 TouchMoveAllowed::Pending => {
557 touch_sequence.state = PendingFling { velocity, point }
558 },
559 TouchMoveAllowed::Prevented => touch_sequence.state = Finished,
560 }
561 } else {
562 let _span = profile_traits::info_span!("TouchHandler::ScrollEnd").entered();
563 touch_sequence.state = Finished;
564 }
565 },
566 Pinching => {
567 touch_sequence.state = Touching;
568 },
569 MultiTouch => {
570 if touch_sequence.active_touch_points.is_empty() {
572 touch_sequence.state = Finished;
573 }
574 },
575 PendingFling { .. } | Flinging { .. } | PendingClick(_) | Finished => {
576 error!("Touch-up received, but touch handler already in post-touchup state.")
577 },
578 }
579 #[cfg(debug_assertions)]
580 if touch_sequence.active_touch_points.is_empty() {
581 debug_assert!(
582 touch_sequence.is_finished(),
583 "Did not transition to a finished state: {:?}",
584 touch_sequence.state
585 );
586 }
587 debug!(
588 "Touch up with remaining active touchpoints: {:?}, in sequence {:?}",
589 touch_sequence.active_touch_points.len(),
590 self.current_sequence_id
591 );
592 }
593
594 pub fn on_touch_cancel(&mut self, id: TouchId, _point: Point2D<f32, DevicePixel>) {
595 let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
597 return;
598 };
599 match touch_sequence
600 .active_touch_points
601 .iter()
602 .position(|t| t.id == id)
603 {
604 Some(i) => {
605 touch_sequence.active_touch_points.swap_remove(i);
606 },
607 None => {
608 warn!("Got a touchcancel event for a non-active touch point");
609 return;
610 },
611 }
612 if touch_sequence.active_touch_points.is_empty() {
613 touch_sequence.state = Finished;
614 }
615 }
616
617 pub(crate) fn get_hit_test_result_cache_value(&self) -> Option<CompositorHitTestResult> {
618 let sequence = self.touch_sequence_map.get(&self.current_sequence_id)?;
619 if sequence.state == Finished {
620 return None;
621 }
622 sequence
623 .hit_test_result_cache
624 .as_ref()
625 .map(|cache| Some(cache.value.clone()))?
626 }
627
628 pub(crate) fn set_hit_test_result_cache_value(
629 &mut self,
630 value: CompositorHitTestResult,
631 device_pixels_per_page: Scale<f32, CSSPixel, DevicePixel>,
632 ) {
633 if let Some(sequence) = self.touch_sequence_map.get_mut(&self.current_sequence_id) {
634 if sequence.hit_test_result_cache.is_none() {
635 sequence.hit_test_result_cache = Some(HitTestResultCache {
636 value,
637 device_pixels_per_page,
638 });
639 }
640 }
641 }
642
643 pub(crate) fn add_pending_touch_input_event(
644 &self,
645 id: InputEventId,
646 event_type: TouchEventType,
647 ) {
648 self.pending_touch_input_events.borrow_mut().insert(
649 id,
650 PendingTouchInputEvent {
651 event_type,
652 sequence_id: self.current_sequence_id,
653 },
654 );
655 }
656
657 pub(crate) fn take_pending_touch_input_event(
658 &self,
659 id: InputEventId,
660 ) -> Option<PendingTouchInputEvent> {
661 self.pending_touch_input_events.borrow_mut().remove(&id)
662 }
663
664 pub(crate) fn add_touch_move_refresh_observer_if_necessary(
665 &self,
666 refresh_driver: Rc<BaseRefreshDriver>,
667 repaint_reason: &Cell<RepaintReason>,
668 ) {
669 if self.observing_frames_for_fling.get() {
670 return;
671 }
672
673 let Some(current_touch_sequence) = self.try_get_current_touch_sequence() else {
674 return;
675 };
676
677 if !matches!(
678 current_touch_sequence.state,
679 TouchSequenceState::Flinging { .. },
680 ) {
681 return;
682 }
683
684 refresh_driver.add_observer(Rc::new(FlingRefreshDriverObserver {
685 webview_id: self.webview_id,
686 }));
687 self.observing_frames_for_fling.set(true);
688 repaint_reason.set(repaint_reason.get().union(RepaintReason::StartedFlinging));
689 }
690}
691
692pub(crate) struct PendingTouchInputEvent {
696 pub event_type: TouchEventType,
697 pub sequence_id: TouchSequenceId,
698}
699
700pub(crate) struct FlingRefreshDriverObserver {
701 pub webview_id: WebViewId,
702}
703
704impl RefreshDriverObserver for FlingRefreshDriverObserver {
705 fn frame_started(&self, painter: &mut Painter) -> bool {
706 painter
707 .webview_renderer_mut(self.webview_id)
708 .is_some_and(WebViewRenderer::update_touch_handling_at_new_frame_start)
709 }
710}