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