import { Plugin } from './Plugin';
const DEFAULT_DECELERATE_OPTIONS = {
friction: 0.98,
bounce: 0.8,
minSpeed: 0.01
};
/**
* Time period of decay (1 frame)
*
* @internal
* @ignore
*/
const TP = 16;
/**
* Plugin to decelerate viewport velocity smoothly after panning ends.
*
* @public
*/
export class Decelerate extends Plugin {
/**
* This is called by {@link Viewport.decelerate}.
*/
constructor(parent, options = {}) {
super(parent);
this.options = Object.assign({}, DEFAULT_DECELERATE_OPTIONS, options);
this.saved = [];
this.timeSinceRelease = 0;
this.reset();
this.parent.on('moved', (data) => this.moved(data));
}
down() {
this.saved = [];
this.x = this.y = null;
return false;
}
isActive() {
return !!(this.x || this.y);
}
move() {
if (this.paused) {
return false;
}
const count = this.parent.input.count();
if (count === 1 || (count > 1 && !this.parent.plugins.get('pinch', true))) {
this.saved.push({ x: this.parent.x, y: this.parent.y, time: performance.now() });
if (this.saved.length > 60) {
this.saved.splice(0, 30);
}
}
// Silently recording viewport positions
return false;
}
/** Listener to viewport's "moved" event. */
moved(data) {
if (this.saved.length) {
const last = this.saved[this.saved.length - 1];
if (data.type === 'clamp-x') {
if (last.x === data.original.x) {
last.x = this.parent.x;
}
}
else if (data.type === 'clamp-y') {
if (last.y === data.original.y) {
last.y = this.parent.y;
}
}
}
}
up() {
if (this.parent.input.count() === 0 && this.saved.length) {
const now = performance.now();
for (const save of this.saved) {
if (save.time >= now - 100) {
const time = now - save.time;
this.x = (this.parent.x - save.x) / time;
this.y = (this.parent.y - save.y) / time;
this.percentChangeX = this.percentChangeY = this.options.friction;
this.timeSinceRelease = 0;
break;
}
}
}
return false;
}
/**
* Manually activate deceleration, starting from the (x, y) velocity components passed in the options.
*
* @param {object} options
* @param {number} [options.x] - Specify x-component of initial velocity.
* @param {number} [options.y] - Specify y-component of initial velocity.
*/
activate(options) {
options = options || {};
if (typeof options.x !== 'undefined') {
this.x = options.x;
this.percentChangeX = this.options.friction;
}
if (typeof options.y !== 'undefined') {
this.y = options.y;
this.percentChangeY = this.options.friction;
}
}
update(elapsed) {
if (this.paused) {
return;
}
/*
* See https://github.com/davidfig/pixi-viewport/issues/271 for math.
*
* The viewport velocity (this.x, this.y) decays exponentially by the the decay factor
* (this.percentChangeX, this.percentChangeY) each frame. This velocity function is integrated
* to calculate the displacement.
*/
const moved = this.x || this.y;
const ti = this.timeSinceRelease;
const tf = this.timeSinceRelease + elapsed;
if (this.x) {
const k = this.percentChangeX;
const lnk = Math.log(k);
// Apply velocity delta on the viewport x-coordinate.
this.parent.x += ((this.x * TP) / lnk) * (Math.pow(k, tf / TP) - Math.pow(k, ti / TP));
// Apply decay on x-component of velocity
this.x *= Math.pow(this.percentChangeX, elapsed / TP);
}
if (this.y) {
const k = this.percentChangeY;
const lnk = Math.log(k);
// Apply velocity delta on the viewport y-coordinate.
this.parent.y += ((this.y * TP) / lnk) * (Math.pow(k, tf / TP) - Math.pow(k, ti / TP));
// Apply decay on y-component of velocity
this.y *= Math.pow(this.percentChangeY, elapsed / TP);
}
this.timeSinceRelease += elapsed;
// End decelerate velocity once it goes under a certain amount of precision.
if (this.x && this.y) {
if (Math.abs(this.x) < this.options.minSpeed && Math.abs(this.y) < this.options.minSpeed) {
this.x = 0;
this.y = 0;
}
}
else {
if (Math.abs(this.x || 0) < this.options.minSpeed) {
this.x = 0;
}
if (Math.abs(this.y || 0) < this.options.minSpeed) {
this.y = 0;
}
}
if (moved) {
this.parent.emit('moved', { viewport: this.parent, type: 'decelerate' });
}
}
reset() {
this.x = this.y = null;
}
}