// @flow import Point from '@mapbox/point-geometry'; import {indexTouches} from './handler_util'; function getCentroid(points: Array) { const sum = new Point(0, 0); for (const point of points) { sum._add(point); } return sum.div(points.length); } export const MAX_TAP_INTERVAL = 500; const MAX_TOUCH_TIME = 500; const MAX_DIST = 30; export class SingleTapRecognizer { numTouches: number; centroid: Point; startTime: number; aborted: boolean; touches: { [number | string]: Point }; constructor(options: { numTouches: number }) { this.reset(); this.numTouches = options.numTouches; } reset() { delete this.centroid; delete this.startTime; delete this.touches; this.aborted = false; } touchstart(e: TouchEvent, points: Array, mapTouches: Array) { if (this.centroid || mapTouches.length > this.numTouches) { this.aborted = true; } if (this.aborted) { return; } if (this.startTime === undefined) { this.startTime = e.timeStamp; } if (mapTouches.length === this.numTouches) { this.centroid = getCentroid(points); this.touches = indexTouches(mapTouches, points); } } touchmove(e: TouchEvent, points: Array, mapTouches: Array) { if (this.aborted || !this.centroid) return; const newTouches = indexTouches(mapTouches, points); for (const id in this.touches) { const prevPos = this.touches[id]; const pos = newTouches[id]; if (!pos || pos.dist(prevPos) > MAX_DIST) { this.aborted = true; } } } touchend(e: TouchEvent, points: Array, mapTouches: Array) { if (!this.centroid || e.timeStamp - this.startTime > MAX_TOUCH_TIME) { this.aborted = true; } if (mapTouches.length === 0) { const centroid = !this.aborted && this.centroid; this.reset(); if (centroid) return centroid; } } } export class TapRecognizer { singleTap: SingleTapRecognizer; numTaps: number; lastTime: number; lastTap: Point; count: number; constructor(options: { numTaps: number, numTouches: number }) { this.singleTap = new SingleTapRecognizer(options); this.numTaps = options.numTaps; this.reset(); } reset() { this.lastTime = Infinity; delete this.lastTap; this.count = 0; this.singleTap.reset(); } touchstart(e: TouchEvent, points: Array, mapTouches: Array) { this.singleTap.touchstart(e, points, mapTouches); } touchmove(e: TouchEvent, points: Array, mapTouches: Array) { this.singleTap.touchmove(e, points, mapTouches); } touchend(e: TouchEvent, points: Array, mapTouches: Array) { const tap = this.singleTap.touchend(e, points, mapTouches); if (tap) { const soonEnough = e.timeStamp - this.lastTime < MAX_TAP_INTERVAL; const closeEnough = !this.lastTap || this.lastTap.dist(tap) < MAX_DIST; if (!soonEnough || !closeEnough) { this.reset(); } this.count++; this.lastTime = e.timeStamp; this.lastTap = tap; if (this.count === this.numTaps) { this.reset(); return tap; } } } }