| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {assert} from 'chrome://resources/js/assert.js'; |
| |
| /** |
| * The longest period of time in milliseconds for a horizontal touch movement to |
| * be considered as a swipe. |
| */ |
| const SWIPE_TIMER_INTERVAL_MS: number = 200; |
| |
| /* The minimum travel distance on the x axis for a swipe. */ |
| const SWIPE_X_DIST_MIN: number = 150; |
| |
| /* The maximum travel distance on the y axis for a swipe. */ |
| const SWIPE_Y_DIST_MAX: number = 100; |
| |
| export interface SwipeEvent { |
| type: string; |
| detail: SwipeDirection; |
| } |
| |
| /** Enumeration of swipe directions. */ |
| export enum SwipeDirection { |
| RIGHT_TO_LEFT = 0, |
| LEFT_TO_RIGHT = 1, |
| } |
| |
| // A class that listens for touch events and produces events when these |
| // touches form swipe gestures. |
| export class SwipeDetector { |
| private element_: HTMLElement; |
| private isPresentationMode_: boolean = false; |
| private swipeStartEvent_: TouchEvent|null = null; |
| |
| private elapsedTimeForTesting_: number|null = null; |
| |
| private eventTarget_: EventTarget = new EventTarget(); |
| |
| /** @param element The element to monitor for touch gestures. */ |
| constructor(element: HTMLElement) { |
| this.element_ = element; |
| |
| this.element_.addEventListener( |
| 'touchstart', (this.onTouchStart_.bind(this) as (p1: Event) => any), |
| {passive: true}); |
| |
| this.element_.addEventListener( |
| 'touchend', (this.onTouchEnd_.bind(this) as (p1: Event) => any), |
| {passive: true}); |
| this.element_.addEventListener( |
| 'touchcancel', () => this.onTouchCancel_(), {passive: true}); |
| } |
| |
| /** |
| * Public for tests. Allow manually setting the elapsed time for a swipe |
| * action. |
| */ |
| setElapsedTimerForTesting(time: number) { |
| this.elapsedTimeForTesting_ = time; |
| } |
| |
| setPresentationMode(enabled: boolean) { |
| this.isPresentationMode_ = enabled; |
| } |
| |
| getPresentationModeForTesting() { |
| return this.isPresentationMode_; |
| } |
| |
| getEventTarget(): EventTarget { |
| return this.eventTarget_; |
| } |
| |
| /** |
| * Call the relevant listeners with the given swipe |direction|. |
| * @param direction The direction of swipe action. |
| */ |
| private notify_(direction: SwipeDirection) { |
| this.eventTarget_.dispatchEvent( |
| new CustomEvent('swipe', {detail: direction})); |
| } |
| |
| /** The callback for touchstart events on the element. */ |
| private onTouchStart_(event: TouchEvent) { |
| if (!this.isPresentationMode_) { |
| return; |
| } |
| |
| // If more than 1 finger touch the screen or there is already an ongoing |
| // swipe detection process, there is no valid swipe event to keep track. |
| if (event.touches.length !== 1 || this.swipeStartEvent_) { |
| this.swipeStartEvent_ = null; |
| return; |
| } |
| |
| this.swipeStartEvent_ = event; |
| return; |
| } |
| |
| /** The callback for touchcancel events on the element. */ |
| private onTouchCancel_() { |
| if (!this.isPresentationMode_ || !this.swipeStartEvent_) { |
| return; |
| } |
| |
| this.swipeStartEvent_ = null; |
| } |
| |
| /** The callback for touchend events on the element. */ |
| private onTouchEnd_(event: TouchEvent) { |
| if (!this.isPresentationMode_ || !this.swipeStartEvent_) { |
| return; |
| } |
| |
| if (event.touches.length !== 0 || |
| this.swipeStartEvent_.touches.length !== 1) { |
| return; |
| } |
| |
| const elapsedTime = this.elapsedTimeForTesting_ ? |
| this.elapsedTimeForTesting_ : |
| event.timeStamp - this.swipeStartEvent_.timeStamp; |
| const swipeStartObj = this.swipeStartEvent_.changedTouches[0]; |
| assert(swipeStartObj); |
| const swipeEndObj = event.changedTouches[0]; |
| assert(swipeEndObj); |
| const distX = swipeEndObj.pageX - swipeStartObj.pageX; |
| const distY = swipeEndObj.pageY - swipeStartObj.pageY; |
| |
| // If this is a valid swipe, notify its direction to the viewer. |
| if (elapsedTime <= SWIPE_TIMER_INTERVAL_MS && |
| Math.abs(distX) >= SWIPE_X_DIST_MIN && |
| Math.abs(distY) <= SWIPE_Y_DIST_MAX) { |
| const direction = distX > 0 ? SwipeDirection.LEFT_TO_RIGHT : |
| SwipeDirection.RIGHT_TO_LEFT; |
| this.notify_(direction); |
| } |
| |
| this.swipeStartEvent_ = null; |
| } |
| } |