// @flow import browser from '../util/browser'; import type Map from './map'; import {bezier, clamp, extend} from '../util/util'; import Point from '@mapbox/point-geometry'; import type {DragPanOptions} from './handler/shim/drag_pan'; const defaultInertiaOptions = { linearity: 0.3, easing: bezier(0, 0, 0.3, 1), }; const defaultPanInertiaOptions = extend({ deceleration: 2500, maxSpeed: 1400 }, defaultInertiaOptions); const defaultZoomInertiaOptions = extend({ deceleration: 20, maxSpeed: 1400 }, defaultInertiaOptions); const defaultBearingInertiaOptions = extend({ deceleration: 1000, maxSpeed: 360 }, defaultInertiaOptions); const defaultPitchInertiaOptions = extend({ deceleration: 1000, maxSpeed: 90 }, defaultInertiaOptions); export type InertiaOptions = { linearity: number; easing: (t: number) => number; deceleration: number; maxSpeed: number; }; export type InputEvent = MouseEvent | TouchEvent | KeyboardEvent | WheelEvent; export default class HandlerInertia { _map: Map; _inertiaBuffer: Array<{ time: number, settings: Object }>; constructor(map: Map) { this._map = map; this.clear(); } clear() { this._inertiaBuffer = []; } record(settings: any) { this._drainInertiaBuffer(); this._inertiaBuffer.push({time: browser.now(), settings}); } _drainInertiaBuffer() { const inertia = this._inertiaBuffer, now = browser.now(), cutoff = 160; //msec while (inertia.length > 0 && now - inertia[0].time > cutoff) inertia.shift(); } _onMoveEnd(panInertiaOptions?: DragPanOptions) { this._drainInertiaBuffer(); if (this._inertiaBuffer.length < 2) { return; } const deltas = { zoom: 0, bearing: 0, pitch: 0, pan: new Point(0, 0), pinchAround: undefined, around: undefined }; for (const {settings} of this._inertiaBuffer) { deltas.zoom += settings.zoomDelta || 0; deltas.bearing += settings.bearingDelta || 0; deltas.pitch += settings.pitchDelta || 0; if (settings.panDelta) deltas.pan._add(settings.panDelta); if (settings.around) deltas.around = settings.around; if (settings.pinchAround) deltas.pinchAround = settings.pinchAround; } const lastEntry = this._inertiaBuffer[this._inertiaBuffer.length - 1]; const duration = (lastEntry.time - this._inertiaBuffer[0].time); const easeOptions = {}; if (deltas.pan.mag()) { const result = calculateEasing(deltas.pan.mag(), duration, extend({}, defaultPanInertiaOptions, panInertiaOptions || {})); easeOptions.offset = deltas.pan.mult(result.amount / deltas.pan.mag()); easeOptions.center = this._map.transform.center; extendDuration(easeOptions, result); } if (deltas.zoom) { const result = calculateEasing(deltas.zoom, duration, defaultZoomInertiaOptions); easeOptions.zoom = this._map.transform.zoom + result.amount; extendDuration(easeOptions, result); } if (deltas.bearing) { const result = calculateEasing(deltas.bearing, duration, defaultBearingInertiaOptions); easeOptions.bearing = this._map.transform.bearing + clamp(result.amount, -179, 179); extendDuration(easeOptions, result); } if (deltas.pitch) { const result = calculateEasing(deltas.pitch, duration, defaultPitchInertiaOptions); easeOptions.pitch = this._map.transform.pitch + result.amount; extendDuration(easeOptions, result); } if (easeOptions.zoom || easeOptions.bearing) { const last = deltas.pinchAround === undefined ? deltas.around : deltas.pinchAround; easeOptions.around = last ? this._map.unproject(last) : this._map.getCenter(); } this.clear(); return extend(easeOptions, { noMoveStart: true }); } } // Unfortunately zoom, bearing, etc can't have different durations and easings so // we need to choose one. We use the longest duration and it's corresponding easing. function extendDuration(easeOptions, result) { if (!easeOptions.duration || easeOptions.duration < result.duration) { easeOptions.duration = result.duration; easeOptions.easing = result.easing; } } function calculateEasing(amount, inertiaDuration: number, inertiaOptions) { const {maxSpeed, linearity, deceleration} = inertiaOptions; const speed = clamp( amount * linearity / (inertiaDuration / 1000), -maxSpeed, maxSpeed); const duration = Math.abs(speed) / (deceleration * linearity); return { easing: inertiaOptions.easing, duration: duration * 1000, amount: speed * (duration / 2) }; }