5857 lines
164 KiB
JavaScript
5857 lines
164 KiB
JavaScript
/* eslint-disable */
|
|
|
|
/*!
|
|
* pixi-viewport - v4.35.1
|
|
* Compiled Sat, 03 Sep 2022 13:47:09 UTC
|
|
*
|
|
* pixi-viewport is licensed under the MIT License.
|
|
* http://www.opensource.org/licenses/mit-license
|
|
*
|
|
* Copyright 2019-2020, David Figatner, All Rights Reserved
|
|
*/
|
|
this.PIXI = this.PIXI || {};
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@pixi/math'), require('@pixi/display'), require('@pixi/ticker')) :
|
|
typeof define === 'function' && define.amd ? define(['exports', '@pixi/math', '@pixi/display', '@pixi/ticker'], factory) :
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.pixi_viewport = {}, global.PIXI, global.PIXI, global.PIXI));
|
|
})(this, (function (exports, math, display, ticker) { 'use strict';
|
|
|
|
/**
|
|
* Derive this class to create user-defined plugins
|
|
*
|
|
* @public
|
|
*/
|
|
class Plugin
|
|
{
|
|
/** The viewport to which this plugin is attached. */
|
|
|
|
|
|
/**
|
|
* Flags whether this plugin has been "paused".
|
|
*
|
|
* @see Plugin#pause
|
|
* @see Plugin#resume
|
|
*/
|
|
|
|
|
|
/** @param {Viewport} parent */
|
|
constructor(parent)
|
|
{
|
|
this.parent = parent;
|
|
this.paused = false;
|
|
}
|
|
|
|
/** Called when plugin is removed */
|
|
destroy()
|
|
{
|
|
// Override for implementation
|
|
}
|
|
|
|
/** Handler for pointerdown PIXI event */
|
|
down(_e)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/** Handler for pointermove PIXI event */
|
|
move(_e)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/** Handler for pointerup PIXI event */
|
|
up(_e)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/** Handler for wheel event on div */
|
|
wheel(_e)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Called on each tick
|
|
* @param {number} elapsed time in millisecond since last update
|
|
*/
|
|
update(_delta)
|
|
{
|
|
// Override for implementation
|
|
}
|
|
|
|
/** Called when the viewport is resized */
|
|
resize()
|
|
{
|
|
// Override for implementation
|
|
}
|
|
|
|
/** Called when the viewport is manually moved */
|
|
reset()
|
|
{
|
|
// Override for implementation
|
|
}
|
|
|
|
/** Pause the plugin */
|
|
pause()
|
|
{
|
|
this.paused = true;
|
|
}
|
|
|
|
/** Un-pause the plugin */
|
|
resume()
|
|
{
|
|
this.paused = false;
|
|
}
|
|
}
|
|
|
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
|
|
function getDefaultExportFromCjs (x) {
|
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
}
|
|
|
|
function createCommonjsModule(fn, basedir, module) {
|
|
return module = {
|
|
path: basedir,
|
|
exports: {},
|
|
require: function (path, base) {
|
|
return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
|
|
}
|
|
}, fn(module, module.exports), module.exports;
|
|
}
|
|
|
|
function getDefaultExportFromNamespaceIfPresent (n) {
|
|
return n && Object.prototype.hasOwnProperty.call(n, 'default') ? n['default'] : n;
|
|
}
|
|
|
|
function getDefaultExportFromNamespaceIfNotNamed (n) {
|
|
return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1 ? n['default'] : n;
|
|
}
|
|
|
|
function commonjsRequire () {
|
|
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
|
|
}
|
|
|
|
var penner = createCommonjsModule(function (module, exports) {
|
|
/*
|
|
Copyright © 2001 Robert Penner
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
Redistributions of source code must retain the above copyright notice, this list of
|
|
conditions and the following disclaimer.
|
|
Redistributions in binary form must reproduce the above copyright notice, this list
|
|
of conditions and the following disclaimer in the documentation and/or other materials
|
|
provided with the distribution.
|
|
|
|
Neither the name of the author nor the names of contributors may be used to endorse
|
|
or promote products derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|
OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
(function() {
|
|
var penner, umd;
|
|
|
|
umd = function(factory) {
|
|
if ('object' === 'object') {
|
|
return module.exports = factory;
|
|
} else if (typeof undefined === 'function' && undefined.amd) {
|
|
return undefined([], factory);
|
|
} else {
|
|
return this.penner = factory;
|
|
}
|
|
};
|
|
|
|
penner = {
|
|
linear: function(t, b, c, d) {
|
|
return c * t / d + b;
|
|
},
|
|
easeInQuad: function(t, b, c, d) {
|
|
return c * (t /= d) * t + b;
|
|
},
|
|
easeOutQuad: function(t, b, c, d) {
|
|
return -c * (t /= d) * (t - 2) + b;
|
|
},
|
|
easeInOutQuad: function(t, b, c, d) {
|
|
if ((t /= d / 2) < 1) {
|
|
return c / 2 * t * t + b;
|
|
} else {
|
|
return -c / 2 * ((--t) * (t - 2) - 1) + b;
|
|
}
|
|
},
|
|
easeInCubic: function(t, b, c, d) {
|
|
return c * (t /= d) * t * t + b;
|
|
},
|
|
easeOutCubic: function(t, b, c, d) {
|
|
return c * ((t = t / d - 1) * t * t + 1) + b;
|
|
},
|
|
easeInOutCubic: function(t, b, c, d) {
|
|
if ((t /= d / 2) < 1) {
|
|
return c / 2 * t * t * t + b;
|
|
} else {
|
|
return c / 2 * ((t -= 2) * t * t + 2) + b;
|
|
}
|
|
},
|
|
easeInQuart: function(t, b, c, d) {
|
|
return c * (t /= d) * t * t * t + b;
|
|
},
|
|
easeOutQuart: function(t, b, c, d) {
|
|
return -c * ((t = t / d - 1) * t * t * t - 1) + b;
|
|
},
|
|
easeInOutQuart: function(t, b, c, d) {
|
|
if ((t /= d / 2) < 1) {
|
|
return c / 2 * t * t * t * t + b;
|
|
} else {
|
|
return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
|
|
}
|
|
},
|
|
easeInQuint: function(t, b, c, d) {
|
|
return c * (t /= d) * t * t * t * t + b;
|
|
},
|
|
easeOutQuint: function(t, b, c, d) {
|
|
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
|
|
},
|
|
easeInOutQuint: function(t, b, c, d) {
|
|
if ((t /= d / 2) < 1) {
|
|
return c / 2 * t * t * t * t * t + b;
|
|
} else {
|
|
return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
|
|
}
|
|
},
|
|
easeInSine: function(t, b, c, d) {
|
|
return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
|
|
},
|
|
easeOutSine: function(t, b, c, d) {
|
|
return c * Math.sin(t / d * (Math.PI / 2)) + b;
|
|
},
|
|
easeInOutSine: function(t, b, c, d) {
|
|
return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
|
|
},
|
|
easeInExpo: function(t, b, c, d) {
|
|
if (t === 0) {
|
|
return b;
|
|
} else {
|
|
return c * Math.pow(2, 10 * (t / d - 1)) + b;
|
|
}
|
|
},
|
|
easeOutExpo: function(t, b, c, d) {
|
|
if (t === d) {
|
|
return b + c;
|
|
} else {
|
|
return c * (-Math.pow(2, -10 * t / d) + 1) + b;
|
|
}
|
|
},
|
|
easeInOutExpo: function(t, b, c, d) {
|
|
if (t === 0) {
|
|
b;
|
|
}
|
|
if (t === d) {
|
|
b + c;
|
|
}
|
|
if ((t /= d / 2) < 1) {
|
|
return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
|
|
} else {
|
|
return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
|
|
}
|
|
},
|
|
easeInCirc: function(t, b, c, d) {
|
|
return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
|
|
},
|
|
easeOutCirc: function(t, b, c, d) {
|
|
return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
|
|
},
|
|
easeInOutCirc: function(t, b, c, d) {
|
|
if ((t /= d / 2) < 1) {
|
|
return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
|
|
} else {
|
|
return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
|
|
}
|
|
},
|
|
easeInElastic: function(t, b, c, d) {
|
|
var a, p, s;
|
|
s = 1.70158;
|
|
p = 0;
|
|
a = c;
|
|
if (t === 0) {
|
|
b;
|
|
} else if ((t /= d) === 1) {
|
|
b + c;
|
|
}
|
|
if (!p) {
|
|
p = d * .3;
|
|
}
|
|
if (a < Math.abs(c)) {
|
|
a = c;
|
|
s = p / 4;
|
|
} else {
|
|
s = p / (2 * Math.PI) * Math.asin(c / a);
|
|
}
|
|
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
|
|
},
|
|
easeOutElastic: function(t, b, c, d) {
|
|
var a, p, s;
|
|
s = 1.70158;
|
|
p = 0;
|
|
a = c;
|
|
if (t === 0) {
|
|
b;
|
|
} else if ((t /= d) === 1) {
|
|
b + c;
|
|
}
|
|
if (!p) {
|
|
p = d * .3;
|
|
}
|
|
if (a < Math.abs(c)) {
|
|
a = c;
|
|
s = p / 4;
|
|
} else {
|
|
s = p / (2 * Math.PI) * Math.asin(c / a);
|
|
}
|
|
return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b;
|
|
},
|
|
easeInOutElastic: function(t, b, c, d) {
|
|
var a, p, s;
|
|
s = 1.70158;
|
|
p = 0;
|
|
a = c;
|
|
if (t === 0) {
|
|
b;
|
|
} else if ((t /= d / 2) === 2) {
|
|
b + c;
|
|
}
|
|
if (!p) {
|
|
p = d * (.3 * 1.5);
|
|
}
|
|
if (a < Math.abs(c)) {
|
|
a = c;
|
|
s = p / 4;
|
|
} else {
|
|
s = p / (2 * Math.PI) * Math.asin(c / a);
|
|
}
|
|
if (t < 1) {
|
|
return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
|
|
} else {
|
|
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b;
|
|
}
|
|
},
|
|
easeInBack: function(t, b, c, d, s) {
|
|
if (s === void 0) {
|
|
s = 1.70158;
|
|
}
|
|
return c * (t /= d) * t * ((s + 1) * t - s) + b;
|
|
},
|
|
easeOutBack: function(t, b, c, d, s) {
|
|
if (s === void 0) {
|
|
s = 1.70158;
|
|
}
|
|
return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
|
|
},
|
|
easeInOutBack: function(t, b, c, d, s) {
|
|
if (s === void 0) {
|
|
s = 1.70158;
|
|
}
|
|
if ((t /= d / 2) < 1) {
|
|
return c / 2 * (t * t * (((s *= 1.525) + 1) * t - s)) + b;
|
|
} else {
|
|
return c / 2 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b;
|
|
}
|
|
},
|
|
easeInBounce: function(t, b, c, d) {
|
|
var v;
|
|
v = penner.easeOutBounce(d - t, 0, c, d);
|
|
return c - v + b;
|
|
},
|
|
easeOutBounce: function(t, b, c, d) {
|
|
if ((t /= d) < 1 / 2.75) {
|
|
return c * (7.5625 * t * t) + b;
|
|
} else if (t < 2 / 2.75) {
|
|
return c * (7.5625 * (t -= 1.5 / 2.75) * t + .75) + b;
|
|
} else if (t < 2.5 / 2.75) {
|
|
return c * (7.5625 * (t -= 2.25 / 2.75) * t + .9375) + b;
|
|
} else {
|
|
return c * (7.5625 * (t -= 2.625 / 2.75) * t + .984375) + b;
|
|
}
|
|
},
|
|
easeInOutBounce: function(t, b, c, d) {
|
|
var v;
|
|
if (t < d / 2) {
|
|
v = penner.easeInBounce(t * 2, 0, c, d);
|
|
return v * .5 + b;
|
|
} else {
|
|
v = penner.easeOutBounce(t * 2 - d, 0, c, d);
|
|
return v * .5 + c * .5 + b;
|
|
}
|
|
}
|
|
};
|
|
|
|
umd(penner);
|
|
|
|
}).call(commonjsGlobal);
|
|
});
|
|
|
|
// eslint-disable-next-line
|
|
|
|
/**
|
|
* Returns correct Penner equation using string or Function.
|
|
*
|
|
* @internal
|
|
* @ignore
|
|
* @param {(function|string)} [ease]
|
|
* @param {defaults} default penner equation to use if none is provided
|
|
*/
|
|
function ease(ease, defaults)
|
|
{
|
|
if (!ease)
|
|
{
|
|
return penner[defaults]
|
|
}
|
|
else if (typeof ease === 'function')
|
|
{
|
|
return ease
|
|
}
|
|
else if (typeof ease === 'string')
|
|
{
|
|
return penner[ease]
|
|
}
|
|
}
|
|
|
|
/** Options for {@link Animate}. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_ANIMATE_OPTIONS = {
|
|
removeOnInterrupt: false,
|
|
ease: 'linear',
|
|
time: 1000,
|
|
};
|
|
|
|
/**
|
|
* Animation plugin.
|
|
*
|
|
* @see Viewport#animate
|
|
* @fires animate-end
|
|
*/
|
|
class Animate extends Plugin
|
|
{
|
|
|
|
|
|
/** The starting x-coordinate of the viewport. */
|
|
|
|
|
|
/** The starting y-coordinate of the viewport. */
|
|
|
|
|
|
/** The change in the x-coordinate of the viewport through the animation.*/
|
|
|
|
|
|
/** The change in the y-coordinate of the viewport through the animation. */
|
|
|
|
|
|
/** Marks whether the center of the viewport is preserved in the animation. */
|
|
|
|
|
|
/** The starting viewport width. */
|
|
__init() {this.startWidth = null;}
|
|
|
|
/** The starting viewport height. */
|
|
__init2() {this.startHeight = null;}
|
|
|
|
/** The change in the viewport's width through the animation. */
|
|
__init3() {this.deltaWidth = null;}
|
|
|
|
/** The change in the viewport's height through the animation. */
|
|
__init4() {this.deltaHeight = null;}
|
|
|
|
/** The viewport's width post-animation. */
|
|
__init5() {this.width = null;}
|
|
|
|
/** The viewport's height post-animation. */
|
|
__init6() {this.height = null;}
|
|
|
|
/** The time since the animation started. */
|
|
__init7() {this.time = 0;}
|
|
|
|
/**
|
|
* This is called by {@link Viewport.animate}.
|
|
*
|
|
* @param parent
|
|
* @param options
|
|
*/
|
|
constructor(parent, options = {})
|
|
{
|
|
super(parent);Animate.prototype.__init.call(this);Animate.prototype.__init2.call(this);Animate.prototype.__init3.call(this);Animate.prototype.__init4.call(this);Animate.prototype.__init5.call(this);Animate.prototype.__init6.call(this);Animate.prototype.__init7.call(this);;
|
|
|
|
this.options = Object.assign({}, DEFAULT_ANIMATE_OPTIONS, options);
|
|
this.options.ease = ease(this.options.ease);
|
|
|
|
this.setupPosition();
|
|
this.setupZoom();
|
|
|
|
this.time = 0;
|
|
}
|
|
|
|
/**
|
|
* Setup `startX`, `startY`, `deltaX`, `deltaY`, `keepCenter`.
|
|
*
|
|
* This is called during construction.
|
|
*/
|
|
setupPosition()
|
|
{
|
|
if (typeof this.options.position !== 'undefined')
|
|
{
|
|
this.startX = this.parent.center.x;
|
|
this.startY = this.parent.center.y;
|
|
this.deltaX = this.options.position.x - this.parent.center.x;
|
|
this.deltaY = this.options.position.y - this.parent.center.y;
|
|
this.keepCenter = false;
|
|
}
|
|
else
|
|
{
|
|
this.keepCenter = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup `startWidth, `startHeight`, `deltaWidth, `deltaHeight, `width`, `height`.
|
|
*
|
|
* This is called during construction.
|
|
*/
|
|
setupZoom()
|
|
{
|
|
this.width = null;
|
|
this.height = null;
|
|
|
|
if (typeof this.options.scale !== 'undefined')
|
|
{
|
|
this.width = this.parent.screenWidth / this.options.scale;
|
|
}
|
|
else if (typeof this.options.scaleX !== 'undefined' || typeof this.options.scaleY !== 'undefined')
|
|
{
|
|
if (typeof this.options.scaleX !== 'undefined')
|
|
{
|
|
// screenSizeInWorldPixels = screenWidth / scale
|
|
this.width = this.parent.screenWidth / this.options.scaleX;
|
|
}
|
|
if (typeof this.options.scaleY !== 'undefined')
|
|
{
|
|
this.height = this.parent.screenHeight / this.options.scaleY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (typeof this.options.width !== 'undefined')
|
|
{
|
|
this.width = this.options.width;
|
|
}
|
|
if (typeof this.options.height !== 'undefined')
|
|
{
|
|
this.height = this.options.height;
|
|
}
|
|
}
|
|
|
|
if (this.width !== null)
|
|
{
|
|
this.startWidth = this.parent.screenWidthInWorldPixels;
|
|
this.deltaWidth = this.width - this.startWidth;
|
|
}
|
|
if (this.height !== null)
|
|
{
|
|
this.startHeight = this.parent.screenHeightInWorldPixels;
|
|
this.deltaHeight = this.height - this.startHeight;
|
|
}
|
|
}
|
|
|
|
down()
|
|
{
|
|
if (this.options.removeOnInterrupt)
|
|
{
|
|
this.parent.plugins.remove('animate');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
complete()
|
|
{
|
|
this.parent.plugins.remove('animate');
|
|
if (this.width !== null)
|
|
{
|
|
this.parent.fitWidth(this.width, this.keepCenter, this.height === null);
|
|
}
|
|
if (this.height !== null)
|
|
{
|
|
this.parent.fitHeight(this.height, this.keepCenter, this.width === null);
|
|
}
|
|
if (!this.keepCenter)
|
|
{
|
|
this.parent.moveCenter(this.options.position);
|
|
}
|
|
|
|
this.parent.emit('animate-end', this.parent);
|
|
|
|
if (this.options.callbackOnComplete)
|
|
{
|
|
this.options.callbackOnComplete(this.parent);
|
|
}
|
|
}
|
|
|
|
update(elapsed)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
this.time += elapsed;
|
|
|
|
const originalZoom = new math.Point(this.parent.scale.x, this.parent.scale.y);
|
|
|
|
if (this.time >= this.options.time)
|
|
{
|
|
const originalWidth = this.parent.width;
|
|
const originalHeight = this.parent.height;
|
|
|
|
this.complete();
|
|
if (originalWidth !== this.parent.width || originalHeight !== this.parent.height)
|
|
{
|
|
this.parent.emit('zoomed', { viewport: this.parent, original: originalZoom, type: 'animate' });
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const percent = this.options.ease(this.time, 0, 1, this.options.time);
|
|
|
|
if (this.width !== null)
|
|
{
|
|
const startWidth = this.startWidth ;
|
|
const deltaWidth = this.deltaWidth ;
|
|
|
|
this.parent.fitWidth(
|
|
startWidth + (deltaWidth * percent),
|
|
this.keepCenter,
|
|
this.height === null);
|
|
}
|
|
if (this.height !== null)
|
|
{
|
|
const startHeight = this.startHeight ;
|
|
const deltaHeight = this.deltaHeight ;
|
|
|
|
this.parent.fitHeight(
|
|
startHeight + (deltaHeight * percent),
|
|
this.keepCenter,
|
|
this.width === null);
|
|
}
|
|
if (this.width === null)
|
|
{
|
|
this.parent.scale.x = this.parent.scale.y;
|
|
}
|
|
else if (this.height === null)
|
|
{
|
|
this.parent.scale.y = this.parent.scale.x;
|
|
}
|
|
if (!this.keepCenter)
|
|
{
|
|
const startX = this.startX ;
|
|
const startY = this.startY ;
|
|
const deltaX = this.deltaX ;
|
|
const deltaY = this.deltaY ;
|
|
const original = new math.Point(this.parent.x, this.parent.y);
|
|
|
|
this.parent.moveCenter(startX + (deltaX * percent), startY + (deltaY * percent));
|
|
this.parent.emit('moved', { viewport: this.parent, original, type: 'animate' });
|
|
}
|
|
if (this.width || this.height)
|
|
{
|
|
this.parent.emit('zoomed', { viewport: this.parent, original: originalZoom, type: 'animate' });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function _optionalChain$1(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
|
|
|
|
|
|
|
|
|
|
/** Options for {@link Bounce}. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_BOUNCE_OPTIONS = {
|
|
sides: 'all',
|
|
friction: 0.5,
|
|
time: 150,
|
|
ease: 'easeInOutSine',
|
|
underflow: 'center',
|
|
bounceBox: null
|
|
};
|
|
|
|
/**
|
|
* @fires bounce-start-x
|
|
* @fires bounce.end-x
|
|
* @fires bounce-start-y
|
|
* @fires bounce-end-y
|
|
* @public
|
|
*/
|
|
class Bounce extends Plugin
|
|
{
|
|
/** The options passed to initialize this plugin, cannot be modified again. */
|
|
|
|
|
|
/** Holds whether to bounce from left side. */
|
|
|
|
|
|
/** Holds whether to bounce from top side. */
|
|
|
|
|
|
/** Holds whether to bounce from right side. */
|
|
|
|
|
|
/** Holds whether to bounce from bottom side. */
|
|
|
|
|
|
/** Direction of underflow along x-axis. */
|
|
|
|
|
|
/** Direction of underflow along y-axis. */
|
|
|
|
|
|
/** Easing */
|
|
|
|
|
|
/** Bounce state along x-axis */
|
|
|
|
|
|
/** Bounce state along y-axis */
|
|
|
|
|
|
/**
|
|
* This is called by {@link Viewport.bounce}.
|
|
*/
|
|
constructor(parent, options = {})
|
|
{
|
|
super(parent);
|
|
|
|
this.options = Object.assign({}, DEFAULT_BOUNCE_OPTIONS, options);
|
|
this.ease = ease(this.options.ease, 'easeInOutSine');
|
|
|
|
if (this.options.sides)
|
|
{
|
|
if (this.options.sides === 'all')
|
|
{
|
|
this.top = this.bottom = this.left = this.right = true;
|
|
}
|
|
else if (this.options.sides === 'horizontal')
|
|
{
|
|
this.right = this.left = true;
|
|
this.top = this.bottom = false;
|
|
}
|
|
else if (this.options.sides === 'vertical')
|
|
{
|
|
this.left = this.right = false;
|
|
this.top = this.bottom = true;
|
|
}
|
|
else
|
|
{
|
|
this.top = this.options.sides.indexOf('top') !== -1;
|
|
this.bottom = this.options.sides.indexOf('bottom') !== -1;
|
|
this.left = this.options.sides.indexOf('left') !== -1;
|
|
this.right = this.options.sides.indexOf('right') !== -1;
|
|
}
|
|
} else {
|
|
this.left = this.top = this.right = this.bottom = false;
|
|
}
|
|
|
|
const clamp = this.options.underflow.toLowerCase();
|
|
|
|
if (clamp === 'center')
|
|
{
|
|
this.underflowX = 0;
|
|
this.underflowY = 0;
|
|
}
|
|
else
|
|
{
|
|
this.underflowX = (clamp.indexOf('left') !== -1) ? -1 : (clamp.indexOf('right') !== -1) ? 1 : 0;
|
|
this.underflowY = (clamp.indexOf('top') !== -1) ? -1 : (clamp.indexOf('bottom') !== -1) ? 1 : 0;
|
|
}
|
|
|
|
this.reset();
|
|
}
|
|
|
|
isActive()
|
|
{
|
|
return this.toX !== null || this.toY !== null;
|
|
}
|
|
|
|
down()
|
|
{
|
|
this.toX = this.toY = null;
|
|
|
|
return false;
|
|
}
|
|
|
|
up()
|
|
{
|
|
this.bounce();
|
|
|
|
return false;
|
|
}
|
|
|
|
update(elapsed)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.bounce();
|
|
|
|
if (this.toX)
|
|
{
|
|
const toX = this.toX;
|
|
|
|
toX.time += elapsed;
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'bounce-x' });
|
|
|
|
if (toX.time >= this.options.time)
|
|
{
|
|
this.parent.x = toX.end;
|
|
this.toX = null;
|
|
this.parent.emit('bounce-x-end', this.parent);
|
|
}
|
|
else
|
|
{
|
|
this.parent.x = this.ease(toX.time, toX.start, toX.delta, this.options.time);
|
|
}
|
|
}
|
|
|
|
if (this.toY)
|
|
{
|
|
const toY = this.toY;
|
|
|
|
toY.time += elapsed;
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'bounce-y' });
|
|
|
|
if (toY.time >= this.options.time)
|
|
{
|
|
this.parent.y = toY.end;
|
|
this.toY = null;
|
|
this.parent.emit('bounce-y-end', this.parent);
|
|
}
|
|
else
|
|
{
|
|
this.parent.y = this.ease(toY.time, toY.start, toY.delta, this.options.time);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @internal */
|
|
calcUnderflowX()
|
|
{
|
|
let x;
|
|
|
|
switch (this.underflowX)
|
|
{
|
|
case -1:
|
|
x = 0;
|
|
break;
|
|
case 1:
|
|
x = (this.parent.screenWidth - this.parent.screenWorldWidth);
|
|
break;
|
|
default:
|
|
x = (this.parent.screenWidth - this.parent.screenWorldWidth) / 2;
|
|
}
|
|
|
|
return x;
|
|
}
|
|
|
|
/** @internal */
|
|
calcUnderflowY()
|
|
{
|
|
let y;
|
|
|
|
switch (this.underflowY)
|
|
{
|
|
case -1:
|
|
y = 0;
|
|
break;
|
|
case 1:
|
|
y = (this.parent.screenHeight - this.parent.screenWorldHeight);
|
|
break;
|
|
default:
|
|
y = (this.parent.screenHeight - this.parent.screenWorldHeight) / 2;
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
oob()
|
|
{
|
|
const box = this.options.bounceBox;
|
|
|
|
if (box)
|
|
{
|
|
const x1 = typeof box.x === 'undefined' ? 0 : box.x;
|
|
const y1 = typeof box.y === 'undefined' ? 0 : box.y;
|
|
const width = typeof box.width === 'undefined' ? this.parent.worldWidth : box.width;
|
|
const height = typeof box.height === 'undefined' ? this.parent.worldHeight : box.height;
|
|
|
|
return {
|
|
left: this.parent.left < x1,
|
|
right: this.parent.right > width,
|
|
top: this.parent.top < y1,
|
|
bottom: this.parent.bottom > height,
|
|
topLeft: new math.Point(
|
|
x1 * this.parent.scale.x,
|
|
y1 * this.parent.scale.y
|
|
),
|
|
bottomRight: new math.Point(
|
|
width * this.parent.scale.x - this.parent.screenWidth,
|
|
height * this.parent.scale.y - this.parent.screenHeight
|
|
)
|
|
};
|
|
}
|
|
|
|
return {
|
|
left: this.parent.left < 0,
|
|
right: this.parent.right > this.parent.worldWidth,
|
|
top: this.parent.top < 0,
|
|
bottom: this.parent.bottom > this.parent.worldHeight,
|
|
topLeft: new math.Point(0, 0),
|
|
bottomRight: new math.Point(
|
|
this.parent.worldWidth * this.parent.scale.x - this.parent.screenWidth,
|
|
this.parent.worldHeight * this.parent.scale.y - this.parent.screenHeight
|
|
)
|
|
};
|
|
}
|
|
|
|
bounce()
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
|
|
let oob;
|
|
let decelerate
|
|
|
|
|
|
|
|
|
|
|
|
= this.parent.plugins.get('decelerate', true) ;
|
|
|
|
if (decelerate && (decelerate.x || decelerate.y))
|
|
{
|
|
if ((decelerate.x && decelerate.percentChangeX === _optionalChain$1([decelerate, 'access', _ => _.options, 'optionalAccess', _2 => _2.friction])) || (decelerate.y && decelerate.percentChangeY === _optionalChain$1([decelerate, 'access', _3 => _3.options, 'optionalAccess', _4 => _4.friction])))
|
|
{
|
|
oob = this.oob();
|
|
if ((oob.left && this.left) || (oob.right && this.right))
|
|
{
|
|
decelerate.percentChangeX = this.options.friction;
|
|
}
|
|
if ((oob.top && this.top) || (oob.bottom && this.bottom))
|
|
{
|
|
decelerate.percentChangeY = this.options.friction;
|
|
}
|
|
}
|
|
}
|
|
const drag = this.parent.plugins.get('drag', true) || {};
|
|
const pinch = this.parent.plugins.get('pinch', true) || {};
|
|
|
|
decelerate = decelerate || {};
|
|
|
|
if (!_optionalChain$1([drag, 'optionalAccess', _5 => _5.active]) && !_optionalChain$1([pinch, 'optionalAccess', _6 => _6.active]) && ((!this.toX || !this.toY) && (!decelerate.x || !decelerate.y)))
|
|
{
|
|
oob = oob || this.oob();
|
|
const topLeft = oob.topLeft;
|
|
const bottomRight = oob.bottomRight;
|
|
|
|
if (!this.toX && !decelerate.x)
|
|
{
|
|
let x = null;
|
|
|
|
if (oob.left && this.left)
|
|
{
|
|
x = (this.parent.screenWorldWidth < this.parent.screenWidth) ? this.calcUnderflowX() : -topLeft.x;
|
|
}
|
|
else if (oob.right && this.right)
|
|
{
|
|
x = (this.parent.screenWorldWidth < this.parent.screenWidth) ? this.calcUnderflowX() : -bottomRight.x;
|
|
}
|
|
if (x !== null && this.parent.x !== x)
|
|
{
|
|
this.toX = { time: 0, start: this.parent.x, delta: x - this.parent.x, end: x };
|
|
this.parent.emit('bounce-x-start', this.parent);
|
|
}
|
|
}
|
|
if (!this.toY && !decelerate.y)
|
|
{
|
|
let y = null;
|
|
|
|
if (oob.top && this.top)
|
|
{
|
|
y = (this.parent.screenWorldHeight < this.parent.screenHeight) ? this.calcUnderflowY() : -topLeft.y;
|
|
}
|
|
else if (oob.bottom && this.bottom)
|
|
{
|
|
y = (this.parent.screenWorldHeight < this.parent.screenHeight) ? this.calcUnderflowY() : -bottomRight.y;
|
|
}
|
|
if (y !== null && this.parent.y !== y)
|
|
{
|
|
this.toY = { time: 0, start: this.parent.y, delta: y - this.parent.y, end: y };
|
|
this.parent.emit('bounce-y-start', this.parent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
reset()
|
|
{
|
|
this.toX = this.toY = null;
|
|
this.bounce();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* There are three ways to clamp:
|
|
* 1. direction: 'all' = the world is clamped to its world boundaries, ie, you cannot drag any part of the world offscreen
|
|
* direction: 'x' | 'y' = only the x or y direction is clamped to its world boundary
|
|
* 2. left, right, top, bottom = true | number = the world is clamped to the world's pixel location for each side;
|
|
* if any of these are set to true, then the location is set to the boundary [0, viewport.worldWidth/viewport.worldHeight]
|
|
* eg: to allow the world to be completely dragged offscreen, set [-viewport.worldWidth, -viewport.worldHeight, viewport.worldWidth * 2, viewport.worldHeight * 2]
|
|
*
|
|
* Underflow determines what happens when the world is smaller than the viewport
|
|
* 1. none = the world is clamped but there is no special behavior
|
|
* 2. center = the world is centered on the viewport
|
|
* 3. combination of top/bottom/center and left/right/center (case insensitive) = the world is stuck to the appropriate boundaries
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_CLAMP_OPTIONS = {
|
|
left: false,
|
|
right: false,
|
|
top: false,
|
|
bottom: false,
|
|
direction: null,
|
|
underflow: 'center'
|
|
};
|
|
|
|
/**
|
|
* Plugin to clamp the viewport to a specific world bounding box.
|
|
*
|
|
* @public
|
|
*/
|
|
class Clamp extends Plugin
|
|
{
|
|
/** Options used to initialize this plugin, cannot be modified later. */
|
|
|
|
|
|
/** Last state of viewport */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* This is called by {@link Viewport.clamp}.
|
|
*/
|
|
constructor(parent, options = {})
|
|
{
|
|
super(parent);
|
|
this.options = Object.assign({}, DEFAULT_CLAMP_OPTIONS, options);
|
|
|
|
if (this.options.direction)
|
|
{
|
|
this.options.left = this.options.direction === 'x' || this.options.direction === 'all' ? true : null;
|
|
this.options.right = this.options.direction === 'x' || this.options.direction === 'all' ? true : null;
|
|
this.options.top = this.options.direction === 'y' || this.options.direction === 'all' ? true : null;
|
|
this.options.bottom = this.options.direction === 'y' || this.options.direction === 'all' ? true : null;
|
|
}
|
|
|
|
this.parseUnderflow();
|
|
this.last = { x: null, y: null, scaleX: null, scaleY: null };
|
|
this.update();
|
|
}
|
|
|
|
parseUnderflow()
|
|
{
|
|
const clamp = this.options.underflow.toLowerCase();
|
|
|
|
if (clamp === 'none')
|
|
{
|
|
this.noUnderflow = true;
|
|
}
|
|
else if (clamp === 'center')
|
|
{
|
|
this.underflowX = this.underflowY = 0;
|
|
this.noUnderflow = false;
|
|
}
|
|
else
|
|
{
|
|
this.underflowX = (clamp.indexOf('left') !== -1) ? -1 : (clamp.indexOf('right') !== -1) ? 1 : 0;
|
|
this.underflowY = (clamp.indexOf('top') !== -1) ? -1 : (clamp.indexOf('bottom') !== -1) ? 1 : 0;
|
|
this.noUnderflow = false;
|
|
}
|
|
}
|
|
|
|
move()
|
|
{
|
|
this.update();
|
|
|
|
return false;
|
|
}
|
|
|
|
update()
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// only clamp on change
|
|
if (this.parent.x === this.last.x
|
|
&& this.parent.y === this.last.y
|
|
&& this.parent.scale.x === this.last.scaleX
|
|
&& this.parent.scale.y === this.last.scaleY)
|
|
{
|
|
return;
|
|
}
|
|
const original = { x: this.parent.x, y: this.parent.y };
|
|
// TODO: Fix
|
|
const decelerate = (this.parent.plugins ).decelerate || {};
|
|
|
|
if (this.options.left !== null || this.options.right !== null)
|
|
{
|
|
let moved = false;
|
|
|
|
if (!this.noUnderflow && this.parent.screenWorldWidth < this.parent.screenWidth)
|
|
{
|
|
switch (this.underflowX)
|
|
{
|
|
case -1:
|
|
if (this.parent.x !== 0)
|
|
{
|
|
this.parent.x = 0;
|
|
moved = true;
|
|
}
|
|
break;
|
|
case 1:
|
|
if (this.parent.x !== this.parent.screenWidth - this.parent.screenWorldWidth)
|
|
{
|
|
this.parent.x = this.parent.screenWidth - this.parent.screenWorldWidth;
|
|
moved = true;
|
|
}
|
|
break;
|
|
default:
|
|
if (this.parent.x !== (this.parent.screenWidth - this.parent.screenWorldWidth) / 2)
|
|
{
|
|
this.parent.x = (this.parent.screenWidth - this.parent.screenWorldWidth) / 2;
|
|
moved = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.options.left !== null)
|
|
{
|
|
if (this.parent.left < (this.options.left === true ? 0 : this.options.left))
|
|
{
|
|
this.parent.x = -(this.options.left === true ? 0 : this.options.left) * this.parent.scale.x;
|
|
decelerate.x = 0;
|
|
moved = true;
|
|
}
|
|
}
|
|
if (this.options.right !== null)
|
|
{
|
|
if (this.parent.right > (this.options.right === true ? this.parent.worldWidth : this.options.right))
|
|
{
|
|
this.parent.x = -(this.options.right === true ? this.parent.worldWidth : this.options.right) * this.parent.scale.x + this.parent.screenWidth;
|
|
decelerate.x = 0;
|
|
moved = true;
|
|
}
|
|
}
|
|
}
|
|
if (moved)
|
|
{
|
|
this.parent.emit('moved', { viewport: this.parent, original, type: 'clamp-x' });
|
|
}
|
|
}
|
|
if (this.options.top !== null || this.options.bottom !== null)
|
|
{
|
|
let moved = false;
|
|
|
|
if (!this.noUnderflow && this.parent.screenWorldHeight < this.parent.screenHeight)
|
|
{
|
|
switch (this.underflowY)
|
|
{
|
|
case -1:
|
|
if (this.parent.y !== 0)
|
|
{
|
|
this.parent.y = 0;
|
|
moved = true;
|
|
}
|
|
break;
|
|
case 1:
|
|
if (this.parent.y !== this.parent.screenHeight - this.parent.screenWorldHeight)
|
|
{
|
|
this.parent.y = (this.parent.screenHeight - this.parent.screenWorldHeight);
|
|
moved = true;
|
|
}
|
|
break;
|
|
default:
|
|
if (this.parent.y !== (this.parent.screenHeight - this.parent.screenWorldHeight) / 2)
|
|
{
|
|
this.parent.y = (this.parent.screenHeight - this.parent.screenWorldHeight) / 2;
|
|
moved = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.options.top !== null)
|
|
{
|
|
if (this.parent.top < (this.options.top === true ? 0 : this.options.top))
|
|
{
|
|
this.parent.y = -(this.options.top === true ? 0 : this.options.top)
|
|
* this.parent.scale.y;
|
|
decelerate.y = 0;
|
|
moved = true;
|
|
}
|
|
}
|
|
if (this.options.bottom !== null)
|
|
{
|
|
if (this.parent.bottom > (this.options.bottom === true ? this.parent.worldHeight : this.options.bottom))
|
|
{
|
|
this.parent.y = -(this.options.bottom === true ? this.parent.worldHeight : this.options.bottom)
|
|
* this.parent.scale.y + this.parent.screenHeight;
|
|
decelerate.y = 0;
|
|
moved = true;
|
|
}
|
|
}
|
|
}
|
|
if (moved)
|
|
{
|
|
this.parent.emit('moved', { viewport: this.parent, original, type: 'clamp-y' });
|
|
}
|
|
}
|
|
this.last.x = this.parent.x;
|
|
this.last.y = this.parent.y;
|
|
this.last.scaleX = this.parent.scale.x;
|
|
this.last.scaleY = this.parent.scale.y;
|
|
}
|
|
|
|
reset()
|
|
{
|
|
this.update();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Options for {@link ClampZoom}.
|
|
*
|
|
* Use either minimum width/height or minimum scale
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_CLAMP_ZOOM_OPTIONS = {
|
|
minWidth: null,
|
|
minHeight: null,
|
|
maxWidth: null,
|
|
maxHeight: null,
|
|
minScale: null,
|
|
maxScale: null
|
|
};
|
|
|
|
/**
|
|
* Plugin to clamp the viewport's zoom to a specific range.
|
|
*
|
|
* @public
|
|
*/
|
|
class ClampZoom extends Plugin
|
|
{
|
|
|
|
|
|
/**
|
|
* This is called by {@link Viewport.clampZoom}.
|
|
*/
|
|
constructor(parent, options = {})
|
|
{
|
|
super(parent);
|
|
this.options = Object.assign({}, DEFAULT_CLAMP_ZOOM_OPTIONS, options);
|
|
|
|
this.clamp();
|
|
}
|
|
|
|
resize()
|
|
{
|
|
this.clamp();
|
|
}
|
|
|
|
/** Clamp the viewport scale zoom) */
|
|
clamp()
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.options.minWidth || this.options.minHeight || this.options.maxWidth || this.options.maxHeight)
|
|
{
|
|
let width = this.parent.worldScreenWidth;
|
|
let height = this.parent.worldScreenHeight;
|
|
|
|
if (this.options.minWidth !== null && width < this.options.minWidth)
|
|
{
|
|
const original = this.parent.scale.x;
|
|
|
|
this.parent.fitWidth(this.options.minWidth, false, false, true);
|
|
this.parent.scale.y *= this.parent.scale.x / original;
|
|
width = this.parent.worldScreenWidth;
|
|
height = this.parent.worldScreenHeight;
|
|
this.parent.emit('zoomed', { viewport: this.parent, type: 'clamp-zoom' });
|
|
}
|
|
if (this.options.maxWidth !== null && width > this.options.maxWidth)
|
|
{
|
|
const original = this.parent.scale.x;
|
|
|
|
this.parent.fitWidth(this.options.maxWidth, false, false, true);
|
|
this.parent.scale.y *= this.parent.scale.x / original;
|
|
width = this.parent.worldScreenWidth;
|
|
height = this.parent.worldScreenHeight;
|
|
this.parent.emit('zoomed', { viewport: this.parent, type: 'clamp-zoom' });
|
|
}
|
|
if (this.options.minHeight !== null && height < this.options.minHeight)
|
|
{
|
|
const original = this.parent.scale.y;
|
|
|
|
this.parent.fitHeight(this.options.minHeight, false, false, true);
|
|
this.parent.scale.x *= this.parent.scale.y / original;
|
|
width = this.parent.worldScreenWidth;
|
|
height = this.parent.worldScreenHeight;
|
|
this.parent.emit('zoomed', { viewport: this.parent, type: 'clamp-zoom' });
|
|
}
|
|
if (this.options.maxHeight !== null && height > this.options.maxHeight)
|
|
{
|
|
const original = this.parent.scale.y;
|
|
|
|
this.parent.fitHeight(this.options.maxHeight, false, false, true);
|
|
this.parent.scale.x *= this.parent.scale.y / original;
|
|
this.parent.emit('zoomed', { viewport: this.parent, type: 'clamp-zoom' });
|
|
}
|
|
}
|
|
else
|
|
if (this.options.minScale || this.options.maxScale)
|
|
{
|
|
const minScale = { x: null, y: null };
|
|
const maxScale = { x: null, y: null };
|
|
|
|
if (typeof this.options.minScale === 'number')
|
|
{
|
|
minScale.x = this.options.minScale;
|
|
minScale.y = this.options.minScale;
|
|
}
|
|
else if (this.options.minScale !== null)
|
|
{
|
|
const optsMinScale = this.options.minScale ;
|
|
|
|
minScale.x = typeof optsMinScale.x === 'undefined' ? null : optsMinScale.x;
|
|
minScale.y = typeof optsMinScale.y === 'undefined' ? null : optsMinScale.y;
|
|
}
|
|
|
|
if (typeof this.options.maxScale === 'number')
|
|
{
|
|
maxScale.x = this.options.maxScale;
|
|
maxScale.y = this.options.maxScale;
|
|
}
|
|
else if (this.options.maxScale !== null)
|
|
{
|
|
const optsMaxScale = this.options.maxScale ;
|
|
|
|
maxScale.x = typeof optsMaxScale.x === 'undefined' ? null : optsMaxScale.x;
|
|
maxScale.y = typeof optsMaxScale.y === 'undefined' ? null : optsMaxScale.y;
|
|
}
|
|
|
|
let scaleX = this.parent.scale.x;
|
|
let scaleY = this.parent.scale.y;
|
|
|
|
if (minScale.x !== null && scaleX < minScale.x)
|
|
{
|
|
scaleX = minScale.x;
|
|
}
|
|
if (maxScale.x !== null && scaleX > maxScale.x)
|
|
{
|
|
scaleX = maxScale.x;
|
|
}
|
|
if (minScale.y !== null && scaleY < minScale.y)
|
|
{
|
|
scaleY = minScale.y;
|
|
}
|
|
if (maxScale.y !== null && scaleY > maxScale.y)
|
|
{
|
|
scaleY = maxScale.y;
|
|
}
|
|
if (scaleX !== this.parent.scale.x || scaleY !== this.parent.scale.y)
|
|
{
|
|
this.parent.scale.set(scaleX, scaleY);
|
|
this.parent.emit('zoomed', { viewport: this.parent, type: 'clamp-zoom' });
|
|
}
|
|
}
|
|
}
|
|
|
|
reset()
|
|
{
|
|
this.clamp();
|
|
}
|
|
}
|
|
|
|
/** This allows independent x and y values for min/maxScale */
|
|
|
|
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
|
|
*/
|
|
class Decelerate extends Plugin
|
|
{
|
|
/** Options used to initialize this plugin. */
|
|
|
|
|
|
/**
|
|
* x-component of the velocity of viewport provided by this plugin, at the current time.
|
|
*
|
|
* This is measured in px/frame, where a frame is normalized to 16 milliseconds.
|
|
*/
|
|
|
|
|
|
/**
|
|
* y-component of the velocity of the viewport provided by this plugin, at the current time.
|
|
*
|
|
* This is measured in px/frame, where a frame is normalized to 16 milliseconds.
|
|
*/
|
|
|
|
|
|
/**
|
|
* The decay factor for the x-component of the viewport.
|
|
*
|
|
* The viewport's velocity decreased by this amount each 16 milliseconds.
|
|
*/
|
|
|
|
|
|
/**
|
|
* The decay factor for the y-component of the viewport.
|
|
*
|
|
* The viewport's velocity decreased by this amount each 16 milliseconds.
|
|
*/
|
|
|
|
|
|
/** Saved list of recent viewport position snapshots, to estimate velocity. */
|
|
|
|
|
|
/** The time since the user released panning of the viewport. */
|
|
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
/** Options for {@link Drag}. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_DRAG_OPTIONS = {
|
|
direction: 'all',
|
|
pressDrag: true,
|
|
wheel: true,
|
|
wheelScroll: 1,
|
|
reverse: false,
|
|
clampWheel: false,
|
|
underflow: 'center',
|
|
factor: 1,
|
|
mouseButtons: 'all',
|
|
keyToPress: null,
|
|
ignoreKeyToPressOnTouch: false,
|
|
lineHeight: 20,
|
|
};
|
|
|
|
/**
|
|
* Plugin to enable panning/dragging of the viewport to move around.
|
|
*
|
|
* @public
|
|
*/
|
|
class Drag extends Plugin
|
|
{
|
|
/** Options used to initialize this plugin, cannot be modified later. */
|
|
|
|
|
|
/** Flags when viewport is moving. */
|
|
|
|
|
|
/** Factor to apply from {@link IDecelerateOptions}'s reverse. */
|
|
|
|
|
|
/** Holds whether dragging is enabled along the x-axis. */
|
|
|
|
|
|
/** Holds whether dragging is enabled along the y-axis. */
|
|
|
|
|
|
/** Flags whether the keys required to drag are pressed currently. */
|
|
|
|
|
|
/** Holds whether the left, center, and right buttons are required to pan. */
|
|
|
|
|
|
/** Underflow factor along x-axis */
|
|
|
|
|
|
/** Underflow factor along y-axis */
|
|
|
|
|
|
/** Last pointer position while panning. */
|
|
|
|
|
|
/** The ID of the pointer currently panning the viewport. */
|
|
|
|
|
|
/**
|
|
* This is called by {@link Viewport.drag}.
|
|
*/
|
|
constructor(parent, options = {})
|
|
{
|
|
super(parent);
|
|
|
|
this.options = Object.assign({}, DEFAULT_DRAG_OPTIONS, options);
|
|
this.moved = false;
|
|
this.reverse = this.options.reverse ? 1 : -1;
|
|
this.xDirection = !this.options.direction || this.options.direction === 'all' || this.options.direction === 'x';
|
|
this.yDirection = !this.options.direction || this.options.direction === 'all' || this.options.direction === 'y';
|
|
this.keyIsPressed = false;
|
|
|
|
this.parseUnderflow();
|
|
this.mouseButtons(this.options.mouseButtons);
|
|
|
|
if (this.options.keyToPress)
|
|
{
|
|
this.handleKeyPresses(this.options.keyToPress);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles keypress events and set the keyIsPressed boolean accordingly
|
|
*
|
|
* @param {array} codes - key codes that can be used to trigger drag event
|
|
*/
|
|
handleKeyPresses(codes)
|
|
{
|
|
window.addEventListener('keydown', (e) =>
|
|
{
|
|
if (codes.includes(e.code))
|
|
{ this.keyIsPressed = true; }
|
|
});
|
|
|
|
window.addEventListener('keyup', (e) =>
|
|
{
|
|
if (codes.includes(e.code))
|
|
{ this.keyIsPressed = false; }
|
|
});
|
|
}
|
|
|
|
/**
|
|
* initialize mousebuttons array
|
|
* @param {string} buttons
|
|
*/
|
|
mouseButtons(buttons)
|
|
{
|
|
if (!buttons || buttons === 'all')
|
|
{
|
|
this.mouse = [true, true, true];
|
|
}
|
|
else
|
|
{
|
|
this.mouse = [
|
|
buttons.indexOf('left') !== -1,
|
|
buttons.indexOf('middle') !== -1,
|
|
buttons.indexOf('right') !== -1
|
|
];
|
|
}
|
|
}
|
|
|
|
parseUnderflow()
|
|
{
|
|
const clamp = this.options.underflow.toLowerCase();
|
|
|
|
if (clamp === 'center')
|
|
{
|
|
this.underflowX = 0;
|
|
this.underflowY = 0;
|
|
}
|
|
else
|
|
{
|
|
if (clamp.includes('left'))
|
|
{
|
|
this.underflowX = -1;
|
|
}
|
|
else if (clamp.includes('right'))
|
|
{
|
|
this.underflowX = 1;
|
|
}
|
|
else
|
|
{
|
|
this.underflowX = 0;
|
|
}
|
|
if (clamp.includes('top'))
|
|
{
|
|
this.underflowY = -1;
|
|
}
|
|
else if (clamp.includes('bottom'))
|
|
{
|
|
this.underflowY = 1;
|
|
}
|
|
else
|
|
{
|
|
this.underflowY = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {PIXI.InteractionEvent} event
|
|
* @returns {boolean}
|
|
*/
|
|
checkButtons(event)
|
|
{
|
|
const isMouse = event.data.pointerType === 'mouse';
|
|
const count = this.parent.input.count();
|
|
|
|
if ((count === 1) || (count > 1 && !this.parent.plugins.get('pinch', true)))
|
|
{
|
|
if (!isMouse || this.mouse[event.data.button])
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param {PIXI.InteractionEvent} event
|
|
* @returns {boolean}
|
|
*/
|
|
checkKeyPress(event)
|
|
{
|
|
return (!this.options.keyToPress
|
|
|| this.keyIsPressed
|
|
|| (this.options.ignoreKeyToPressOnTouch && event.data.pointerType === 'touch'));
|
|
}
|
|
|
|
down(event)
|
|
{
|
|
if (this.paused || !this.options.pressDrag)
|
|
{
|
|
return false;
|
|
}
|
|
if (this.checkButtons(event) && this.checkKeyPress(event))
|
|
{
|
|
this.last = { x: event.data.global.x, y: event.data.global.y };
|
|
this.current = event.data.pointerId;
|
|
|
|
return true;
|
|
}
|
|
this.last = null;
|
|
|
|
return false;
|
|
}
|
|
|
|
get active()
|
|
{
|
|
return this.moved;
|
|
}
|
|
|
|
move(event)
|
|
{
|
|
if (this.paused || !this.options.pressDrag)
|
|
{
|
|
return false;
|
|
}
|
|
if (this.last && this.current === event.data.pointerId)
|
|
{
|
|
const x = event.data.global.x;
|
|
const y = event.data.global.y;
|
|
const count = this.parent.input.count();
|
|
|
|
if (count === 1 || (count > 1 && !this.parent.plugins.get('pinch', true)))
|
|
{
|
|
const distX = x - this.last.x;
|
|
const distY = y - this.last.y;
|
|
|
|
if (this.moved
|
|
|| ((this.xDirection && this.parent.input.checkThreshold(distX))
|
|
|| (this.yDirection && this.parent.input.checkThreshold(distY))))
|
|
{
|
|
const newPoint = { x, y };
|
|
|
|
if (this.xDirection)
|
|
{
|
|
this.parent.x += (newPoint.x - this.last.x) * this.options.factor;
|
|
}
|
|
if (this.yDirection)
|
|
{
|
|
this.parent.y += (newPoint.y - this.last.y) * this.options.factor;
|
|
}
|
|
this.last = newPoint;
|
|
if (!this.moved)
|
|
{
|
|
this.parent.emit('drag-start', {
|
|
event,
|
|
screen: new math.Point(this.last.x, this.last.y),
|
|
world: this.parent.toWorld(new math.Point(this.last.x, this.last.y)),
|
|
viewport: this.parent
|
|
});
|
|
}
|
|
this.moved = true;
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'drag' });
|
|
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.moved = false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
up(event)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const touches = this.parent.input.touches;
|
|
|
|
if (touches.length === 1)
|
|
{
|
|
const pointer = touches[0];
|
|
|
|
if (pointer.last)
|
|
{
|
|
this.last = { x: pointer.last.x, y: pointer.last.y };
|
|
this.current = pointer.id;
|
|
}
|
|
this.moved = false;
|
|
|
|
return true;
|
|
}
|
|
else if (this.last)
|
|
{
|
|
if (this.moved)
|
|
{
|
|
const screen = new math.Point(this.last.x, this.last.y);
|
|
|
|
this.parent.emit('drag-end', {
|
|
event, screen,
|
|
world: this.parent.toWorld(screen),
|
|
viewport: this.parent,
|
|
});
|
|
this.last = null;
|
|
this.moved = false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
wheel(event)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (this.options.wheel)
|
|
{
|
|
const wheel = this.parent.plugins.get('wheel', true);
|
|
|
|
if (!wheel || (!wheel.options.wheelZoom && !event.ctrlKey))
|
|
{
|
|
const step = event.deltaMode ? this.options.lineHeight : 1;
|
|
|
|
if (this.xDirection)
|
|
{
|
|
this.parent.x += event.deltaX * step * this.options.wheelScroll * this.reverse;
|
|
}
|
|
if (this.yDirection)
|
|
{
|
|
this.parent.y += event.deltaY * step * this.options.wheelScroll * this.reverse;
|
|
}
|
|
if (this.options.clampWheel)
|
|
{
|
|
this.clamp();
|
|
}
|
|
this.parent.emit('wheel-scroll', this.parent);
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'wheel' });
|
|
if (!this.parent.options.passiveWheel)
|
|
{
|
|
event.preventDefault();
|
|
}
|
|
if (this.parent.options.stopPropagation)
|
|
{
|
|
event.stopPropagation();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
resume()
|
|
{
|
|
this.last = null;
|
|
this.paused = false;
|
|
}
|
|
|
|
clamp()
|
|
{
|
|
const decelerate = this.parent.plugins.get('decelerate', true) || {};
|
|
|
|
if (this.options.clampWheel !== 'y')
|
|
{
|
|
if (this.parent.screenWorldWidth < this.parent.screenWidth)
|
|
{
|
|
switch (this.underflowX)
|
|
{
|
|
case -1:
|
|
this.parent.x = 0;
|
|
break;
|
|
case 1:
|
|
this.parent.x = (this.parent.screenWidth - this.parent.screenWorldWidth);
|
|
break;
|
|
default:
|
|
this.parent.x = (this.parent.screenWidth - this.parent.screenWorldWidth) / 2;
|
|
}
|
|
}
|
|
else
|
|
if (this.parent.left < 0)
|
|
{
|
|
this.parent.x = 0;
|
|
decelerate.x = 0;
|
|
}
|
|
else if (this.parent.right > this.parent.worldWidth)
|
|
{
|
|
this.parent.x = (-this.parent.worldWidth * this.parent.scale.x) + this.parent.screenWidth;
|
|
decelerate.x = 0;
|
|
}
|
|
}
|
|
if (this.options.clampWheel !== 'x')
|
|
{
|
|
if (this.parent.screenWorldHeight < this.parent.screenHeight)
|
|
{
|
|
switch (this.underflowY)
|
|
{
|
|
case -1:
|
|
this.parent.y = 0;
|
|
break;
|
|
case 1:
|
|
this.parent.y = (this.parent.screenHeight - this.parent.screenWorldHeight);
|
|
break;
|
|
default:
|
|
this.parent.y = (this.parent.screenHeight - this.parent.screenWorldHeight) / 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.parent.top < 0)
|
|
{
|
|
this.parent.y = 0;
|
|
decelerate.y = 0;
|
|
}
|
|
if (this.parent.bottom > this.parent.worldHeight)
|
|
{
|
|
this.parent.y = (-this.parent.worldHeight * this.parent.scale.y) + this.parent.screenHeight;
|
|
decelerate.y = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Options for {@link Follow}. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_FOLLOW_OPTIONS = {
|
|
speed: 0,
|
|
acceleration: null,
|
|
radius: null
|
|
};
|
|
|
|
/**
|
|
* Plugin to follow a display-object.
|
|
*
|
|
* @see Viewport.follow
|
|
* @public
|
|
*/
|
|
class Follow extends Plugin
|
|
{
|
|
/** The options used to initialize this plugin. */
|
|
|
|
|
|
/** The target this plugin will make the viewport follow. */
|
|
|
|
|
|
/** The velocity provided the viewport by following, at the current time. */
|
|
|
|
|
|
/**
|
|
* This is called by {@link Viewport.follow}.
|
|
*
|
|
* @param parent
|
|
* @param target - target to follow
|
|
* @param options
|
|
*/
|
|
constructor(parent, target, options = {})
|
|
{
|
|
super(parent);
|
|
|
|
this.target = target;
|
|
this.options = Object.assign({}, DEFAULT_FOLLOW_OPTIONS, options);
|
|
this.velocity = { x: 0, y: 0 };
|
|
}
|
|
|
|
update(elapsed)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const center = this.parent.center;
|
|
let toX = this.target.x;
|
|
let toY = this.target.y;
|
|
|
|
if (this.options.radius)
|
|
{
|
|
const distance = Math.sqrt(Math.pow(this.target.y - center.y, 2) + Math.pow(this.target.x - center.x, 2));
|
|
|
|
if (distance > this.options.radius)
|
|
{
|
|
const angle = Math.atan2(this.target.y - center.y, this.target.x - center.x);
|
|
|
|
toX = this.target.x - (Math.cos(angle) * this.options.radius);
|
|
toY = this.target.y - (Math.sin(angle) * this.options.radius);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
const deltaX = toX - center.x;
|
|
const deltaY = toY - center.y;
|
|
|
|
if (deltaX || deltaY)
|
|
{
|
|
if (this.options.speed)
|
|
{
|
|
if (this.options.acceleration)
|
|
{
|
|
const angle = Math.atan2(toY - center.y, toX - center.x);
|
|
const distance = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
|
|
|
|
if (distance)
|
|
{
|
|
const decelerationDistance = (Math.pow(this.velocity.x, 2) + Math.pow(this.velocity.y, 2)) / (2 * this.options.acceleration);
|
|
|
|
if (distance > decelerationDistance)
|
|
{
|
|
this.velocity = {
|
|
x: Math.min(this.velocity.x + this.options.acceleration * elapsed, this.options.speed),
|
|
y: Math.min(this.velocity.y + this.options.acceleration * elapsed, this.options.speed)
|
|
};
|
|
}
|
|
else
|
|
{
|
|
this.velocity = {
|
|
x: Math.max(this.velocity.x - this.options.acceleration * this.options.speed, 0),
|
|
y: Math.max(this.velocity.y - this.options.acceleration * this.options.speed, 0)
|
|
};
|
|
}
|
|
const changeX = Math.cos(angle) * this.velocity.x;
|
|
const changeY = Math.sin(angle) * this.velocity.y;
|
|
const x = Math.abs(changeX) > Math.abs(deltaX) ? toX : center.x + changeX;
|
|
const y = Math.abs(changeY) > Math.abs(deltaY) ? toY : center.y + changeY;
|
|
|
|
this.parent.moveCenter(x, y);
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'follow' });
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const angle = Math.atan2(toY - center.y, toX - center.x);
|
|
const changeX = Math.cos(angle) * this.options.speed;
|
|
const changeY = Math.sin(angle) * this.options.speed;
|
|
const x = Math.abs(changeX) > Math.abs(deltaX) ? toX : center.x + changeX;
|
|
const y = Math.abs(changeY) > Math.abs(deltaY) ? toY : center.y + changeY;
|
|
|
|
this.parent.moveCenter(x, y);
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'follow' });
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.parent.moveCenter(toX, toY);
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'follow' });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Insets for mouse edges scrolling regions */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const MOUSE_EDGES_OPTIONS = {
|
|
radius: null,
|
|
distance: null,
|
|
top: null,
|
|
bottom: null,
|
|
left: null,
|
|
right: null,
|
|
speed: 8,
|
|
reverse: false,
|
|
noDecelerate: false,
|
|
linear: false,
|
|
allowButtons: false,
|
|
};
|
|
|
|
/**
|
|
* Scroll viewport when mouse hovers near one of the edges.
|
|
*
|
|
* @event mouse-edge-start(Viewport) emitted when mouse-edge starts
|
|
* @event mouse-edge-end(Viewport) emitted when mouse-edge ends
|
|
*/
|
|
class MouseEdges extends Plugin
|
|
{
|
|
/** Options used to initialize this plugin, cannot be modified later. */
|
|
|
|
|
|
/** Factor from reverse option. */
|
|
|
|
|
|
/** Radius squared */
|
|
|
|
|
|
/** Scroll region size on the left side. */
|
|
|
|
|
|
/** Scroll region size on the top size. */
|
|
|
|
|
|
/** Scroll region size on the right side. */
|
|
|
|
|
|
/** Scroll region size on the bottom side. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* This is called by {@link Viewport.mouseEdges}.
|
|
*/
|
|
constructor(parent, options = {})
|
|
{
|
|
super(parent);
|
|
|
|
this.options = Object.assign({}, MOUSE_EDGES_OPTIONS, options);
|
|
this.reverse = this.options.reverse ? 1 : -1;
|
|
this.radiusSquared = typeof this.options.radius === 'number' ? Math.pow(this.options.radius, 2) : null;
|
|
|
|
this.resize();
|
|
}
|
|
|
|
resize()
|
|
{
|
|
const distance = this.options.distance;
|
|
|
|
if (distance !== null)
|
|
{
|
|
this.left = distance;
|
|
this.top = distance;
|
|
this.right = this.parent.screenWidth - distance;
|
|
this.bottom = this.parent.screenHeight - distance;
|
|
}
|
|
else if (!this.options.radius)
|
|
{
|
|
this.left = this.options.left;
|
|
this.top = this.options.top;
|
|
this.right = this.options.right === null ? null : this.parent.screenWidth - this.options.right;
|
|
this.bottom = this.options.bottom === null ? null : this.parent.screenHeight - this.options.bottom;
|
|
}
|
|
}
|
|
|
|
down()
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return false;
|
|
}
|
|
if (!this.options.allowButtons)
|
|
{
|
|
this.horizontal = this.vertical = null;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
move(event)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return false;
|
|
}
|
|
if ((event.data.pointerType !== 'mouse' && event.data.identifier !== 1)
|
|
|| (!this.options.allowButtons && event.data.buttons !== 0))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const x = event.data.global.x;
|
|
const y = event.data.global.y;
|
|
|
|
if (this.radiusSquared)
|
|
{
|
|
const center = this.parent.toScreen(this.parent.center);
|
|
const distance = Math.pow(center.x - x, 2) + Math.pow(center.y - y, 2);
|
|
|
|
if (distance >= this.radiusSquared)
|
|
{
|
|
const angle = Math.atan2(center.y - y, center.x - x);
|
|
|
|
if (this.options.linear)
|
|
{
|
|
this.horizontal = Math.round(Math.cos(angle)) * this.options.speed * this.reverse * (60 / 1000);
|
|
this.vertical = Math.round(Math.sin(angle)) * this.options.speed * this.reverse * (60 / 1000);
|
|
}
|
|
else
|
|
{
|
|
this.horizontal = Math.cos(angle) * this.options.speed * this.reverse * (60 / 1000);
|
|
this.vertical = Math.sin(angle) * this.options.speed * this.reverse * (60 / 1000);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.horizontal)
|
|
{
|
|
this.decelerateHorizontal();
|
|
}
|
|
if (this.vertical)
|
|
{
|
|
this.decelerateVertical();
|
|
}
|
|
|
|
this.horizontal = this.vertical = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.left !== null && x < this.left)
|
|
{
|
|
this.horizontal = Number(this.reverse) * this.options.speed * (60 / 1000);
|
|
}
|
|
else if (this.right !== null && x > this.right)
|
|
{
|
|
this.horizontal = -1 * this.reverse * this.options.speed * (60 / 1000);
|
|
}
|
|
else
|
|
{
|
|
this.decelerateHorizontal();
|
|
this.horizontal = 0;
|
|
}
|
|
if (this.top !== null && y < this.top)
|
|
{
|
|
this.vertical = Number(this.reverse) * this.options.speed * (60 / 1000);
|
|
}
|
|
else if (this.bottom !== null && y > this.bottom)
|
|
{
|
|
this.vertical = -1 * this.reverse * this.options.speed * (60 / 1000);
|
|
}
|
|
else
|
|
{
|
|
this.decelerateVertical();
|
|
this.vertical = 0;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
decelerateHorizontal()
|
|
{
|
|
const decelerate = this.parent.plugins.get('decelerate', true);
|
|
|
|
if (this.horizontal && decelerate && !this.options.noDecelerate)
|
|
{
|
|
decelerate.activate({ x: (this.horizontal * this.options.speed * this.reverse) / (1000 / 60) });
|
|
}
|
|
}
|
|
|
|
decelerateVertical()
|
|
{
|
|
const decelerate = this.parent.plugins.get('decelerate', true);
|
|
|
|
if (this.vertical && decelerate && !this.options.noDecelerate)
|
|
{
|
|
decelerate.activate({ y: (this.vertical * this.options.speed * this.reverse) / (1000 / 60) });
|
|
}
|
|
}
|
|
|
|
up()
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return false;
|
|
}
|
|
if (this.horizontal)
|
|
{
|
|
this.decelerateHorizontal();
|
|
}
|
|
if (this.vertical)
|
|
{
|
|
this.decelerateVertical();
|
|
}
|
|
this.horizontal = this.vertical = null;
|
|
|
|
return false;
|
|
}
|
|
|
|
update()
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.horizontal || this.vertical)
|
|
{
|
|
const center = this.parent.center;
|
|
|
|
if (this.horizontal)
|
|
{
|
|
center.x += this.horizontal * this.options.speed;
|
|
}
|
|
if (this.vertical)
|
|
{
|
|
center.y += this.vertical * this.options.speed;
|
|
}
|
|
|
|
this.parent.moveCenter(center);
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'mouse-edges' });
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Options for {@link Pinch}. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_PINCH_OPTIONS = {
|
|
noDrag: false,
|
|
percent: 1,
|
|
center: null,
|
|
factor: 1,
|
|
axis: 'all',
|
|
};
|
|
|
|
/**
|
|
* Plugin for enabling two-finger pinching (or dragging).
|
|
*
|
|
* @public
|
|
*/
|
|
class Pinch extends Plugin
|
|
{
|
|
/** Options used to initialize this plugin. */
|
|
|
|
|
|
/** Flags whether this plugin is active, i.e. a pointer is down on the viewport. */
|
|
__init() {this.active = false;}
|
|
|
|
/** Flags whether the viewport is being pinched. */
|
|
__init2() {this.pinching = false;}
|
|
|
|
__init3() {this.moved = false;}
|
|
|
|
|
|
/**
|
|
* This is called by {@link Viewport.pinch}.
|
|
*/
|
|
constructor(parent, options = {})
|
|
{
|
|
super(parent);Pinch.prototype.__init.call(this);Pinch.prototype.__init2.call(this);Pinch.prototype.__init3.call(this);;
|
|
this.options = Object.assign({}, DEFAULT_PINCH_OPTIONS, options);
|
|
}
|
|
|
|
down()
|
|
{
|
|
if (this.parent.input.count() >= 2)
|
|
{
|
|
this.active = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
isAxisX()
|
|
{
|
|
return ['all', 'x'].includes(this.options.axis);
|
|
}
|
|
|
|
isAxisY()
|
|
{
|
|
return ['all', 'y'].includes(this.options.axis);
|
|
}
|
|
|
|
move(e)
|
|
{
|
|
if (this.paused || !this.active)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const x = e.data.global.x;
|
|
const y = e.data.global.y;
|
|
|
|
const pointers = this.parent.input.touches;
|
|
|
|
if (pointers.length >= 2)
|
|
{
|
|
const first = pointers[0] ;
|
|
const second = pointers[1] ;
|
|
const last = (first.last && second.last)
|
|
? Math.sqrt(Math.pow(second.last.x - first.last.x, 2) + Math.pow(second.last.y - first.last.y, 2))
|
|
: null;
|
|
|
|
if (first.id === e.data.pointerId)
|
|
{
|
|
first.last = { x, y, data: e.data } ;
|
|
}
|
|
else if (second.id === e.data.pointerId)
|
|
{
|
|
second.last = { x, y, data: e.data } ;
|
|
}
|
|
if (last)
|
|
{
|
|
let oldPoint;
|
|
|
|
const point = {
|
|
x: (first.last ).x
|
|
+ ((second.last ).x - (first.last ).x) / 2,
|
|
y: (first.last ).y
|
|
+ ((second.last ).y - (first.last ).y) / 2,
|
|
};
|
|
|
|
if (!this.options.center)
|
|
{
|
|
oldPoint = this.parent.toLocal(point);
|
|
}
|
|
let dist = Math.sqrt(Math.pow(
|
|
(second.last ).x - (first.last ).x, 2)
|
|
+ Math.pow((second.last ).y - (first.last ).y, 2));
|
|
|
|
dist = dist === 0 ? dist = 0.0000000001 : dist;
|
|
|
|
const change = (1 - last / dist) * this.options.percent
|
|
* (this.isAxisX() ? this.parent.scale.x : this.parent.scale.y);
|
|
|
|
if (this.isAxisX())
|
|
{
|
|
this.parent.scale.x += change;
|
|
}
|
|
if (this.isAxisY())
|
|
{
|
|
this.parent.scale.y += change;
|
|
}
|
|
|
|
this.parent.emit('zoomed', { viewport: this.parent, type: 'pinch', center: point });
|
|
|
|
const clamp = this.parent.plugins.get('clamp-zoom', true);
|
|
|
|
if (clamp)
|
|
{
|
|
clamp.clamp();
|
|
}
|
|
if (this.options.center)
|
|
{
|
|
this.parent.moveCenter(this.options.center);
|
|
}
|
|
else
|
|
{
|
|
const newPoint = this.parent.toGlobal(oldPoint );
|
|
|
|
this.parent.x += (point.x - newPoint.x) * this.options.factor;
|
|
this.parent.y += (point.y - newPoint.y) * this.options.factor;
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'pinch' });
|
|
}
|
|
if (!this.options.noDrag && this.lastCenter)
|
|
{
|
|
this.parent.x += (point.x - this.lastCenter.x) * this.options.factor;
|
|
this.parent.y += (point.y - this.lastCenter.y) * this.options.factor;
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'pinch' });
|
|
}
|
|
|
|
this.lastCenter = point;
|
|
this.moved = true;
|
|
}
|
|
else if (!this.pinching)
|
|
{
|
|
this.parent.emit('pinch-start', this.parent);
|
|
this.pinching = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
up()
|
|
{
|
|
if (this.pinching)
|
|
{
|
|
if (this.parent.input.touches.length <= 1)
|
|
{
|
|
this.active = false;
|
|
this.lastCenter = null;
|
|
this.pinching = false;
|
|
this.moved = false;
|
|
this.parent.emit('pinch-end', this.parent);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const DEFAULT_SNAP_OPTIONS = {
|
|
topLeft: false,
|
|
friction: 0.8,
|
|
time: 1000,
|
|
ease: 'easeInOutSine',
|
|
interrupt: true,
|
|
removeOnComplete: false,
|
|
removeOnInterrupt: false,
|
|
forceStart: false
|
|
};
|
|
|
|
/**
|
|
* @event snap-start(Viewport) emitted each time a snap animation starts
|
|
* @event snap-restart(Viewport) emitted each time a snap resets because of a change in viewport size
|
|
* @event snap-end(Viewport) emitted each time snap reaches its target
|
|
* @event snap-remove(Viewport) emitted if snap plugin is removed
|
|
*/
|
|
class Snap extends Plugin
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* This is called by {@link Viewport.snap}.
|
|
*/
|
|
constructor(parent, x, y, options = {})
|
|
{
|
|
super(parent);
|
|
this.options = Object.assign({}, DEFAULT_SNAP_OPTIONS, options);
|
|
this.ease = ease(options.ease, 'easeInOutSine');
|
|
this.x = x;
|
|
this.y = y;
|
|
|
|
if (this.options.forceStart)
|
|
{
|
|
this.snapStart();
|
|
}
|
|
}
|
|
|
|
snapStart()
|
|
{
|
|
this.percent = 0;
|
|
this.snapping = { time: 0 };
|
|
const current = this.options.topLeft ? this.parent.corner : this.parent.center;
|
|
|
|
this.deltaX = this.x - current.x;
|
|
this.deltaY = this.y - current.y;
|
|
this.startX = current.x;
|
|
this.startY = current.y;
|
|
this.parent.emit('snap-start', this.parent);
|
|
}
|
|
|
|
wheel()
|
|
{
|
|
if (this.options.removeOnInterrupt)
|
|
{
|
|
this.parent.plugins.remove('snap');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
down()
|
|
{
|
|
if (this.options.removeOnInterrupt)
|
|
{
|
|
this.parent.plugins.remove('snap');
|
|
}
|
|
else if (this.options.interrupt)
|
|
{
|
|
this.snapping = null;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
up()
|
|
{
|
|
if (this.parent.input.count() === 0)
|
|
{
|
|
const decelerate = this.parent.plugins.get('decelerate', true);
|
|
|
|
if (decelerate && (decelerate.x || decelerate.y))
|
|
{
|
|
decelerate.percentChangeX = decelerate.percentChangeY = this.options.friction;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
update(elapsed)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
if (this.options.interrupt && this.parent.input.count() !== 0)
|
|
{
|
|
return;
|
|
}
|
|
if (!this.snapping)
|
|
{
|
|
const current = this.options.topLeft ? this.parent.corner : this.parent.center;
|
|
|
|
if (current.x !== this.x || current.y !== this.y)
|
|
{
|
|
this.snapStart();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const snapping = this.snapping;
|
|
|
|
snapping.time += elapsed;
|
|
let finished;
|
|
let x;
|
|
let y;
|
|
|
|
const startX = this.startX ;
|
|
const startY = this.startY ;
|
|
const deltaX = this.deltaX ;
|
|
const deltaY = this.deltaY ;
|
|
|
|
if (snapping.time > this.options.time)
|
|
{
|
|
finished = true;
|
|
x = startX + deltaX;
|
|
y = startY + deltaY;
|
|
}
|
|
else
|
|
{
|
|
const percent = this.ease(snapping.time, 0, 1, this.options.time);
|
|
|
|
x = startX + (deltaX * percent);
|
|
y = startY + (deltaY * percent);
|
|
}
|
|
if (this.options.topLeft)
|
|
{
|
|
this.parent.moveCorner(x, y);
|
|
}
|
|
else
|
|
{
|
|
this.parent.moveCenter(x, y);
|
|
}
|
|
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'snap' });
|
|
|
|
if (finished)
|
|
{
|
|
if (this.options.removeOnComplete)
|
|
{
|
|
this.parent.plugins.remove('snap');
|
|
}
|
|
this.parent.emit('snap-end', this.parent);
|
|
this.snapping = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Options for {@link SnapZoom}. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_SNAP_ZOOM_OPTIONS = {
|
|
width: 0,
|
|
height: 0,
|
|
time: 1000,
|
|
ease: 'easeInOutSine',
|
|
center: null,
|
|
interrupt: true,
|
|
removeOnComplete: false,
|
|
removeOnInterrupt: false,
|
|
forceStart: false,
|
|
noMove: false
|
|
};
|
|
|
|
/**
|
|
* @event snap-zoom-start(Viewport) emitted each time a fit animation starts
|
|
* @event snap-zoom-end(Viewport) emitted each time fit reaches its target
|
|
* @event snap-zoom-end(Viewport) emitted each time fit reaches its target
|
|
*/
|
|
class SnapZoom extends Plugin
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* This is called by {@link Viewport.snapZoom}.
|
|
*/
|
|
constructor(parent, options = {})
|
|
{
|
|
super(parent);
|
|
|
|
this.options = Object.assign({}, DEFAULT_SNAP_ZOOM_OPTIONS, options);
|
|
this.ease = ease(this.options.ease);
|
|
|
|
// Assign defaults for typescript.
|
|
this.xIndependent = false;
|
|
this.yIndependent = false;
|
|
this.xScale = 0;
|
|
this.yScale = 0;
|
|
|
|
if (this.options.width > 0)
|
|
{
|
|
this.xScale = parent.screenWidth / this.options.width;
|
|
this.xIndependent = true;
|
|
}
|
|
if (this.options.height > 0)
|
|
{
|
|
this.yScale = parent.screenHeight / this.options.height;
|
|
this.yIndependent = true;
|
|
}
|
|
|
|
this.xScale = this.xIndependent ? (this.xScale ) : (this.yScale );
|
|
this.yScale = this.yIndependent ? (this.yScale ) : this.xScale;
|
|
|
|
if (this.options.time === 0)
|
|
{
|
|
// TODO: Fix this
|
|
// @ts-expect-error todo
|
|
parent.container.scale.x = this.xScale;
|
|
|
|
// TODO: Fix this
|
|
// @ts-expect-error todo
|
|
parent.container.scale.y = this.yScale;
|
|
|
|
if (this.options.removeOnComplete)
|
|
{
|
|
this.parent.plugins.remove('snap-zoom');
|
|
}
|
|
}
|
|
else if (options.forceStart)
|
|
{
|
|
this.createSnapping();
|
|
}
|
|
}
|
|
|
|
createSnapping()
|
|
{
|
|
const startWorldScreenWidth = this.parent.worldScreenWidth;
|
|
const startWorldScreenHeight = this.parent.worldScreenHeight;
|
|
const endWorldScreenWidth = this.parent.screenWidth / this.xScale;
|
|
const endWorldScreenHeight = this.parent.screenHeight / this.yScale;
|
|
|
|
this.snapping = {
|
|
time: 0,
|
|
startX: startWorldScreenWidth,
|
|
startY: startWorldScreenHeight,
|
|
deltaX: endWorldScreenWidth - startWorldScreenWidth,
|
|
deltaY: endWorldScreenHeight - startWorldScreenHeight
|
|
};
|
|
|
|
this.parent.emit('snap-zoom-start', this.parent);
|
|
}
|
|
|
|
resize()
|
|
{
|
|
this.snapping = null;
|
|
|
|
if (this.options.width > 0)
|
|
{
|
|
this.xScale = this.parent.screenWidth / this.options.width;
|
|
}
|
|
if (this.options.height > 0)
|
|
{
|
|
this.yScale = this.parent.screenHeight / this.options.height;
|
|
}
|
|
this.xScale = this.xIndependent ? this.xScale : this.yScale;
|
|
this.yScale = this.yIndependent ? this.yScale : this.xScale;
|
|
}
|
|
|
|
wheel()
|
|
{
|
|
if (this.options.removeOnInterrupt)
|
|
{
|
|
this.parent.plugins.remove('snap-zoom');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
down()
|
|
{
|
|
if (this.options.removeOnInterrupt)
|
|
{
|
|
this.parent.plugins.remove('snap-zoom');
|
|
}
|
|
else if (this.options.interrupt)
|
|
{
|
|
this.snapping = null;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
update(elapsed)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
if (this.options.interrupt && this.parent.input.count() !== 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
let oldCenter;
|
|
|
|
if (!this.options.center && !this.options.noMove)
|
|
{
|
|
oldCenter = this.parent.center;
|
|
}
|
|
if (!this.snapping)
|
|
{
|
|
if (this.parent.scale.x !== this.xScale || this.parent.scale.y !== this.yScale)
|
|
{
|
|
this.createSnapping();
|
|
}
|
|
}
|
|
else if (this.snapping)
|
|
{
|
|
const snapping = this.snapping;
|
|
|
|
snapping.time += elapsed;
|
|
|
|
if (snapping.time >= this.options.time)
|
|
{
|
|
this.parent.scale.set(this.xScale, this.yScale);
|
|
if (this.options.removeOnComplete)
|
|
{
|
|
this.parent.plugins.remove('snap-zoom');
|
|
}
|
|
this.parent.emit('snap-zoom-end', this.parent);
|
|
this.snapping = null;
|
|
}
|
|
else
|
|
{
|
|
const snapping = this.snapping;
|
|
const worldScreenWidth = this.ease(snapping.time, snapping.startX, snapping.deltaX, this.options.time);
|
|
const worldScreenHeight = this.ease(snapping.time, snapping.startY, snapping.deltaY, this.options.time);
|
|
|
|
this.parent.scale.x = this.parent.screenWidth / worldScreenWidth;
|
|
this.parent.scale.y = this.parent.screenHeight / worldScreenHeight;
|
|
}
|
|
const clamp = this.parent.plugins.get('clamp-zoom', true);
|
|
|
|
if (clamp)
|
|
{
|
|
clamp.clamp();
|
|
}
|
|
if (!this.options.noMove)
|
|
{
|
|
if (!this.options.center)
|
|
{
|
|
this.parent.moveCenter(oldCenter );
|
|
}
|
|
else
|
|
{
|
|
this.parent.moveCenter(this.options.center);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
resume()
|
|
{
|
|
this.snapping = null;
|
|
super.resume();
|
|
}
|
|
}
|
|
|
|
/** Options for {@link Wheel}. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_WHEEL_OPTIONS = {
|
|
percent: 0.1,
|
|
smooth: false,
|
|
interrupt: true,
|
|
reverse: false,
|
|
center: null,
|
|
lineHeight: 20,
|
|
axis: 'all',
|
|
keyToPress: null,
|
|
trackpadPinch: false,
|
|
wheelZoom: true,
|
|
};
|
|
|
|
/**
|
|
* Plugin for handling wheel scrolling for viewport zoom.
|
|
*
|
|
* @event wheel({wheel: {dx, dy, dz}, event, viewport})
|
|
*/
|
|
class Wheel extends Plugin
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Flags whether the keys required to zoom are pressed currently. */
|
|
|
|
|
|
/**
|
|
* This is called by {@link Viewport.wheel}.
|
|
*/
|
|
constructor(parent, options = {})
|
|
{
|
|
super(parent);
|
|
this.options = Object.assign({}, DEFAULT_WHEEL_OPTIONS, options);
|
|
this.keyIsPressed = false;
|
|
|
|
if (this.options.keyToPress)
|
|
{
|
|
this.handleKeyPresses(this.options.keyToPress);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles keypress events and set the keyIsPressed boolean accordingly
|
|
*
|
|
* @param {array} codes - key codes that can be used to trigger zoom event
|
|
*/
|
|
handleKeyPresses(codes)
|
|
{
|
|
window.addEventListener('keydown', (e) =>
|
|
{
|
|
if (codes.includes(e.code))
|
|
{
|
|
this.keyIsPressed = true;
|
|
}
|
|
});
|
|
|
|
window.addEventListener('keyup', (e) =>
|
|
{
|
|
if (codes.includes(e.code))
|
|
{
|
|
this.keyIsPressed = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
checkKeyPress()
|
|
{
|
|
return !this.options.keyToPress || this.keyIsPressed;
|
|
}
|
|
|
|
down()
|
|
{
|
|
if (this.options.interrupt)
|
|
{
|
|
this.smoothing = null;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
isAxisX()
|
|
{
|
|
return ['all', 'x'].includes(this.options.axis);
|
|
}
|
|
|
|
isAxisY()
|
|
{
|
|
return ['all', 'y'].includes(this.options.axis);
|
|
}
|
|
|
|
update()
|
|
{
|
|
if (this.smoothing)
|
|
{
|
|
const point = this.smoothingCenter;
|
|
const change = this.smoothing;
|
|
let oldPoint;
|
|
|
|
if (!this.options.center)
|
|
{
|
|
oldPoint = this.parent.toLocal(point );
|
|
}
|
|
if (this.isAxisX())
|
|
{
|
|
this.parent.scale.x += change.x;
|
|
}
|
|
if (this.isAxisY())
|
|
{
|
|
this.parent.scale.y += change.y;
|
|
}
|
|
|
|
this.parent.emit('zoomed', { viewport: this.parent, type: 'wheel' });
|
|
const clamp = this.parent.plugins.get('clamp-zoom', true);
|
|
|
|
if (clamp)
|
|
{
|
|
clamp.clamp();
|
|
}
|
|
if (this.options.center)
|
|
{
|
|
this.parent.moveCenter(this.options.center);
|
|
}
|
|
else
|
|
{
|
|
const newPoint = this.parent.toGlobal(oldPoint );
|
|
|
|
this.parent.x += (point ).x - newPoint.x;
|
|
this.parent.y += (point ).y - newPoint.y;
|
|
}
|
|
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'wheel' });
|
|
(this.smoothingCount )++;
|
|
|
|
if ((this.smoothingCount ) >= this.options.smooth)
|
|
{
|
|
this.smoothing = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
pinch(e)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const point = this.parent.input.getPointerPosition(e);
|
|
const step = -e.deltaY * (e.deltaMode ? this.options.lineHeight : 1) / 200;
|
|
const change = Math.pow(2, (1 + this.options.percent) * step);
|
|
|
|
let oldPoint;
|
|
|
|
if (!this.options.center)
|
|
{
|
|
oldPoint = this.parent.toLocal(point);
|
|
}
|
|
if (this.isAxisX())
|
|
{
|
|
this.parent.scale.x *= change;
|
|
}
|
|
if (this.isAxisY())
|
|
{
|
|
this.parent.scale.y *= change;
|
|
}
|
|
this.parent.emit('zoomed', { viewport: this.parent, type: 'wheel' });
|
|
const clamp = this.parent.plugins.get('clamp-zoom', true);
|
|
|
|
if (clamp)
|
|
{
|
|
clamp.clamp();
|
|
}
|
|
if (this.options.center)
|
|
{
|
|
this.parent.moveCenter(this.options.center);
|
|
}
|
|
else
|
|
{
|
|
const newPoint = this.parent.toGlobal(oldPoint );
|
|
|
|
this.parent.x += point.x - newPoint.x;
|
|
this.parent.y += point.y - newPoint.y;
|
|
}
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'wheel' });
|
|
this.parent.emit('wheel',
|
|
{ wheel: { dx: e.deltaX, dy: e.deltaY, dz: e.deltaZ }, event: e, viewport: this.parent });
|
|
}
|
|
|
|
wheel(e)
|
|
{
|
|
if (this.paused)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!this.checkKeyPress())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (e.ctrlKey && this.options.trackpadPinch)
|
|
{
|
|
this.pinch(e);
|
|
}
|
|
else if (this.options.wheelZoom)
|
|
{
|
|
const point = this.parent.input.getPointerPosition(e);
|
|
const sign = this.options.reverse ? -1 : 1;
|
|
const step = sign * -e.deltaY * (e.deltaMode ? this.options.lineHeight : 1) / 500;
|
|
const change = Math.pow(2, (1 + this.options.percent) * step);
|
|
|
|
if (this.options.smooth)
|
|
{
|
|
const original = {
|
|
x: this.smoothing ? this.smoothing.x * (this.options.smooth - (this.smoothingCount )) : 0,
|
|
y: this.smoothing ? this.smoothing.y * (this.options.smooth - (this.smoothingCount )) : 0
|
|
};
|
|
|
|
this.smoothing = {
|
|
x: ((this.parent.scale.x + original.x) * change - this.parent.scale.x) / this.options.smooth,
|
|
y: ((this.parent.scale.y + original.y) * change - this.parent.scale.y) / this.options.smooth,
|
|
};
|
|
this.smoothingCount = 0;
|
|
this.smoothingCenter = point;
|
|
}
|
|
else
|
|
{
|
|
let oldPoint;
|
|
|
|
if (!this.options.center)
|
|
{
|
|
oldPoint = this.parent.toLocal(point);
|
|
}
|
|
if (this.isAxisX())
|
|
{
|
|
this.parent.scale.x *= change;
|
|
}
|
|
if (this.isAxisY())
|
|
{
|
|
this.parent.scale.y *= change;
|
|
}
|
|
this.parent.emit('zoomed', { viewport: this.parent, type: 'wheel' });
|
|
const clamp = this.parent.plugins.get('clamp-zoom', true);
|
|
|
|
if (clamp)
|
|
{
|
|
clamp.clamp();
|
|
}
|
|
if (this.options.center)
|
|
{
|
|
this.parent.moveCenter(this.options.center);
|
|
}
|
|
else
|
|
{
|
|
const newPoint = this.parent.toGlobal(oldPoint );
|
|
|
|
this.parent.x += point.x - newPoint.x;
|
|
this.parent.y += point.y - newPoint.y;
|
|
}
|
|
}
|
|
|
|
this.parent.emit('moved', { viewport: this.parent, type: 'wheel' });
|
|
this.parent.emit('wheel',
|
|
{ wheel: { dx: e.deltaX, dy: e.deltaY, dz: e.deltaZ }, event: e, viewport: this.parent });
|
|
}
|
|
|
|
return !this.parent.options.passiveWheel;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles all input for Viewport
|
|
*
|
|
* @internal
|
|
* @ignore
|
|
* @private
|
|
*/
|
|
class InputManager
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** List of active touches on viewport */
|
|
|
|
|
|
constructor(viewport)
|
|
{
|
|
this.viewport = viewport;
|
|
this.touches = [];
|
|
|
|
this.addListeners();
|
|
}
|
|
|
|
/** Add input listeners */
|
|
addListeners()
|
|
{
|
|
this.viewport.interactive = true;
|
|
if (!this.viewport.forceHitArea)
|
|
{
|
|
this.viewport.hitArea = new math.Rectangle(0, 0, this.viewport.worldWidth, this.viewport.worldHeight);
|
|
}
|
|
this.viewport.on('pointerdown', this.down, this);
|
|
this.viewport.on('pointermove', this.move, this);
|
|
this.viewport.on('pointerup', this.up, this);
|
|
this.viewport.on('pointerupoutside', this.up, this);
|
|
this.viewport.on('pointercancel', this.up, this);
|
|
this.viewport.on('pointerout', this.up, this);
|
|
this.wheelFunction = (e) => this.handleWheel(e);
|
|
this.viewport.options.divWheel.addEventListener(
|
|
'wheel',
|
|
this.wheelFunction ,
|
|
{ passive: this.viewport.options.passiveWheel });
|
|
this.isMouseDown = false;
|
|
}
|
|
|
|
/**
|
|
* Removes all event listeners from viewport
|
|
* (useful for cleanup of wheel when removing viewport)
|
|
*/
|
|
destroy()
|
|
{
|
|
this.viewport.options.divWheel.removeEventListener('wheel', this.wheelFunction );
|
|
}
|
|
|
|
/**
|
|
* handle down events for viewport
|
|
*
|
|
* @param {PIXI.InteractionEvent} event
|
|
*/
|
|
down(event)
|
|
{
|
|
if (this.viewport.pause || !this.viewport.worldVisible)
|
|
{
|
|
return;
|
|
}
|
|
if (event.data.pointerType === 'mouse')
|
|
{
|
|
this.isMouseDown = true;
|
|
}
|
|
else if (!this.get(event.data.pointerId))
|
|
{
|
|
this.touches.push({ id: event.data.pointerId, last: null });
|
|
}
|
|
if (this.count() === 1)
|
|
{
|
|
this.last = event.data.global.clone();
|
|
|
|
// clicked event does not fire if viewport is decelerating or bouncing
|
|
const decelerate = this.viewport.plugins.get('decelerate', true);
|
|
const bounce = this.viewport.plugins.get('bounce', true);
|
|
|
|
if ((!decelerate || !decelerate.isActive()) && (!bounce || !bounce.isActive()))
|
|
{
|
|
this.clickedAvailable = true;
|
|
}
|
|
else
|
|
{
|
|
this.clickedAvailable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.clickedAvailable = false;
|
|
}
|
|
|
|
const stop = this.viewport.plugins.down(event);
|
|
|
|
if (stop && this.viewport.options.stopPropagation)
|
|
{
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
|
|
/** Clears all pointer events */
|
|
clear()
|
|
{
|
|
this.isMouseDown = false;
|
|
this.touches = [];
|
|
this.last = null;
|
|
}
|
|
|
|
/**
|
|
* @param {number} change
|
|
* @returns whether change exceeds threshold
|
|
*/
|
|
checkThreshold(change)
|
|
{
|
|
if (Math.abs(change) >= this.viewport.threshold)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Handle move events for viewport */
|
|
move(event)
|
|
{
|
|
if (this.viewport.pause || !this.viewport.worldVisible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const stop = this.viewport.plugins.move(event);
|
|
|
|
if (this.clickedAvailable && this.last)
|
|
{
|
|
const distX = event.data.global.x - this.last.x;
|
|
const distY = event.data.global.y - this.last.y;
|
|
|
|
if (this.checkThreshold(distX) || this.checkThreshold(distY))
|
|
{
|
|
this.clickedAvailable = false;
|
|
}
|
|
}
|
|
|
|
if (stop && this.viewport.options.stopPropagation)
|
|
{
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
|
|
/** Handle up events for viewport */
|
|
up(event)
|
|
{
|
|
if (this.viewport.pause || !this.viewport.worldVisible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (event.data.pointerType === 'mouse')
|
|
{
|
|
this.isMouseDown = false;
|
|
}
|
|
|
|
if (event.data.pointerType !== 'mouse')
|
|
{
|
|
this.remove(event.data.pointerId);
|
|
}
|
|
|
|
const stop = this.viewport.plugins.up(event);
|
|
|
|
if (this.clickedAvailable && this.count() === 0 && this.last)
|
|
{
|
|
this.viewport.emit('clicked', {
|
|
event,
|
|
screen: this.last,
|
|
world: this.viewport.toWorld(this.last),
|
|
viewport: this
|
|
});
|
|
this.clickedAvailable = false;
|
|
}
|
|
|
|
if (stop && this.viewport.options.stopPropagation)
|
|
{
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
|
|
/** Gets pointer position if this.interaction is set */
|
|
getPointerPosition(event)
|
|
{
|
|
const point = new math.Point();
|
|
|
|
if (this.viewport.options.interaction)
|
|
{
|
|
this.viewport.options.interaction.mapPositionToPoint(point, event.clientX, event.clientY);
|
|
}
|
|
else if (this.viewport.options.useDivWheelForInputManager && this.viewport.options.divWheel)
|
|
{
|
|
const rect = this.viewport.options.divWheel.getBoundingClientRect();
|
|
|
|
point.x = event.clientX - rect.left;
|
|
point.y = event.clientY - rect.top;
|
|
}
|
|
else
|
|
{
|
|
point.x = event.clientX;
|
|
point.y = event.clientY;
|
|
}
|
|
|
|
return point;
|
|
}
|
|
|
|
/** Handle wheel events */
|
|
handleWheel(event)
|
|
{
|
|
if (this.viewport.pause || !this.viewport.worldVisible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// do not handle events coming from other elements
|
|
if (this.viewport.options.interaction
|
|
&& (this.viewport.options.interaction ).interactionDOMElement !== event.target)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// only handle wheel events where the mouse is over the viewport
|
|
const point = this.viewport.toLocal(this.getPointerPosition(event));
|
|
|
|
if (this.viewport.left <= point.x
|
|
&& point.x <= this.viewport.right
|
|
&& this.viewport.top <= point.y
|
|
&& point.y <= this.viewport.bottom)
|
|
{
|
|
const stop = this.viewport.plugins.wheel(event);
|
|
|
|
if (stop && !this.viewport.options.passiveWheel)
|
|
{
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
|
|
pause()
|
|
{
|
|
this.touches = [];
|
|
this.isMouseDown = false;
|
|
}
|
|
|
|
/** Get touch by id */
|
|
get(id)
|
|
{
|
|
for (const touch of this.touches)
|
|
{
|
|
if (touch.id === id)
|
|
{
|
|
return touch;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/** Remove touch by number */
|
|
remove(id)
|
|
{
|
|
for (let i = 0; i < this.touches.length; i++)
|
|
{
|
|
if (this.touches[i].id === id)
|
|
{
|
|
this.touches.splice(i, 1);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @returns {number} count of mouse/touch pointers that are down on the viewport
|
|
*/
|
|
count()
|
|
{
|
|
return (this.isMouseDown ? 1 : 0) + this.touches.length;
|
|
}
|
|
}
|
|
|
|
function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const PLUGIN_ORDER = [
|
|
'drag',
|
|
'pinch',
|
|
'wheel',
|
|
'follow',
|
|
'mouse-edges',
|
|
'decelerate',
|
|
'animate',
|
|
'bounce',
|
|
'snap-zoom',
|
|
'clamp-zoom',
|
|
'snap',
|
|
'clamp',
|
|
];
|
|
|
|
/**
|
|
* Use this to access current plugins or add user-defined plugins
|
|
*
|
|
* @public
|
|
*/
|
|
class PluginManager
|
|
{
|
|
/** Maps mounted plugins by their type */
|
|
|
|
|
|
/**
|
|
* List of plugins mounted
|
|
*
|
|
* This list is kept sorted by the internal priority of plugins (hard-coded).
|
|
*/
|
|
|
|
|
|
/** The viewport using the plugins managed by `this`. */
|
|
|
|
|
|
/** This is called by {@link Viewport} to initialize the {@link Viewport.plugins plugins}. */
|
|
constructor(viewport)
|
|
{
|
|
this.viewport = viewport;
|
|
this.list = [];
|
|
this.plugins = {};
|
|
}
|
|
|
|
/**
|
|
* Inserts a named plugin or a user plugin into the viewport
|
|
* default plugin order: 'drag', 'pinch', 'wheel', 'follow', 'mouse-edges', 'decelerate', 'bounce',
|
|
* 'snap-zoom', 'clamp-zoom', 'snap', 'clamp'
|
|
*
|
|
* @param {string} name of plugin
|
|
* @param {Plugin} plugin - instantiated Plugin class
|
|
* @param {number} index to insert userPlugin (otherwise inserts it at the end)
|
|
*/
|
|
add(name, plugin, index = PLUGIN_ORDER.length)
|
|
{
|
|
this.plugins[name] = plugin;
|
|
|
|
const current = PLUGIN_ORDER.indexOf(name);
|
|
|
|
if (current !== -1)
|
|
{
|
|
PLUGIN_ORDER.splice(current, 1);
|
|
}
|
|
|
|
PLUGIN_ORDER.splice(index, 0, name);
|
|
this.sort();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Get plugin
|
|
*
|
|
* @param {string} name of plugin
|
|
* @param {boolean} [ignorePaused] return null if plugin is paused
|
|
*/
|
|
get(name, ignorePaused)
|
|
{
|
|
if (ignorePaused)
|
|
{
|
|
if (_optionalChain([this, 'access', _ => _.plugins, 'access', _2 => _2[name], 'optionalAccess', _3 => _3.paused]))
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return this.plugins[name] ;
|
|
}
|
|
|
|
/**
|
|
* Update all active plugins
|
|
*
|
|
* @internal
|
|
* @ignore
|
|
* @param {number} elapsed type in milliseconds since last update
|
|
*/
|
|
update(elapsed)
|
|
{
|
|
for (const plugin of this.list)
|
|
{
|
|
plugin.update(elapsed);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resize all active plugins
|
|
*
|
|
* @internal
|
|
* @ignore
|
|
*/
|
|
resize()
|
|
{
|
|
for (const plugin of this.list)
|
|
{
|
|
plugin.resize();
|
|
}
|
|
}
|
|
|
|
/** Clamps and resets bounce and decelerate (as needed) after manually moving viewport */
|
|
reset()
|
|
{
|
|
for (const plugin of this.list)
|
|
{
|
|
plugin.reset();
|
|
}
|
|
}
|
|
|
|
/** removes all installed plugins */
|
|
removeAll()
|
|
{
|
|
this.plugins = {};
|
|
this.sort();
|
|
}
|
|
|
|
/**
|
|
* Removes installed plugin
|
|
*
|
|
* @param {string} name of plugin (e.g., 'drag', 'pinch')
|
|
*/
|
|
remove(name)
|
|
{
|
|
if (this.plugins[name])
|
|
{
|
|
delete this.plugins[name];
|
|
this.viewport.emit(`${name}-remove`);
|
|
this.sort();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pause plugin
|
|
*
|
|
* @param {string} name of plugin (e.g., 'drag', 'pinch')
|
|
*/
|
|
pause(name)
|
|
{
|
|
_optionalChain([this, 'access', _4 => _4.plugins, 'access', _5 => _5[name], 'optionalAccess', _6 => _6.pause, 'call', _7 => _7()]);
|
|
}
|
|
|
|
/**
|
|
* Resume plugin
|
|
*
|
|
* @param {string} name of plugin (e.g., 'drag', 'pinch')
|
|
*/
|
|
resume(name)
|
|
{
|
|
_optionalChain([this, 'access', _8 => _8.plugins, 'access', _9 => _9[name], 'optionalAccess', _10 => _10.resume, 'call', _11 => _11()]);
|
|
}
|
|
|
|
/**
|
|
* Sort plugins according to PLUGIN_ORDER
|
|
*
|
|
* @internal
|
|
* @ignore
|
|
*/
|
|
sort()
|
|
{
|
|
this.list = [];
|
|
|
|
for (const plugin of PLUGIN_ORDER)
|
|
{
|
|
if (this.plugins[plugin])
|
|
{
|
|
this.list.push(this.plugins[plugin] );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle down for all plugins
|
|
*
|
|
* @internal
|
|
* @ignore
|
|
*/
|
|
down(event)
|
|
{
|
|
let stop = false;
|
|
|
|
for (const plugin of this.list)
|
|
{
|
|
if (plugin.down(event))
|
|
{
|
|
stop = true;
|
|
}
|
|
}
|
|
|
|
return stop;
|
|
}
|
|
|
|
/**
|
|
* Handle move for all plugins
|
|
*
|
|
* @internal
|
|
* @ignore
|
|
*/
|
|
move(event)
|
|
{
|
|
let stop = false;
|
|
|
|
for (const plugin of this.viewport.plugins.list)
|
|
{
|
|
if (plugin.move(event))
|
|
{
|
|
stop = true;
|
|
}
|
|
}
|
|
|
|
return stop;
|
|
}
|
|
|
|
/**
|
|
* Handle up for all plugins
|
|
*
|
|
* @internal
|
|
* @ignore
|
|
*/
|
|
up(event)
|
|
{
|
|
let stop = false;
|
|
|
|
for (const plugin of this.list)
|
|
{
|
|
if (plugin.up(event))
|
|
{
|
|
stop = true;
|
|
}
|
|
}
|
|
|
|
return stop;
|
|
}
|
|
|
|
/**
|
|
* Handle wheel event for all plugins
|
|
*
|
|
* @internal
|
|
* @ignore
|
|
*/
|
|
wheel(e)
|
|
{
|
|
let result = false;
|
|
|
|
for (const plugin of this.list)
|
|
{
|
|
if (plugin.wheel(e))
|
|
{
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/** Options for {@link Viewport}. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_VIEWPORT_OPTIONS = {
|
|
screenWidth: window.innerWidth,
|
|
screenHeight: window.innerHeight,
|
|
worldWidth: null,
|
|
worldHeight: null,
|
|
threshold: 5,
|
|
passiveWheel: true,
|
|
stopPropagation: false,
|
|
forceHitArea: null,
|
|
noTicker: false,
|
|
interaction: null,
|
|
disableOnContextMenu: false,
|
|
ticker: ticker.Ticker.shared,
|
|
};
|
|
|
|
/**
|
|
* Main class to use when creating a Viewport
|
|
*
|
|
* @public
|
|
* @fires clicked
|
|
* @fires drag-start
|
|
* @fires drag-end
|
|
* @fires drag-remove
|
|
* @fires pinch-start
|
|
* @fires pinch-end
|
|
* @fires pinch-remove
|
|
* @fires snap-start
|
|
* @fires snap-end
|
|
* @fires snap-remove
|
|
* @fires snap-zoom-start
|
|
* @fires snap-zoom-end
|
|
* @fires snap-zoom-remove
|
|
* @fires bounce-x-start
|
|
* @fires bounce-x-end
|
|
* @fires bounce-y-start
|
|
* @fires bounce-y-end
|
|
* @fires bounce-remove
|
|
* @fires wheel
|
|
* @fires wheel-remove
|
|
* @fires wheel-scroll
|
|
* @fires wheel-scroll-remove
|
|
* @fires mouse-edge-start
|
|
* @fires mouse-edge-end
|
|
* @fires mouse-edge-remove
|
|
* @fires moved
|
|
* @fires moved-end
|
|
* @fires zoomed
|
|
* @fires zoomed-end
|
|
* @fires frame-end
|
|
*/
|
|
class Viewport extends display.Container
|
|
{
|
|
/** Flags whether the viewport is being panned */
|
|
|
|
|
|
|
|
|
|
|
|
/** Number of pixels to move to trigger an input event (e.g., drag, pinch) or disable a clicked event */
|
|
|
|
|
|
|
|
|
|
/** Use this to add user plugins or access existing plugins (e.g., to pause, resume, or remove them) */
|
|
|
|
|
|
/** Flags whether the viewport zoom is being changed. */
|
|
|
|
|
|
|
|
|
|
/** The options passed when creating this viewport, merged with the default values */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__init() {this._disableOnContextMenu = (e) => e.preventDefault();}
|
|
|
|
/**
|
|
* @param {IViewportOptions} ViewportOptions
|
|
* @param {number} [options.screenWidth=window.innerWidth]
|
|
* @param {number} [options.screenHeight=window.innerHeight]
|
|
* @param {number} [options.worldWidth=this.width]
|
|
* @param {number} [options.worldHeight=this.height]
|
|
* @param {number} [options.threshold=5] number of pixels to move to trigger an input event (e.g., drag, pinch)
|
|
* or disable a clicked event
|
|
* @param {boolean} [options.passiveWheel=true] whether the 'wheel' event is set to passive (note: if false,
|
|
* e.preventDefault() will be called when wheel is used over the viewport)
|
|
* @param {boolean} [options.stopPropagation=false] whether to stopPropagation of events that impact the viewport
|
|
* (except wheel events, see options.passiveWheel)
|
|
* @param {HitArea} [options.forceHitArea] change the default hitArea from world size to a new value
|
|
* @param {boolean} [options.noTicker] set this if you want to manually call update() function on each frame
|
|
* @param {PIXI.Ticker} [options.ticker=PIXI.Ticker.shared] use this PIXI.ticker for updates
|
|
* @param {PIXI.InteractionManager} [options.interaction=null] InteractionManager, available from instantiated
|
|
* WebGLRenderer/CanvasRenderer.plugins.interaction - used to calculate pointer position relative to canvas
|
|
* location on screen
|
|
* @param {HTMLElement} [options.divWheel=document.body] div to attach the wheel event
|
|
* @param {boolean} [options.disableOnContextMenu] remove oncontextmenu=() => {} from the divWheel element
|
|
*/
|
|
constructor(options = {})
|
|
{
|
|
super();Viewport.prototype.__init.call(this);;
|
|
this.options = Object.assign(
|
|
{},
|
|
{ divWheel: document.body },
|
|
DEFAULT_VIEWPORT_OPTIONS,
|
|
options
|
|
);
|
|
|
|
this.screenWidth = this.options.screenWidth;
|
|
this.screenHeight = this.options.screenHeight;
|
|
|
|
this._worldWidth = this.options.worldWidth;
|
|
this._worldHeight = this.options.worldHeight;
|
|
this.forceHitArea = this.options.forceHitArea;
|
|
this.threshold = this.options.threshold;
|
|
|
|
this.options.divWheel = this.options.divWheel || document.body;
|
|
|
|
if (this.options.disableOnContextMenu)
|
|
{
|
|
this.options.divWheel.addEventListener('contextmenu', this._disableOnContextMenu);
|
|
}
|
|
if (!this.options.noTicker)
|
|
{
|
|
this.tickerFunction = () => this.update(this.options.ticker.elapsedMS);
|
|
this.options.ticker.add(this.tickerFunction);
|
|
}
|
|
|
|
this.input = new InputManager(this);
|
|
this.plugins = new PluginManager(this);
|
|
}
|
|
|
|
/** Overrides PIXI.Container's destroy to also remove the 'wheel' and PIXI.Ticker listeners */
|
|
destroy(options)
|
|
{
|
|
if (!this.options.noTicker && this.tickerFunction)
|
|
{
|
|
this.options.ticker.remove(this.tickerFunction);
|
|
}
|
|
if (this.options.disableOnContextMenu)
|
|
{
|
|
this.options.divWheel.removeEventListener('contextmenu', this._disableOnContextMenu);
|
|
}
|
|
|
|
this.input.destroy();
|
|
super.destroy(options);
|
|
}
|
|
|
|
/**
|
|
* Update viewport on each frame.
|
|
*
|
|
* By default, you do not need to call this unless you set `options.noTicker=true`.
|
|
*
|
|
* @param {number} elapsed time in milliseconds since last update
|
|
*/
|
|
update(elapsed)
|
|
{
|
|
if (!this.pause)
|
|
{
|
|
this.plugins.update(elapsed);
|
|
|
|
if (this.lastViewport)
|
|
{
|
|
// Check for moved-end event
|
|
if (this.lastViewport.x !== this.x || this.lastViewport.y !== this.y)
|
|
{
|
|
this.moving = true;
|
|
}
|
|
else if (this.moving)
|
|
{
|
|
this.emit('moved-end', this);
|
|
this.moving = false;
|
|
}
|
|
|
|
// Check for zoomed-end event
|
|
if (this.lastViewport.scaleX !== this.scale.x || this.lastViewport.scaleY !== this.scale.y)
|
|
{
|
|
this.zooming = true;
|
|
}
|
|
else if (this.zooming)
|
|
{
|
|
this.emit('zoomed-end', this);
|
|
this.zooming = false;
|
|
}
|
|
}
|
|
|
|
if (!this.forceHitArea)
|
|
{
|
|
this._hitAreaDefault = new math.Rectangle(this.left, this.top, this.worldScreenWidth, this.worldScreenHeight);
|
|
this.hitArea = this._hitAreaDefault;
|
|
}
|
|
|
|
this._dirty = this._dirty || !this.lastViewport
|
|
|| this.lastViewport.x !== this.x || this.lastViewport.y !== this.y
|
|
|| this.lastViewport.scaleX !== this.scale.x || this.lastViewport.scaleY !== this.scale.y;
|
|
|
|
this.lastViewport = {
|
|
x: this.x,
|
|
y: this.y,
|
|
scaleX: this.scale.x,
|
|
scaleY: this.scale.y
|
|
};
|
|
this.emit('frame-end', this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use this to set screen and world sizes, needed for pinch/wheel/clamp/bounce.
|
|
* @param {number} screenWidth=window.innerWidth
|
|
* @param {number} screenHeight=window.innerHeight
|
|
* @param {number} [worldWidth]
|
|
* @param {number} [worldHeight]
|
|
*/
|
|
resize(
|
|
screenWidth = window.innerWidth,
|
|
screenHeight = window.innerHeight,
|
|
worldWidth,
|
|
worldHeight
|
|
)
|
|
{
|
|
this.screenWidth = screenWidth;
|
|
this.screenHeight = screenHeight;
|
|
|
|
if (typeof worldWidth !== 'undefined')
|
|
{
|
|
this._worldWidth = worldWidth;
|
|
}
|
|
if (typeof worldHeight !== 'undefined')
|
|
{
|
|
this._worldHeight = worldHeight;
|
|
}
|
|
|
|
this.plugins.resize();
|
|
this.dirty = true;
|
|
}
|
|
|
|
/** World width, in pixels */
|
|
get worldWidth()
|
|
{
|
|
if (this._worldWidth)
|
|
{
|
|
return this._worldWidth;
|
|
}
|
|
|
|
return this.width / this.scale.x;
|
|
}
|
|
set worldWidth(value)
|
|
{
|
|
this._worldWidth = value;
|
|
this.plugins.resize();
|
|
}
|
|
|
|
/** World height, in pixels */
|
|
get worldHeight()
|
|
{
|
|
if (this._worldHeight)
|
|
{
|
|
return this._worldHeight;
|
|
}
|
|
|
|
return this.height / this.scale.y;
|
|
}
|
|
set worldHeight(value)
|
|
{
|
|
this._worldHeight = value;
|
|
this.plugins.resize();
|
|
}
|
|
|
|
/** Get visible world bounds of viewport */
|
|
getVisibleBounds()
|
|
{
|
|
return new math.Rectangle(this.left, this.top, this.worldScreenWidth, this.worldScreenHeight);
|
|
}
|
|
|
|
/** Change coordinates from screen to world */
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Changes coordinate from screen to world
|
|
* @param {number|PIXI.Point} x
|
|
* @param {number} y
|
|
* @returns {PIXI.Point}
|
|
*/
|
|
toWorld(x, y)
|
|
{
|
|
if (arguments.length === 2)
|
|
{
|
|
return this.toLocal(new math.Point(x , y));
|
|
}
|
|
|
|
return this.toLocal(x );
|
|
}
|
|
|
|
/** Change coordinates from world to screen */
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Changes coordinate from world to screen
|
|
* @param {number|PIXI.Point} x
|
|
* @param {number} y
|
|
* @returns {PIXI.Point}
|
|
*/
|
|
toScreen(x, y)
|
|
{
|
|
if (arguments.length === 2)
|
|
{
|
|
return this.toGlobal(new math.Point(x , y));
|
|
}
|
|
|
|
return this.toGlobal(x );
|
|
}
|
|
|
|
/** Screen width in world coordinates */
|
|
get worldScreenWidth()
|
|
{
|
|
return this.screenWidth / this.scale.x;
|
|
}
|
|
|
|
/** Screen height in world coordinates */
|
|
get worldScreenHeight()
|
|
{
|
|
return this.screenHeight / this.scale.y;
|
|
}
|
|
|
|
/** World width in screen coordinates */
|
|
get screenWorldWidth()
|
|
{
|
|
return this.worldWidth * this.scale.x;
|
|
}
|
|
|
|
/** World height in screen coordinates */
|
|
get screenWorldHeight()
|
|
{
|
|
return this.worldHeight * this.scale.y;
|
|
}
|
|
|
|
/** Center of screen in world coordinates */
|
|
get center()
|
|
{
|
|
return new math.Point(
|
|
(this.worldScreenWidth / 2) - (this.x / this.scale.x),
|
|
(this.worldScreenHeight / 2) - (this.y / this.scale.y),
|
|
);
|
|
}
|
|
set center(value)
|
|
{
|
|
this.moveCenter(value);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Move center of viewport to (x, y)
|
|
* @param {number|PIXI.Point} x
|
|
* @param {number} [y]
|
|
* @return {Viewport}
|
|
*/
|
|
moveCenter(...args)
|
|
{
|
|
let x;
|
|
let y;
|
|
|
|
if (typeof args[0] === 'number')
|
|
{
|
|
x = args[0];
|
|
y = args[1] ;
|
|
}
|
|
else
|
|
{
|
|
x = args[0].x;
|
|
y = args[0].y;
|
|
}
|
|
|
|
const newX = ((this.worldScreenWidth / 2) - x) * this.scale.x;
|
|
const newY = ((this.worldScreenHeight / 2) - y) * this.scale.y;
|
|
|
|
if (this.x !== newX || this.y !== newY)
|
|
{
|
|
this.position.set(newX, newY);
|
|
this.plugins.reset();
|
|
this.dirty = true;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/** Top-left corner of Viewport */
|
|
get corner()
|
|
{
|
|
return new math.Point(-this.x / this.scale.x, -this.y / this.scale.y);
|
|
}
|
|
set corner(value)
|
|
{
|
|
this.moveCorner(value);
|
|
}
|
|
|
|
/** Move Viewport's top-left corner; also clamps and resets decelerate and bounce (as needed) */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* MoveCorner
|
|
* @param {number|PIXI.Point} x
|
|
* @param {number} [y]
|
|
* @returns {Viewport}
|
|
*/
|
|
moveCorner(...args)
|
|
{
|
|
let x;
|
|
let y;
|
|
|
|
if (args.length === 1)
|
|
{
|
|
x = -args[0].x * this.scale.x;
|
|
y = -args[0].y * this.scale.y;
|
|
}
|
|
else
|
|
{
|
|
x = -args[0] * this.scale.x;
|
|
y = -args[1] * this.scale.y;
|
|
}
|
|
|
|
if (x !== this.x || y !== this.y)
|
|
{
|
|
this.position.set(x, y);
|
|
this.plugins.reset();
|
|
this.dirty = true;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/** Get how many world pixels fit in screen's width */
|
|
get screenWidthInWorldPixels()
|
|
{
|
|
return this.screenWidth / this.scale.x;
|
|
}
|
|
|
|
/** Get how many world pixels fit on screen's height */
|
|
get screenHeightInWorldPixels()
|
|
{
|
|
return this.screenHeight / this.scale.y;
|
|
}
|
|
|
|
/**
|
|
* Find the scale value that fits a world width on the screen
|
|
* does not change the viewport (use fit... to change)
|
|
*
|
|
* @param width - Width in world pixels
|
|
* @return - scale
|
|
*/
|
|
findFitWidth(width)
|
|
{
|
|
return this.screenWidth / width;
|
|
}
|
|
|
|
/**
|
|
* Finds the scale value that fits a world height on the screens
|
|
* does not change the viewport (use fit... to change)
|
|
*
|
|
* @param height - Height in world pixels
|
|
* @return - scale
|
|
*/
|
|
findFitHeight(height)
|
|
{
|
|
return this.screenHeight / height;
|
|
}
|
|
|
|
/**
|
|
* Finds the scale value that fits the smaller of a world width and world height on the screen
|
|
* does not change the viewport (use fit... to change)
|
|
*
|
|
* @param {number} width in world pixels
|
|
* @param {number} height in world pixels
|
|
* @returns {number} scale
|
|
*/
|
|
findFit(width, height)
|
|
{
|
|
const scaleX = this.screenWidth / width;
|
|
const scaleY = this.screenHeight / height;
|
|
|
|
return Math.min(scaleX, scaleY);
|
|
}
|
|
|
|
/**
|
|
* Finds the scale value that fits the larger of a world width and world height on the screen
|
|
* does not change the viewport (use fit... to change)
|
|
*
|
|
* @param {number} width in world pixels
|
|
* @param {number} height in world pixels
|
|
* @returns {number} scale
|
|
*/
|
|
findCover(width, height)
|
|
{
|
|
const scaleX = this.screenWidth / width;
|
|
const scaleY = this.screenHeight / height;
|
|
|
|
return Math.max(scaleX, scaleY);
|
|
}
|
|
|
|
/**
|
|
* Change zoom so the width fits in the viewport
|
|
*
|
|
* @param width - width in world coordinates
|
|
* @param center - maintain the same center
|
|
* @param scaleY - whether to set scaleY=scaleX
|
|
* @param noClamp - whether to disable clamp-zoom
|
|
* @returns {Viewport} this
|
|
*/
|
|
fitWidth(width = this.worldWidth, center, scaleY = true, noClamp)
|
|
{
|
|
let save;
|
|
|
|
if (center)
|
|
{
|
|
save = this.center;
|
|
}
|
|
this.scale.x = this.screenWidth / width;
|
|
|
|
if (scaleY)
|
|
{
|
|
this.scale.y = this.scale.x;
|
|
}
|
|
|
|
const clampZoom = this.plugins.get('clamp-zoom', true);
|
|
|
|
if (!noClamp && clampZoom)
|
|
{
|
|
clampZoom.clamp();
|
|
}
|
|
|
|
if (center && save)
|
|
{
|
|
this.moveCenter(save);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Change zoom so the height fits in the viewport
|
|
*
|
|
* @param {number} [height=this.worldHeight] in world coordinates
|
|
* @param {boolean} [center] maintain the same center of the screen after zoom
|
|
* @param {boolean} [scaleX=true] whether to set scaleX = scaleY
|
|
* @param {boolean} [noClamp] whether to disable clamp-zoom
|
|
* @returns {Viewport} this
|
|
*/
|
|
fitHeight(height = this.worldHeight, center, scaleX = true, noClamp)
|
|
{
|
|
let save;
|
|
|
|
if (center)
|
|
{
|
|
save = this.center;
|
|
}
|
|
this.scale.y = this.screenHeight / height;
|
|
|
|
if (scaleX)
|
|
{
|
|
this.scale.x = this.scale.y;
|
|
}
|
|
|
|
const clampZoom = this.plugins.get('clamp-zoom', true);
|
|
|
|
if (!noClamp && clampZoom)
|
|
{
|
|
clampZoom.clamp();
|
|
}
|
|
|
|
if (center && save)
|
|
{
|
|
this.moveCenter(save);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Change zoom so it fits the entire world in the viewport
|
|
*
|
|
* @param {boolean} center maintain the same center of the screen after zoom
|
|
* @returns {Viewport} this
|
|
*/
|
|
fitWorld(center)
|
|
{
|
|
let save;
|
|
|
|
if (center)
|
|
{
|
|
save = this.center;
|
|
}
|
|
|
|
this.scale.x = this.screenWidth / this.worldWidth;
|
|
this.scale.y = this.screenHeight / this.worldHeight;
|
|
|
|
if (this.scale.x < this.scale.y)
|
|
{
|
|
this.scale.y = this.scale.x;
|
|
}
|
|
else
|
|
{
|
|
this.scale.x = this.scale.y;
|
|
}
|
|
|
|
const clampZoom = this.plugins.get('clamp-zoom', true);
|
|
|
|
if (clampZoom)
|
|
{
|
|
clampZoom.clamp();
|
|
}
|
|
|
|
if (center && save)
|
|
{
|
|
this.moveCenter(save);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Change zoom so it fits the size or the entire world in the viewport
|
|
*
|
|
* @param {boolean} [center] maintain the same center of the screen after zoom
|
|
* @param {number} [width=this.worldWidth] desired width
|
|
* @param {number} [height=this.worldHeight] desired height
|
|
* @returns {Viewport} this
|
|
*/
|
|
fit(center, width = this.worldWidth, height = this.worldHeight)
|
|
{
|
|
let save;
|
|
|
|
if (center)
|
|
{
|
|
save = this.center;
|
|
}
|
|
|
|
this.scale.x = this.screenWidth / width;
|
|
this.scale.y = this.screenHeight / height;
|
|
|
|
if (this.scale.x < this.scale.y)
|
|
{
|
|
this.scale.y = this.scale.x;
|
|
}
|
|
else
|
|
{
|
|
this.scale.x = this.scale.y;
|
|
}
|
|
const clampZoom = this.plugins.get('clamp-zoom', true);
|
|
|
|
if (clampZoom)
|
|
{
|
|
clampZoom.clamp();
|
|
}
|
|
if (center && save)
|
|
{
|
|
this.moveCenter(save);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Zoom viewport to specific value.
|
|
*
|
|
* @param {number} scale value (e.g., 1 would be 100%, 0.25 would be 25%)
|
|
* @param {boolean} [center] maintain the same center of the screen after zoom
|
|
* @return {Viewport} this
|
|
*/
|
|
setZoom(scale, center)
|
|
{
|
|
let save;
|
|
|
|
if (center)
|
|
{
|
|
save = this.center;
|
|
}
|
|
this.scale.set(scale);
|
|
const clampZoom = this.plugins.get('clamp-zoom', true);
|
|
|
|
if (clampZoom)
|
|
{
|
|
clampZoom.clamp();
|
|
}
|
|
if (center && save)
|
|
{
|
|
this.moveCenter(save);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Zoom viewport by a certain percent (in both x and y direction).
|
|
*
|
|
* @param {number} percent change (e.g., 0.25 would increase a starting scale of 1.0 to 1.25)
|
|
* @param {boolean} [center] maintain the same center of the screen after zoom
|
|
* @return {Viewport} this
|
|
*/
|
|
zoomPercent(percent, center)
|
|
{
|
|
return this.setZoom(this.scale.x + (this.scale.x * percent), center);
|
|
}
|
|
|
|
/**
|
|
* Zoom viewport by increasing/decreasing width by a certain number of pixels.
|
|
*
|
|
* @param {number} change in pixels
|
|
* @param {boolean} [center] maintain the same center of the screen after zoom
|
|
* @return {Viewport} this
|
|
*/
|
|
zoom(change, center)
|
|
{
|
|
this.fitWidth(change + this.worldScreenWidth, center);
|
|
|
|
return this;
|
|
}
|
|
|
|
/** Changes scale of viewport and maintains center of viewport */
|
|
get scaled()
|
|
{
|
|
return this.scale.x;
|
|
}
|
|
set scaled(scale)
|
|
{
|
|
this.setZoom(scale, true);
|
|
}
|
|
|
|
/**
|
|
* Returns zoom to the desired scale
|
|
*
|
|
* @param {ISnapZoomOptions} options
|
|
* @param {number} [options.width=0] - the desired width to snap (to maintain aspect ratio, choose width or height)
|
|
* @param {number} [options.height=0] - the desired height to snap (to maintain aspect ratio, choose width or height)
|
|
* @param {number} [options.time=1000] - time for snapping in ms
|
|
* @param {(string|function)} [options.ease=easeInOutSine] ease function or name (see http://easings.net/
|
|
* for supported names)
|
|
* @param {PIXI.Point} [options.center] - place this point at center during zoom instead of center of the viewport
|
|
* @param {boolean} [options.interrupt=true] - pause snapping with any user input on the viewport
|
|
* @param {boolean} [options.removeOnComplete] - removes this plugin after snapping is complete
|
|
* @param {boolean} [options.removeOnInterrupt] - removes this plugin if interrupted by any user input
|
|
* @param {boolean} [options.forceStart] - starts the snap immediately regardless of whether the viewport is at the
|
|
* desired zoom
|
|
* @param {boolean} [options.noMove] - zoom but do not move
|
|
*/
|
|
snapZoom(options)
|
|
{
|
|
this.plugins.add('snap-zoom', new SnapZoom(this, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/** Is container out of world bounds */
|
|
OOB()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
return {
|
|
left: this.left < 0,
|
|
right: this.right > this.worldWidth,
|
|
top: this.top < 0,
|
|
bottom: this.bottom > this.worldHeight,
|
|
cornerPoint: new math.Point(
|
|
(this.worldWidth * this.scale.x) - this.screenWidth,
|
|
(this.worldHeight * this.scale.y) - this.screenHeight
|
|
)
|
|
};
|
|
}
|
|
|
|
/** World coordinates of the right edge of the screen */
|
|
get right()
|
|
{
|
|
return (-this.x / this.scale.x) + this.worldScreenWidth;
|
|
}
|
|
set right(value)
|
|
{
|
|
this.x = (-value * this.scale.x) + this.screenWidth;
|
|
this.plugins.reset();
|
|
}
|
|
|
|
/** World coordinates of the left edge of the screen */
|
|
get left()
|
|
{
|
|
return -this.x / this.scale.x;
|
|
}
|
|
set left(value)
|
|
{
|
|
this.x = -value * this.scale.x;
|
|
this.plugins.reset();
|
|
}
|
|
|
|
/** World coordinates of the top edge of the screen */
|
|
get top()
|
|
{
|
|
return -this.y / this.scale.y;
|
|
}
|
|
set top(value)
|
|
{
|
|
this.y = -value * this.scale.y;
|
|
this.plugins.reset();
|
|
}
|
|
|
|
/** World coordinates of the bottom edge of the screen */
|
|
get bottom()
|
|
{
|
|
return (-this.y / this.scale.y) + this.worldScreenHeight;
|
|
}
|
|
set bottom(value)
|
|
{
|
|
this.y = (-value * this.scale.y) + this.screenHeight;
|
|
this.plugins.reset();
|
|
}
|
|
|
|
/**
|
|
* Determines whether the viewport is dirty (i.e., needs to be rendered to the screen because of a change)
|
|
*/
|
|
get dirty()
|
|
{
|
|
return !!this._dirty;
|
|
}
|
|
set dirty(value)
|
|
{
|
|
this._dirty = value;
|
|
}
|
|
|
|
/**
|
|
* Permanently changes the Viewport's hitArea
|
|
*
|
|
* NOTE: if not set then hitArea = PIXI.Rectangle(Viewport.left, Viewport.top, Viewport.worldScreenWidth,
|
|
* Viewport.worldScreenHeight)
|
|
*/
|
|
get forceHitArea()
|
|
{
|
|
return this._forceHitArea;
|
|
}
|
|
set forceHitArea(value)
|
|
{
|
|
if (value)
|
|
{
|
|
this._forceHitArea = value;
|
|
this.hitArea = value;
|
|
}
|
|
else
|
|
{
|
|
this._forceHitArea = null;
|
|
this.hitArea = new math.Rectangle(0, 0, this.worldWidth, this.worldHeight);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable one-finger touch to drag
|
|
*
|
|
* NOTE: if you expect users to use right-click dragging, you should enable `viewport.options.disableOnContextMenu`
|
|
* to avoid the context menu popping up on each right-click drag.
|
|
*
|
|
* @param {IDragOptions} [options]
|
|
* @param {string} [options.direction=all] direction to drag
|
|
* @param {boolean} [options.pressDrag=true] whether click to drag is active
|
|
* @param {boolean} [options.wheel=true] use wheel to scroll in direction (unless wheel plugin is active)
|
|
* @param {number} [options.wheelScroll=1] number of pixels to scroll with each wheel spin
|
|
* @param {boolean} [options.reverse] reverse the direction of the wheel scroll
|
|
* @param {(boolean|string)} [options.clampWheel=false] clamp wheel(to avoid weird bounce with mouse wheel)
|
|
* @param {string} [options.underflow=center] where to place world if too small for screen
|
|
* @param {number} [options.factor=1] factor to multiply drag to increase the speed of movement
|
|
* @param {string} [options.mouseButtons=all] changes which mouse buttons trigger drag, use: 'all', 'left',
|
|
* 'right' 'middle', or some combination, like, 'middle-right'; you may want to set
|
|
* viewport.options.disableOnContextMenu if you want to use right-click dragging
|
|
* @param {string[]} [options.keyToPress=null] - array containing
|
|
* {@link key|https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code} codes of keys that can be
|
|
* pressed for the drag to be triggered, e.g.: ['ShiftLeft', 'ShiftRight'}.
|
|
* @param {boolean} [options.ignoreKeyToPressOnTouch=false] - ignore keyToPress for touch events
|
|
* @param {number} [options.lineHeight=20] - scaling factor for non-DOM_DELTA_PIXEL scrolling events
|
|
* @returns {Viewport} this
|
|
*/
|
|
drag(options)
|
|
{
|
|
this.plugins.add('drag', new Drag(this, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Clamp to world boundaries or other provided boundaries
|
|
* There are three ways to clamp:
|
|
* 1. direction: 'all' = the world is clamped to its world boundaries, ie, you cannot drag any part of offscreen
|
|
* direction: 'x' | 'y' = only the x or y direction is clamped to its world boundary
|
|
* 2. left, right, top, bottom = true | number = the world is clamped to the world's pixel location for each side;
|
|
* if any of these are set to true, then the location is set to the boundary
|
|
* [0, viewport.worldWidth/viewport.worldHeight], eg: to allow the world to be completely dragged offscreen,
|
|
* set [-viewport.worldWidth, -viewport.worldHeight, viewport.worldWidth * 2, viewport.worldHeight * 2]
|
|
*
|
|
* Underflow determines what happens when the world is smaller than the viewport
|
|
* 1. none = the world is clamped but there is no special behavior
|
|
* 2. center = the world is centered on the viewport
|
|
* 3. combination of top/bottom/center and left/right/center (case insensitive) = the world is stuck to the
|
|
* appropriate boundaries
|
|
*
|
|
* NOTES:
|
|
* clamp is disabled if called with no options; use { direction: 'all' } for all edge clamping
|
|
* screenWidth, screenHeight, worldWidth, and worldHeight needs to be set for this to work properly
|
|
*
|
|
* @param {object} [options]
|
|
* @param {(number|boolean)} [options.left=false] - clamp left; true = 0
|
|
* @param {(number|boolean)} [options.right=false] - clamp right; true = viewport.worldWidth
|
|
* @param {(number|boolean)} [options.top=false] - clamp top; true = 0
|
|
* @param {(number|boolean)} [options.bottom=false] - clamp bottom; true = viewport.worldHeight
|
|
* @param {string} [direction] - (all, x, or y) using clamps of [0, viewport.worldWidth/viewport.worldHeight];
|
|
* replaces left/right/top/bottom if set
|
|
* @param {string} [underflow=center] - where to place world if too small for screen (e.g., top-right, center,
|
|
* none, bottomLeft) * @returns {Viewport} this
|
|
*/
|
|
clamp(options)
|
|
{
|
|
this.plugins.add('clamp', new Clamp(this, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Decelerate after a move
|
|
*
|
|
* NOTE: this fires 'moved' event during deceleration
|
|
*
|
|
* @param {IDecelerateOptions} [options]
|
|
* @param {number} [options.friction=0.95] - percent to decelerate after movement
|
|
* @param {number} [options.bounce=0.8] - percent to decelerate when past boundaries (only applicable when
|
|
* viewport.bounce() is active)
|
|
* @param {number} [options.minSpeed=0.01] - minimum velocity before stopping/reversing acceleration
|
|
* @return {Viewport} this
|
|
*/
|
|
decelerate(options)
|
|
{
|
|
this.plugins.add('decelerate', new Decelerate(this, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Bounce on borders
|
|
* NOTES:
|
|
* screenWidth, screenHeight, worldWidth, and worldHeight needs to be set for this to work properly
|
|
* fires 'moved', 'bounce-x-start', 'bounce-y-start', 'bounce-x-end', and 'bounce-y-end' events
|
|
* @param {object} [options]
|
|
* @param {string} [options.sides=all] - all, horizontal, vertical, or combination of top, bottom, right, left
|
|
* (e.g., 'top-bottom-right')
|
|
* @param {number} [options.friction=0.5] - friction to apply to decelerate if active
|
|
* @param {number} [options.time=150] - time in ms to finish bounce
|
|
* @param {object} [options.bounceBox] - use this bounceBox instead of (0, 0, viewport.worldWidth, viewport.worldHeight)
|
|
* @param {number} [options.bounceBox.x=0]
|
|
* @param {number} [options.bounceBox.y=0]
|
|
* @param {number} [options.bounceBox.width=viewport.worldWidth]
|
|
* @param {number} [options.bounceBox.height=viewport.worldHeight]
|
|
* @param {string|function} [options.ease=easeInOutSine] - ease function or name
|
|
* (see http://easings.net/ for supported names)
|
|
* @param {string} [options.underflow=center] - (top/bottom/center and left/right/center, or center)
|
|
* where to place world if too small for screen
|
|
* @return {Viewport} this
|
|
*/
|
|
bounce(options)
|
|
{
|
|
this.plugins.add('bounce', new Bounce(this, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Enable pinch to zoom and two-finger touch to drag
|
|
*
|
|
* @param {PinchOptions} [options]
|
|
* @param {boolean} [options.noDrag] - disable two-finger dragging
|
|
* @param {number} [options.percent=1] - percent to modify pinch speed
|
|
* @param {number} [options.factor=1] - factor to multiply two-finger drag to increase the speed of movement
|
|
* @param {PIXI.Point} [options.center] - place this point at center during zoom instead of center of two fingers
|
|
* @param {('all'|'x'|'y')} [options.axis=all] - axis to zoom
|
|
* @return {Viewport} this
|
|
*/
|
|
pinch(options)
|
|
{
|
|
this.plugins.add('pinch', new Pinch(this, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Snap to a point
|
|
*
|
|
* @param {number} x
|
|
* @param {number} y
|
|
* @param {ISnapOptions} [options]
|
|
* @param {boolean} [options.topLeft] - snap to the top-left of viewport instead of center
|
|
* @param {number} [options.friction=0.8] - friction/frame to apply if decelerate is active
|
|
* @param {number} [options.time=1000] - time in ms to snap
|
|
* @param {string|function} [options.ease=easeInOutSine] - ease function or name (see http://easings.net/
|
|
* for supported names)
|
|
* @param {boolean} [options.interrupt=true] - pause snapping with any user input on the viewport
|
|
* @param {boolean} [options.removeOnComplete] - removes this plugin after snapping is complete
|
|
* @param {boolean} [options.removeOnInterrupt] - removes this plugin if interrupted by any user input
|
|
* @param {boolean} [options.forceStart] - starts the snap immediately regardless of whether the viewport is at
|
|
* the desired location
|
|
* @return {Viewport} this
|
|
*/
|
|
snap(x, y, options)
|
|
{
|
|
this.plugins.add('snap', new Snap(this, x, y, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Follow a target
|
|
*
|
|
* NOTES:
|
|
* uses the (x, y) as the center to follow; for PIXI.Sprite to work properly, use sprite.anchor.set(0.5)
|
|
* options.acceleration is not perfect as it doesn't know the velocity of the target. It adds acceleration
|
|
* to the start of movement and deceleration to the end of movement when the target is stopped.
|
|
* To cancel the follow, use: `viewport.plugins.remove('follow')`
|
|
*
|
|
* @fires 'moved' event
|
|
*
|
|
* @param {PIXI.DisplayObject} target to follow
|
|
* @param {IFollowOptions} [options]
|
|
* @param {number} [options.speed=0] - to follow in pixels/frame (0=teleport to location)
|
|
* @param {number} [options.acceleration] - set acceleration to accelerate and decelerate at this rate; speed
|
|
* cannot be 0 to use acceleration
|
|
* @param {number} [options.radius] - radius (in world coordinates) of center circle where movement is allowed
|
|
* without moving the viewport * @returns {Viewport} this
|
|
* @returns {Viewport} this
|
|
*/
|
|
follow(target, options)
|
|
{
|
|
this.plugins.add('follow', new Follow(this, target, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Zoom using mouse wheel
|
|
*
|
|
* NOTE: the default event listener for 'wheel' event is document.body. Use `Viewport.options.divWheel` to
|
|
* change this default
|
|
*
|
|
* @param {IWheelOptions} [options]
|
|
* @param {number} [options.percent=0.1] - percent to scroll with each spin
|
|
* @param {number} [options.smooth] - smooth the zooming by providing the number of frames to zoom between wheel spins
|
|
* @param {boolean} [options.interrupt=true] - stop smoothing with any user input on the viewport
|
|
* @param {boolean} [options.reverse] - reverse the direction of the scroll
|
|
* @param {PIXI.Point} [options.center] - place this point at center during zoom instead of current mouse position
|
|
* @param {number} [options.lineHeight=20] - scaling factor for non-DOM_DELTA_PIXEL scrolling events
|
|
* @param {('all'|'x'|'y')} [options.axis=all] - axis to zoom
|
|
* @return {Viewport} this
|
|
*/
|
|
wheel(options)
|
|
{
|
|
this.plugins.add('wheel', new Wheel(this, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Animate the position and/or scale of the viewport
|
|
* To set the zoom level, use: (1) scale, (2) scaleX and scaleY, or (3) width and/or height
|
|
* @param {object} options
|
|
* @param {number} [options.time=1000] - time to animate
|
|
* @param {PIXI.Point} [options.position=viewport.center] - position to move viewport
|
|
* @param {number} [options.width] - desired viewport width in world pixels (use instead of scale;
|
|
* aspect ratio is maintained if height is not provided)
|
|
* @param {number} [options.height] - desired viewport height in world pixels (use instead of scale;
|
|
* aspect ratio is maintained if width is not provided)
|
|
* @param {number} [options.scale] - scale to change zoom (scale.x = scale.y)
|
|
* @param {number} [options.scaleX] - independently change zoom in x-direction
|
|
* @param {number} [options.scaleY] - independently change zoom in y-direction
|
|
* @param {(function|string)} [options.ease=linear] - easing function to use
|
|
* @param {function} [options.callbackOnComplete]
|
|
* @param {boolean} [options.removeOnInterrupt] removes this plugin if interrupted by any user input
|
|
* @returns {Viewport} this
|
|
*/
|
|
animate(options)
|
|
{
|
|
this.plugins.add('animate', new Animate(this, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Enable clamping of zoom to constraints
|
|
*
|
|
* The minWidth/Height settings are how small the world can get (as it would appear on the screen)
|
|
* before clamping. The maxWidth/maxHeight is how larger the world can scale (as it would appear on
|
|
* the screen) before clamping.
|
|
*
|
|
* For example, if you have a world size of 1000 x 1000 and a screen size of 100 x 100, if you set
|
|
* minWidth/Height = 100 then the world will not be able to zoom smaller than the screen size (ie,
|
|
* zooming out so it appears smaller than the screen). Similarly, if you set maxWidth/Height = 100
|
|
* the world will not be able to zoom larger than the screen size (ie, zooming in so it appears
|
|
* larger than the screen).
|
|
*
|
|
* @param {object} [options]
|
|
* @param {number} [options.minWidth] - minimum width
|
|
* @param {number} [options.minHeight] - minimum height
|
|
* @param {number} [options.maxWidth] - maximum width
|
|
* @param {number} [options.maxHeight] - maximum height
|
|
* @param {number} [options.minScale] - minimum scale
|
|
* @param {number} [options.maxScale] - minimum scale
|
|
* @return {Viewport} this
|
|
*/
|
|
clampZoom(options)
|
|
{
|
|
this.plugins.add('clamp-zoom', new ClampZoom(this, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Scroll viewport when mouse hovers near one of the edges or radius-distance from center of screen.
|
|
*
|
|
* NOTES: fires 'moved' event; there's a known bug where the mouseEdges does not work properly with "windowed" viewports
|
|
*
|
|
* @param {IMouseEdgesOptions} [options]
|
|
* @param {number} [options.radius] - distance from center of screen in screen pixels
|
|
* @param {number} [options.distance] - distance from all sides in screen pixels
|
|
* @param {number} [options.top] - alternatively, set top distance (leave unset for no top scroll)
|
|
* @param {number} [options.bottom] - alternatively, set bottom distance (leave unset for no top scroll)
|
|
* @param {number} [options.left] - alternatively, set left distance (leave unset for no top scroll)
|
|
* @param {number} [options.right] - alternatively, set right distance (leave unset for no top scroll)
|
|
* @param {number} [options.speed=8] - speed in pixels/frame to scroll viewport
|
|
* @param {boolean} [options.reverse] - reverse direction of scroll
|
|
* @param {boolean} [options.noDecelerate] - don't use decelerate plugin even if it's installed
|
|
* @param {boolean} [options.linear] - if using radius, use linear movement (+/- 1, +/- 1) instead of angled
|
|
* movement (Math.cos(angle from center), Math.sin(angle from center))
|
|
* @param {boolean} [options.allowButtons] allows plugin to continue working even when there's a mousedown event
|
|
*/
|
|
mouseEdges(options)
|
|
{
|
|
this.plugins.add('mouse-edges', new MouseEdges(this, options));
|
|
|
|
return this;
|
|
}
|
|
|
|
/** Pause viewport (including animation updates such as decelerate) */
|
|
get pause()
|
|
{
|
|
return !!this._pause;
|
|
}
|
|
set pause(value)
|
|
{
|
|
this._pause = value;
|
|
|
|
this.lastViewport = null;
|
|
this.moving = false;
|
|
this.zooming = false;
|
|
|
|
if (value)
|
|
{
|
|
this.input.pause();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Move the viewport so the bounding box is visible
|
|
*
|
|
* @param x - left
|
|
* @param y - top
|
|
* @param width
|
|
* @param height
|
|
* @param resizeToFit - Resize the viewport so the box fits within the viewport
|
|
*/
|
|
ensureVisible(x, y, width, height, resizeToFit)
|
|
{
|
|
if (resizeToFit && (width > this.worldScreenWidth || height > this.worldScreenHeight))
|
|
{
|
|
this.fit(true, width, height);
|
|
this.emit('zoomed', { viewport: this, type: 'ensureVisible' });
|
|
}
|
|
let moved = false;
|
|
|
|
if (x < this.left)
|
|
{
|
|
this.left = x;
|
|
moved = true;
|
|
}
|
|
else if (x + width > this.right)
|
|
{
|
|
this.right = x + width;
|
|
moved = true;
|
|
}
|
|
if (y < this.top)
|
|
{
|
|
this.top = y;
|
|
moved = true;
|
|
}
|
|
else if (y + height > this.bottom)
|
|
{
|
|
this.bottom = y + height;
|
|
moved = true;
|
|
}
|
|
if (moved)
|
|
{
|
|
this.emit('moved', { viewport: this, type: 'ensureVisible' });
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fires after a mouse or touch click
|
|
* @event Viewport#clicked
|
|
* @type {object}
|
|
* @property {PIXI.Point} screen
|
|
* @property {PIXI.Point} world
|
|
* @property {Viewport} viewport
|
|
*/
|
|
|
|
/**
|
|
* Fires when a drag starts
|
|
* @event Viewport#drag-start
|
|
* @type {object}
|
|
* @property {PIXI.Point} screen
|
|
* @property {PIXI.Point} world
|
|
* @property {Viewport} viewport
|
|
*/
|
|
|
|
/**
|
|
* Fires when a drag ends
|
|
* @event Viewport#drag-end
|
|
* @type {object}
|
|
* @property {PIXI.Point} screen
|
|
* @property {PIXI.Point} world
|
|
* @property {Viewport} viewport
|
|
*/
|
|
|
|
/**
|
|
* Fires when a pinch starts
|
|
* @event Viewport#pinch-start
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when a pinch end
|
|
* @event Viewport#pinch-end
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when a snap starts
|
|
* @event Viewport#snap-start
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when a snap ends
|
|
* @event Viewport#snap-end
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when a snap-zoom starts
|
|
* @event Viewport#snap-zoom-start
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when a snap-zoom ends
|
|
* @event Viewport#snap-zoom-end
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when a bounce starts in the x direction
|
|
* @event Viewport#bounce-x-start
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when a bounce ends in the x direction
|
|
* @event Viewport#bounce-x-end
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when a bounce starts in the y direction
|
|
* @event Viewport#bounce-y-start
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when a bounce ends in the y direction
|
|
* @event Viewport#bounce-y-end
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when for a mouse wheel event
|
|
* @event Viewport#wheel
|
|
* @type {object}
|
|
* @property {object} wheel
|
|
* @property {number} wheel.dx
|
|
* @property {number} wheel.dy
|
|
* @property {number} wheel.dz
|
|
* @property {Viewport} viewport
|
|
*/
|
|
|
|
/**
|
|
* Fires when a wheel-scroll occurs
|
|
* @event Viewport#wheel-scroll
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when a mouse-edge starts to scroll
|
|
* @event Viewport#mouse-edge-start
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when the mouse-edge scrolling ends
|
|
* @event Viewport#mouse-edge-end
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when viewport moves through UI interaction, deceleration, ensureVisible, or follow
|
|
* @event Viewport#moved
|
|
* @type {object}
|
|
* @property {Viewport} viewport
|
|
* @property {string} type - (drag, snap, pinch, follow, bounce-x, bounce-y,
|
|
* clamp-x, clamp-y, decelerate, mouse-edges, wheel, ensureVisible)
|
|
*/
|
|
|
|
/**
|
|
* Fires when viewport moves through UI interaction, deceleration, ensureVisible, or follow
|
|
* @event Viewport#zoomed
|
|
* @type {object}
|
|
* @property {Viewport} viewport
|
|
* @property {string} type (drag-zoom, pinch, wheel, clamp-zoom, ensureVisible)
|
|
*/
|
|
|
|
/**
|
|
* Fires when viewport stops moving
|
|
* @event Viewport#moved-end
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires when viewport stops zooming
|
|
* @event Viewport#zoomed-end
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
/**
|
|
* Fires at the end of an update frame
|
|
* @event Viewport#frame-end
|
|
* @type {Viewport}
|
|
*/
|
|
|
|
exports.Animate = Animate;
|
|
exports.Bounce = Bounce;
|
|
exports.Clamp = Clamp;
|
|
exports.ClampZoom = ClampZoom;
|
|
exports.Decelerate = Decelerate;
|
|
exports.Drag = Drag;
|
|
exports.Follow = Follow;
|
|
exports.InputManager = InputManager;
|
|
exports.MouseEdges = MouseEdges;
|
|
exports.Pinch = Pinch;
|
|
exports.Plugin = Plugin;
|
|
exports.PluginManager = PluginManager;
|
|
exports.Snap = Snap;
|
|
exports.SnapZoom = SnapZoom;
|
|
exports.Viewport = Viewport;
|
|
exports.Wheel = Wheel;
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
}));
|
|
if (typeof pixi_viewport !== 'undefined') { Object.assign(this.PIXI, pixi_viewport); }
|
|
//# sourceMappingURL=viewport.min.js.map
|