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 touch_sequence.state = Finished;
379 self.try_remove_touch_sequence(self.current_sequence_id);
382 None
383 } else {
384 *velocity *= FLING_SCALING_FACTOR;
387 debug_assert!(velocity.length() <= FLING_MAX_SCREEN_PX);
388 Some(FlingAction {
389 delta: DeviceVector2D::new(velocity.x, velocity.y),
390 cursor: *cursor,
391 })
392 }
393 }
394
395 pub fn on_touch_move(
396 &mut self,
397 id: TouchId,
398 point: Point2D<f32, DevicePixel>,
399 ) -> Option<ScrollZoomEvent> {
400 let touch_sequence = self.try_get_current_touch_sequence_mut()?;
404 let idx = match touch_sequence
405 .active_touch_points
406 .iter_mut()
407 .position(|t| t.id == id)
408 {
409 Some(i) => i,
410 None => {
411 error!("Got a touchmove event for a non-active touch point");
412 return None;
413 },
414 };
415 let old_point = touch_sequence.active_touch_points[idx].point;
416 let delta = point - old_point;
417 touch_sequence.update_hit_test_result_cache_pointer(delta);
418
419 let action = match touch_sequence.touch_count() {
420 1 => {
421 if let Panning { ref mut velocity } = touch_sequence.state {
422 *velocity += delta;
424 *velocity /= 2.0;
425 touch_sequence.active_touch_points[idx].point = point;
427
428 Some(ScrollZoomEvent::Scroll(ScrollEvent {
430 scroll: Scroll::Delta((-delta).into()),
431 point,
432 event_count: 1,
433 }))
434 } else if delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX ||
435 delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX
436 {
437 touch_sequence.state = Panning {
438 velocity: Vector2D::new(delta.x, delta.y),
439 };
440 touch_sequence.prevent_click = true;
442 touch_sequence.active_touch_points[idx].point = point;
444
445 Some(ScrollZoomEvent::Scroll(ScrollEvent {
447 scroll: Scroll::Delta((-delta).into()),
448 point,
449 event_count: 1,
450 }))
451 } else {
452 None
455 }
456 },
457 2 => {
458 if touch_sequence.state == Pinching ||
459 delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX ||
460 delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX
461 {
462 touch_sequence.state = Pinching;
463 let (d0, _) = touch_sequence.pinch_distance_and_center();
464
465 touch_sequence.active_touch_points[idx].point = point;
467 let (d1, c1) = touch_sequence.pinch_distance_and_center();
468
469 Some(ScrollZoomEvent::PinchZoom(d1 / d0, c1))
470 } else {
471 None
474 }
475 },
476 _ => {
477 touch_sequence.active_touch_points[idx].point = point;
478 touch_sequence.state = MultiTouch;
479 None
480 },
481 };
482 if let Some(action) = action {
485 if touch_sequence.prevent_move == TouchMoveAllowed::Pending {
486 touch_sequence.add_pending_touch_move_action(action);
487 }
488 }
489
490 action
491 }
492
493 pub fn on_touch_up(&mut self, id: TouchId, point: Point2D<f32, DevicePixel>) {
494 let touch_sequence = self.get_current_touch_sequence_mut();
495 let old = match touch_sequence
496 .active_touch_points
497 .iter()
498 .position(|t| t.id == id)
499 {
500 Some(i) => Some(touch_sequence.active_touch_points.swap_remove(i).point),
501 None => {
502 warn!("Got a touch up event for a non-active touch point");
503 None
504 },
505 };
506 match touch_sequence.state {
507 Touching => {
508 if touch_sequence.prevent_click {
509 touch_sequence.state = Finished;
510 } else {
511 touch_sequence.state = PendingClick(point);
512 }
513 },
514 Panning { velocity } => {
515 if velocity.length().abs() >= FLING_MIN_SCREEN_PX {
516 debug!(
518 "Transitioning to Fling. Cursor is {point:?}. Old cursor was {old:?}. \
519 Raw velocity is {velocity:?}."
520 );
521
522 let velocity = (velocity * 2.0).with_max_length(FLING_MAX_SCREEN_PX);
525 match touch_sequence.prevent_move {
526 TouchMoveAllowed::Allowed => {
527 touch_sequence.state = Flinging { velocity, point }
528 },
531 TouchMoveAllowed::Pending => {
532 touch_sequence.state = PendingFling { velocity, point }
533 },
534 TouchMoveAllowed::Prevented => touch_sequence.state = Finished,
535 }
536 } else {
537 touch_sequence.state = Finished;
538 }
539 },
540 Pinching => {
541 touch_sequence.state = Touching;
542 },
543 MultiTouch => {
544 if touch_sequence.active_touch_points.is_empty() {
546 touch_sequence.state = Finished;
547 }
548 },
549 PendingFling { .. } | Flinging { .. } | PendingClick(_) | Finished => {
550 error!("Touch-up received, but touch handler already in post-touchup state.")
551 },
552 }
553 #[cfg(debug_assertions)]
554 if touch_sequence.active_touch_points.is_empty() {
555 debug_assert!(
556 touch_sequence.is_finished(),
557 "Did not transition to a finished state: {:?}",
558 touch_sequence.state
559 );
560 }
561 debug!(
562 "Touch up with remaining active touchpoints: {:?}, in sequence {:?}",
563 touch_sequence.active_touch_points.len(),
564 self.current_sequence_id
565 );
566 }
567
568 pub fn on_touch_cancel(&mut self, id: TouchId, _point: Point2D<f32, DevicePixel>) {
569 let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
571 return;
572 };
573 match touch_sequence
574 .active_touch_points
575 .iter()
576 .position(|t| t.id == id)
577 {
578 Some(i) => {
579 touch_sequence.active_touch_points.swap_remove(i);
580 },
581 None => {
582 warn!("Got a touchcancel event for a non-active touch point");
583 return;
584 },
585 }
586 if touch_sequence.active_touch_points.is_empty() {
587 touch_sequence.state = Finished;
588 }
589 }
590
591 pub(crate) fn get_hit_test_result_cache_value(&self) -> Option<CompositorHitTestResult> {
592 let sequence = self.touch_sequence_map.get(&self.current_sequence_id)?;
593 if sequence.state == Finished {
594 return None;
595 }
596 sequence
597 .hit_test_result_cache
598 .as_ref()
599 .map(|cache| Some(cache.value.clone()))?
600 }
601
602 pub(crate) fn set_hit_test_result_cache_value(
603 &mut self,
604 value: CompositorHitTestResult,
605 device_pixels_per_page: Scale<f32, CSSPixel, DevicePixel>,
606 ) {
607 if let Some(sequence) = self.touch_sequence_map.get_mut(&self.current_sequence_id) {
608 if sequence.hit_test_result_cache.is_none() {
609 sequence.hit_test_result_cache = Some(HitTestResultCache {
610 value,
611 device_pixels_per_page,
612 });
613 }
614 }
615 }
616
617 pub(crate) fn add_pending_touch_input_event(
618 &self,
619 id: InputEventId,
620 event_type: TouchEventType,
621 ) {
622 self.pending_touch_input_events.borrow_mut().insert(
623 id,
624 PendingTouchInputEvent {
625 event_type,
626 sequence_id: self.current_sequence_id,
627 },
628 );
629 }
630
631 pub(crate) fn take_pending_touch_input_event(
632 &self,
633 id: InputEventId,
634 ) -> Option<PendingTouchInputEvent> {
635 self.pending_touch_input_events.borrow_mut().remove(&id)
636 }
637}
638
639pub(crate) struct PendingTouchInputEvent {
643 pub event_type: TouchEventType,
644 pub sequence_id: TouchSequenceId,
645}
646
647pub(crate) struct FlingRefreshDriverObserver {
648 pub webview_id: WebViewId,
649}
650
651impl RefreshDriverObserver for FlingRefreshDriverObserver {
652 fn frame_started(&self, compositor: &mut Painter) -> bool {
653 compositor
654 .webview_renderer_mut(self.webview_id)
655 .is_some_and(WebViewRenderer::update_touch_handling_at_new_frame_start)
656 }
657}