phaser/src/input/InputPlugin.js
Jay Mattis c5cfae0118 Fix drag coordinates with camera zoom (issue 4755)
The dragX and dragY passed to 'dragStart' and 'drag' events are supported to be in world coordinates (as specified by the docs) but the coordinates used (pointer.x and pointer.y) were not transformed by the camera into world space. The drags now use pointer.worldX and pointer.worldY which are the post-transform coordinates.
2020-07-12 21:13:33 -07:00

3168 lines
98 KiB
JavaScript

/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2020 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Circle = require('../geom/circle/Circle');
var CircleContains = require('../geom/circle/Contains');
var Class = require('../utils/Class');
var CONST = require('./const');
var CreateInteractiveObject = require('./CreateInteractiveObject');
var CreatePixelPerfectHandler = require('./CreatePixelPerfectHandler');
var DistanceBetween = require('../math/distance/DistanceBetween');
var Ellipse = require('../geom/ellipse/Ellipse');
var EllipseContains = require('../geom/ellipse/Contains');
var Events = require('./events');
var EventEmitter = require('eventemitter3');
var GetFastValue = require('../utils/object/GetFastValue');
var GEOM_CONST = require('../geom/const');
var InputPluginCache = require('./InputPluginCache');
var IsPlainObject = require('../utils/object/IsPlainObject');
var PluginCache = require('../plugins/PluginCache');
var Rectangle = require('../geom/rectangle/Rectangle');
var RectangleContains = require('../geom/rectangle/Contains');
var SceneEvents = require('../scene/events');
var Triangle = require('../geom/triangle/Triangle');
var TriangleContains = require('../geom/triangle/Contains');
/**
* @classdesc
* The Input Plugin belongs to a Scene and handles all input related events and operations for it.
*
* You can access it from within a Scene using `this.input`.
*
* It emits events directly. For example, you can do:
*
* ```javascript
* this.input.on('pointerdown', callback, context);
* ```
*
* To listen for a pointer down event anywhere on the game canvas.
*
* Game Objects can be enabled for input by calling their `setInteractive` method. After which they
* will directly emit input events:
*
* ```javascript
* var sprite = this.add.sprite(x, y, texture);
* sprite.setInteractive();
* sprite.on('pointerdown', callback, context);
* ```
*
* There are lots of game configuration options available relating to input.
* See the [Input Config object]{@linkcode Phaser.Types.Core.InputConfig} for more details, including how to deal with Phaser
* listening for input events outside of the canvas, how to set a default number of pointers, input
* capture settings and more.
*
* Please also see the Input examples and tutorials for further information.
*
* @class InputPlugin
* @extends Phaser.Events.EventEmitter
* @memberof Phaser.Input
* @constructor
* @since 3.0.0
*
* @param {Phaser.Scene} scene - A reference to the Scene that this Input Plugin is responsible for.
*/
var InputPlugin = new Class({
Extends: EventEmitter,
initialize:
function InputPlugin (scene)
{
EventEmitter.call(this);
/**
* A reference to the Scene that this Input Plugin is responsible for.
*
* @name Phaser.Input.InputPlugin#scene
* @type {Phaser.Scene}
* @since 3.0.0
*/
this.scene = scene;
/**
* A reference to the Scene Systems class.
*
* @name Phaser.Input.InputPlugin#systems
* @type {Phaser.Scenes.Systems}
* @since 3.0.0
*/
this.systems = scene.sys;
/**
* A reference to the Scene Systems Settings.
*
* @name Phaser.Input.InputPlugin#settings
* @type {Phaser.Types.Scenes.SettingsObject}
* @since 3.5.0
*/
this.settings = scene.sys.settings;
/**
* A reference to the Game Input Manager.
*
* @name Phaser.Input.InputPlugin#manager
* @type {Phaser.Input.InputManager}
* @since 3.0.0
*/
this.manager = scene.sys.game.input;
/**
* Internal event queue used for plugins only.
*
* @name Phaser.Input.InputPlugin#pluginEvents
* @type {Phaser.Events.EventEmitter}
* @private
* @since 3.10.0
*/
this.pluginEvents = new EventEmitter();
/**
* If `true` this Input Plugin will process DOM input events.
*
* @name Phaser.Input.InputPlugin#enabled
* @type {boolean}
* @default true
* @since 3.5.0
*/
this.enabled = true;
/**
* A reference to the Scene Display List. This property is set during the `boot` method.
*
* @name Phaser.Input.InputPlugin#displayList
* @type {Phaser.GameObjects.DisplayList}
* @since 3.0.0
*/
this.displayList;
/**
* A reference to the Scene Cameras Manager. This property is set during the `boot` method.
*
* @name Phaser.Input.InputPlugin#cameras
* @type {Phaser.Cameras.Scene2D.CameraManager}
* @since 3.0.0
*/
this.cameras;
// Inject the available input plugins into this class
InputPluginCache.install(this);
/**
* A reference to the Mouse Manager.
*
* This property is only set if Mouse support has been enabled in your Game Configuration file.
*
* If you just wish to get access to the mouse pointer, use the `mousePointer` property instead.
*
* @name Phaser.Input.InputPlugin#mouse
* @type {?Phaser.Input.Mouse.MouseManager}
* @since 3.0.0
*/
this.mouse = this.manager.mouse;
/**
* When set to `true` (the default) the Input Plugin will emulate DOM behavior by only emitting events from
* the top-most Game Objects in the Display List.
*
* If set to `false` it will emit events from all Game Objects below a Pointer, not just the top one.
*
* @name Phaser.Input.InputPlugin#topOnly
* @type {boolean}
* @default true
* @since 3.0.0
*/
this.topOnly = true;
/**
* How often should the Pointers be checked?
*
* The value is a time, given in ms, and is the time that must have elapsed between game steps before
* the Pointers will be polled again. When a pointer is polled it runs a hit test to see which Game
* Objects are currently below it, or being interacted with it.
*
* Pointers will *always* be checked if they have been moved by the user, or press or released.
*
* This property only controls how often they will be polled if they have not been updated.
* You should set this if you want to have Game Objects constantly check against the pointers, even
* if the pointer didn't itself move.
*
* Set to 0 to poll constantly. Set to -1 to only poll on user movement.
*
* @name Phaser.Input.InputPlugin#pollRate
* @type {integer}
* @default -1
* @since 3.0.0
*/
this.pollRate = -1;
/**
* Internal poll timer value.
*
* @name Phaser.Input.InputPlugin#_pollTimer
* @type {number}
* @private
* @default 0
* @since 3.0.0
*/
this._pollTimer = 0;
var _eventData = { cancelled: false };
/**
* Internal event propagation callback container.
*
* @name Phaser.Input.InputPlugin#_eventContainer
* @type {Phaser.Types.Input.EventData}
* @private
* @since 3.13.0
*/
this._eventContainer = {
stopPropagation: function ()
{
_eventData.cancelled = true;
}
};
/**
* Internal event propagation data object.
*
* @name Phaser.Input.InputPlugin#_eventData
* @type {object}
* @private
* @since 3.13.0
*/
this._eventData = _eventData;
/**
* The distance, in pixels, a pointer has to move while being held down, before it thinks it is being dragged.
*
* @name Phaser.Input.InputPlugin#dragDistanceThreshold
* @type {number}
* @default 0
* @since 3.0.0
*/
this.dragDistanceThreshold = 0;
/**
* The amount of time, in ms, a pointer has to be held down before it thinks it is dragging.
*
* The default polling rate is to poll only on move so once the time threshold is reached the
* drag event will not start until you move the mouse. If you want it to start immediately
* when the time threshold is reached, you must increase the polling rate by calling
* [setPollAlways]{@linkcode Phaser.Input.InputPlugin#setPollAlways} or
* [setPollRate]{@linkcode Phaser.Input.InputPlugin#setPollRate}.
*
* @name Phaser.Input.InputPlugin#dragTimeThreshold
* @type {number}
* @default 0
* @since 3.0.0
*/
this.dragTimeThreshold = 0;
/**
* Used to temporarily store the results of the Hit Test
*
* @name Phaser.Input.InputPlugin#_temp
* @type {array}
* @private
* @default []
* @since 3.0.0
*/
this._temp = [];
/**
* Used to temporarily store the results of the Hit Test dropZones
*
* @name Phaser.Input.InputPlugin#_tempZones
* @type {array}
* @private
* @default []
* @since 3.0.0
*/
this._tempZones = [];
/**
* A list of all Game Objects that have been set to be interactive in the Scene this Input Plugin is managing.
*
* @name Phaser.Input.InputPlugin#_list
* @type {Phaser.GameObjects.GameObject[]}
* @private
* @default []
* @since 3.0.0
*/
this._list = [];
/**
* Objects waiting to be inserted to the list on the next call to 'begin'.
*
* @name Phaser.Input.InputPlugin#_pendingInsertion
* @type {Phaser.GameObjects.GameObject[]}
* @private
* @default []
* @since 3.0.0
*/
this._pendingInsertion = [];
/**
* Objects waiting to be removed from the list on the next call to 'begin'.
*
* @name Phaser.Input.InputPlugin#_pendingRemoval
* @type {Phaser.GameObjects.GameObject[]}
* @private
* @default []
* @since 3.0.0
*/
this._pendingRemoval = [];
/**
* A list of all Game Objects that have been enabled for dragging.
*
* @name Phaser.Input.InputPlugin#_draggable
* @type {Phaser.GameObjects.GameObject[]}
* @private
* @default []
* @since 3.0.0
*/
this._draggable = [];
/**
* A list of all Interactive Objects currently considered as being 'draggable' by any pointer, indexed by pointer ID.
*
* @name Phaser.Input.InputPlugin#_drag
* @type {{0:Array,1:Array,2:Array,3:Array,4:Array,5:Array,6:Array,7:Array,8:Array,9:Array,10:Array}}
* @private
* @since 3.0.0
*/
this._drag = { 0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [] };
/**
* A array containing the dragStates, for this Scene, index by the Pointer ID.
*
* @name Phaser.Input.InputPlugin#_dragState
* @type {integer[]}
* @private
* @since 3.16.0
*/
this._dragState = [];
/**
* A list of all Interactive Objects currently considered as being 'over' by any pointer, indexed by pointer ID.
*
* @name Phaser.Input.InputPlugin#_over
* @type {{0:Array,1:Array,2:Array,3:Array,4:Array,5:Array,6:Array,7:Array,8:Array,9:Array,10:Array}}
* @private
* @since 3.0.0
*/
this._over = { 0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [] };
/**
* A list of valid DOM event types.
*
* @name Phaser.Input.InputPlugin#_validTypes
* @type {string[]}
* @private
* @since 3.0.0
*/
this._validTypes = [ 'onDown', 'onUp', 'onOver', 'onOut', 'onMove', 'onDragStart', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragLeave', 'onDragOver', 'onDrop' ];
/**
* Internal property that tracks frame event state.
*
* @name Phaser.Input.InputPlugin#_updatedThisFrame
* @type {boolean}
* @private
* @since 3.18.0
*/
this._updatedThisFrame = false;
scene.sys.events.once(SceneEvents.BOOT, this.boot, this);
scene.sys.events.on(SceneEvents.START, this.start, this);
},
/**
* This method is called automatically, only once, when the Scene is first created.
* Do not invoke it directly.
*
* @method Phaser.Input.InputPlugin#boot
* @fires Phaser.Input.Events#BOOT
* @private
* @since 3.5.1
*/
boot: function ()
{
this.cameras = this.systems.cameras;
this.displayList = this.systems.displayList;
this.systems.events.once(SceneEvents.DESTROY, this.destroy, this);
// Registered input plugins listen for this
this.pluginEvents.emit(Events.BOOT);
},
/**
* This method is called automatically by the Scene when it is starting up.
* It is responsible for creating local systems, properties and listening for Scene events.
* Do not invoke it directly.
*
* @method Phaser.Input.InputPlugin#start
* @fires Phaser.Input.Events#START
* @private
* @since 3.5.0
*/
start: function ()
{
var eventEmitter = this.systems.events;
eventEmitter.on(SceneEvents.TRANSITION_START, this.transitionIn, this);
eventEmitter.on(SceneEvents.TRANSITION_OUT, this.transitionOut, this);
eventEmitter.on(SceneEvents.TRANSITION_COMPLETE, this.transitionComplete, this);
eventEmitter.on(SceneEvents.PRE_UPDATE, this.preUpdate, this);
eventEmitter.once(SceneEvents.SHUTDOWN, this.shutdown, this);
this.manager.events.on(Events.GAME_OUT, this.onGameOut, this);
this.manager.events.on(Events.GAME_OVER, this.onGameOver, this);
this.enabled = true;
// Populate the pointer drag states
this._dragState = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
// Registered input plugins listen for this
this.pluginEvents.emit(Events.START);
},
/**
* Game Over handler.
*
* @method Phaser.Input.InputPlugin#onGameOver
* @fires Phaser.Input.Events#GAME_OVER
* @private
* @since 3.16.2
*/
onGameOver: function (event)
{
if (this.isActive())
{
this.emit(Events.GAME_OVER, event.timeStamp, event);
}
},
/**
* Game Out handler.
*
* @method Phaser.Input.InputPlugin#onGameOut
* @fires Phaser.Input.Events#GAME_OUT
* @private
* @since 3.16.2
*/
onGameOut: function (event)
{
if (this.isActive())
{
this.emit(Events.GAME_OUT, event.timeStamp, event);
}
},
/**
* The pre-update handler is responsible for checking the pending removal and insertion lists and
* deleting old Game Objects.
*
* @method Phaser.Input.InputPlugin#preUpdate
* @private
* @fires Phaser.Input.Events#PRE_UPDATE
* @since 3.0.0
*/
preUpdate: function ()
{
// Registered input plugins listen for this
this.pluginEvents.emit(Events.PRE_UPDATE);
var removeList = this._pendingRemoval;
var insertList = this._pendingInsertion;
var toRemove = removeList.length;
var toInsert = insertList.length;
if (toRemove === 0 && toInsert === 0)
{
// Quick bail
return;
}
var current = this._list;
// Delete old gameObjects
for (var i = 0; i < toRemove; i++)
{
var gameObject = removeList[i];
var index = current.indexOf(gameObject);
if (index > -1)
{
current.splice(index, 1);
this.clear(gameObject, true);
}
}
// Clear the removal list
removeList.length = 0;
this._pendingRemoval.length = 0;
// Move pendingInsertion to list (also clears pendingInsertion at the same time)
this._list = current.concat(insertList.splice(0));
},
/**
* Checks to see if both this plugin and the Scene to which it belongs is active.
*
* @method Phaser.Input.InputPlugin#isActive
* @since 3.10.0
*
* @return {boolean} `true` if the plugin and the Scene it belongs to is active.
*/
isActive: function ()
{
return (this.enabled && this.scene.sys.isActive());
},
/**
* This is called automatically by the Input Manager.
* It emits events for plugins to listen to and also handles polling updates, if enabled.
*
* @method Phaser.Input.InputPlugin#updatePoll
* @since 3.18.0
*
* @param {number} time - The current time. Either a High Resolution Timer value if it comes from Request Animation Frame, or Date.now if using SetTimeout.
* @param {number} delta - The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
*
* @return {boolean} `true` if the plugin and the Scene it belongs to is active.
*/
updatePoll: function (time, delta)
{
if (!this.isActive())
{
return false;
}
// The plugins should update every frame, regardless if there has been
// any DOM input events or not (such as the Gamepad and Keyboard)
this.pluginEvents.emit(Events.UPDATE, time, delta);
// We can leave now if we've already updated once this frame via the immediate DOM event handlers
if (this._updatedThisFrame)
{
this._updatedThisFrame = false;
return false;
}
var i;
var manager = this.manager;
var pointers = manager.pointers;
var pointersTotal = manager.pointersTotal;
for (i = 0; i < pointersTotal; i++)
{
pointers[i].updateMotion();
}
// No point going any further if there aren't any interactive objects
if (this._list.length === 0)
{
return false;
}
var rate = this.pollRate;
if (rate === -1)
{
return false;
}
else if (rate > 0)
{
this._pollTimer -= delta;
if (this._pollTimer < 0)
{
// Discard timer diff, we're ready to poll again
this._pollTimer = this.pollRate;
}
else
{
// Not enough time has elapsed since the last poll, so abort now
return false;
}
}
// We got this far? Then we should poll for movement
var captured = false;
for (i = 0; i < pointersTotal; i++)
{
var total = 0;
var pointer = pointers[i];
// Always reset this array
this._tempZones = [];
// _temp contains a hit tested and camera culled list of IO objects
this._temp = this.hitTestPointer(pointer);
this.sortGameObjects(this._temp);
this.sortGameObjects(this._tempZones);
if (this.topOnly)
{
// Only the top-most one counts now, so safely ignore the rest
if (this._temp.length)
{
this._temp.splice(1);
}
if (this._tempZones.length)
{
this._tempZones.splice(1);
}
}
total += this.processOverOutEvents(pointer);
if (this.getDragState(pointer) === 2)
{
this.processDragThresholdEvent(pointer, time);
}
if (total > 0)
{
// We interacted with an event in this Scene, so block any Scenes below us from doing the same this frame
captured = true;
}
}
return captured;
},
/**
* This method is called when a DOM Event is received by the Input Manager. It handles dispatching the events
* to relevant input enabled Game Objects in this scene.
*
* @method Phaser.Input.InputPlugin#update
* @private
* @fires Phaser.Input.Events#UPDATE
* @since 3.0.0
*
* @param {integer} type - The type of event to process.
* @param {Phaser.Input.Pointer[]} pointers - An array of Pointers on which the event occurred.
*
* @return {boolean} `true` if this Scene has captured the input events from all other Scenes, otherwise `false`.
*/
update: function (type, pointers)
{
if (!this.isActive())
{
return false;
}
var pointersTotal = pointers.length;
var captured = false;
for (var i = 0; i < pointersTotal; i++)
{
var total = 0;
var pointer = pointers[i];
// Always reset this array
this._tempZones = [];
// _temp contains a hit tested and camera culled list of IO objects
this._temp = this.hitTestPointer(pointer);
this.sortGameObjects(this._temp);
this.sortGameObjects(this._tempZones);
if (this.topOnly)
{
// Only the top-most one counts now, so safely ignore the rest
if (this._temp.length)
{
this._temp.splice(1);
}
if (this._tempZones.length)
{
this._tempZones.splice(1);
}
}
switch (type)
{
case CONST.MOUSE_DOWN:
total += this.processDragDownEvent(pointer);
total += this.processDownEvents(pointer);
total += this.processOverOutEvents(pointer);
break;
case CONST.MOUSE_UP:
total += this.processDragUpEvent(pointer);
total += this.processUpEvents(pointer);
total += this.processOverOutEvents(pointer);
break;
case CONST.TOUCH_START:
total += this.processDragDownEvent(pointer);
total += this.processDownEvents(pointer);
total += this.processOverEvents(pointer);
break;
case CONST.TOUCH_END:
case CONST.TOUCH_CANCEL:
total += this.processDragUpEvent(pointer);
total += this.processUpEvents(pointer);
total += this.processOutEvents(pointer);
break;
case CONST.MOUSE_MOVE:
case CONST.TOUCH_MOVE:
total += this.processDragMoveEvent(pointer);
total += this.processMoveEvents(pointer);
total += this.processOverOutEvents(pointer);
break;
case CONST.MOUSE_WHEEL:
total += this.processWheelEvent(pointer);
break;
}
if (total > 0)
{
// We interacted with an event in this Scene, so block any Scenes below us from doing the same this frame
captured = true;
}
}
this._updatedThisFrame = true;
return captured;
},
/**
* Clears a Game Object so it no longer has an Interactive Object associated with it.
* The Game Object is then queued for removal from the Input Plugin on the next update.
*
* @method Phaser.Input.InputPlugin#clear
* @since 3.0.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - The Game Object that will have its Interactive Object removed.
* @param {boolean} [skipQueue=false] - Skip adding this Game Object into the removal queue?
*
* @return {Phaser.GameObjects.GameObject} The Game Object that had its Interactive Object removed.
*/
clear: function (gameObject, skipQueue)
{
if (skipQueue === undefined) { skipQueue = false; }
var input = gameObject.input;
// If GameObject.input already cleared from higher class
if (!input)
{
return;
}
if (!skipQueue)
{
this.queueForRemoval(gameObject);
}
input.gameObject = undefined;
input.target = undefined;
input.hitArea = undefined;
input.hitAreaCallback = undefined;
input.callbackContext = undefined;
this.manager.resetCursor(input);
gameObject.input = null;
// Clear from _draggable, _drag and _over
var index = this._draggable.indexOf(gameObject);
if (index > -1)
{
this._draggable.splice(index, 1);
}
index = this._drag[0].indexOf(gameObject);
if (index > -1)
{
this._drag[0].splice(index, 1);
}
index = this._over[0].indexOf(gameObject);
if (index > -1)
{
this._over[0].splice(index, 1);
}
return gameObject;
},
/**
* Disables Input on a single Game Object.
*
* An input disabled Game Object still retains its Interactive Object component and can be re-enabled
* at any time, by passing it to `InputPlugin.enable`.
*
* @method Phaser.Input.InputPlugin#disable
* @since 3.0.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - The Game Object to have its input system disabled.
*/
disable: function (gameObject)
{
gameObject.input.enabled = false;
},
/**
* Enable a Game Object for interaction.
*
* If the Game Object already has an Interactive Object component, it is enabled and returned.
*
* Otherwise, a new Interactive Object component is created and assigned to the Game Object's `input` property.
*
* Input works by using hit areas, these are nearly always geometric shapes, such as rectangles or circles, that act as the hit area
* for the Game Object. However, you can provide your own hit area shape and callback, should you wish to handle some more advanced
* input detection.
*
* If no arguments are provided it will try and create a rectangle hit area based on the texture frame the Game Object is using. If
* this isn't a texture-bound object, such as a Graphics or BitmapText object, this will fail, and you'll need to provide a specific
* shape for it to use.
*
* You can also provide an Input Configuration Object as the only argument to this method.
*
* @method Phaser.Input.InputPlugin#enable
* @since 3.0.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - The Game Object to be enabled for input.
* @param {(Phaser.Types.Input.InputConfiguration|any)} [shape] - Either an input configuration object, or a geometric shape that defines the hit area for the Game Object. If not specified a Rectangle will be used.
* @param {Phaser.Types.Input.HitAreaCallback} [callback] - The 'contains' function to invoke to check if the pointer is within the hit area.
* @param {boolean} [dropZone=false] - Is this Game Object a drop zone or not?
*
* @return {this} This Input Plugin.
*/
enable: function (gameObject, shape, callback, dropZone)
{
if (dropZone === undefined) { dropZone = false; }
if (gameObject.input)
{
// If it is already has an InteractiveObject then just enable it and return
gameObject.input.enabled = true;
}
else
{
// Create an InteractiveObject and enable it
this.setHitArea(gameObject, shape, callback);
}
if (gameObject.input && dropZone && !gameObject.input.dropZone)
{
gameObject.input.dropZone = dropZone;
}
return this;
},
/**
* Takes the given Pointer and performs a hit test against it, to see which interactive Game Objects
* it is currently above.
*
* The hit test is performed against which-ever Camera the Pointer is over. If it is over multiple
* cameras, it starts checking the camera at the top of the camera list, and if nothing is found, iterates down the list.
*
* @method Phaser.Input.InputPlugin#hitTestPointer
* @since 3.0.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer to check against the Game Objects.
*
* @return {Phaser.GameObjects.GameObject[]} An array of all the interactive Game Objects the Pointer was above.
*/
hitTestPointer: function (pointer)
{
var cameras = this.cameras.getCamerasBelowPointer(pointer);
for (var c = 0; c < cameras.length; c++)
{
var camera = cameras[c];
// Get a list of all objects that can be seen by the camera below the pointer in the scene and store in 'over' array.
// All objects in this array are input enabled, as checked by the hitTest method, so we don't need to check later on as well.
var over = this.manager.hitTest(pointer, this._list, camera);
// Filter out the drop zones
for (var i = 0; i < over.length; i++)
{
var obj = over[i];
if (obj.input.dropZone)
{
this._tempZones.push(obj);
}
}
if (over.length > 0)
{
pointer.camera = camera;
return over;
}
}
// If we got this far then there were no Game Objects below the pointer, but it was still over
// a camera, so set that the top-most one into the pointer
pointer.camera = cameras[0];
return [];
},
/**
* An internal method that handles the Pointer down event.
*
* @method Phaser.Input.InputPlugin#processDownEvents
* @private
* @fires Phaser.Input.Events#GAMEOBJECT_POINTER_DOWN
* @fires Phaser.Input.Events#GAMEOBJECT_DOWN
* @fires Phaser.Input.Events#POINTER_DOWN
* @fires Phaser.Input.Events#POINTER_DOWN_OUTSIDE
* @since 3.0.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer being tested.
*
* @return {integer} The total number of objects interacted with.
*/
processDownEvents: function (pointer)
{
var total = 0;
var currentlyOver = this._temp;
var _eventData = this._eventData;
var _eventContainer = this._eventContainer;
_eventData.cancelled = false;
var aborted = false;
// Go through all objects the pointer was over and fire their events / callbacks
for (var i = 0; i < currentlyOver.length; i++)
{
var gameObject = currentlyOver[i];
if (!gameObject.input)
{
continue;
}
total++;
gameObject.emit(Events.GAMEOBJECT_POINTER_DOWN, pointer, gameObject.input.localX, gameObject.input.localY, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
this.emit(Events.GAMEOBJECT_DOWN, pointer, gameObject, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
}
// If they released outside the canvas, but pressed down inside it, we'll still dispatch the event.
if (!aborted && this.manager)
{
if (pointer.downElement === this.manager.game.canvas)
{
this.emit(Events.POINTER_DOWN, pointer, currentlyOver);
}
else
{
this.emit(Events.POINTER_DOWN_OUTSIDE, pointer);
}
}
return total;
},
/**
* Returns the drag state of the given Pointer for this Input Plugin.
*
* The state will be one of the following:
*
* 0 = Not dragging anything
* 1 = Primary button down and objects below, so collect a draglist
* 2 = Pointer being checked if meets drag criteria
* 3 = Pointer meets criteria, notify the draglist
* 4 = Pointer actively dragging the draglist and has moved
* 5 = Pointer actively dragging but has been released, notify draglist
*
* @method Phaser.Input.InputPlugin#getDragState
* @since 3.16.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer to get the drag state for.
*
* @return {integer} The drag state of the given Pointer.
*/
getDragState: function (pointer)
{
return this._dragState[pointer.id];
},
/**
* Sets the drag state of the given Pointer for this Input Plugin.
*
* The state must be one of the following values:
*
* 0 = Not dragging anything
* 1 = Primary button down and objects below, so collect a draglist
* 2 = Pointer being checked if meets drag criteria
* 3 = Pointer meets criteria, notify the draglist
* 4 = Pointer actively dragging the draglist and has moved
* 5 = Pointer actively dragging but has been released, notify draglist
*
* @method Phaser.Input.InputPlugin#setDragState
* @since 3.16.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer to set the drag state for.
* @param {integer} state - The drag state value. An integer between 0 and 5.
*/
setDragState: function (pointer, state)
{
this._dragState[pointer.id] = state;
},
/**
* Checks to see if a Pointer is ready to drag the objects below it, based on either a distance
* or time threshold.
*
* @method Phaser.Input.InputPlugin#processDragThresholdEvent
* @private
* @since 3.18.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer to check the drag thresholds on.
* @param {number} time - The current time.
*/
processDragThresholdEvent: function (pointer, time)
{
var passed = false;
var timeThreshold = this.dragTimeThreshold;
var distanceThreshold = this.dragDistanceThreshold;
if (distanceThreshold > 0 && DistanceBetween(pointer.x, pointer.y, pointer.downX, pointer.downY) >= distanceThreshold)
{
// It has moved far enough to be considered a drag
passed = true;
}
else if (timeThreshold > 0 && (time >= pointer.downTime + timeThreshold))
{
// It has been held down long enough to be considered a drag
passed = true;
}
if (passed)
{
this.setDragState(pointer, 3);
return this.processDragStartList(pointer);
}
},
/**
* Processes the drag list for the given pointer and dispatches the start events for each object on it.
*
* @method Phaser.Input.InputPlugin#processDragStartList
* @private
* @fires Phaser.Input.Events#DRAG_START
* @fires Phaser.Input.Events#GAMEOBJECT_DRAG_START
* @since 3.18.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer to process the drag event on.
*
* @return {integer} The number of items that DRAG_START was called on.
*/
processDragStartList: function (pointer)
{
// 3 = Pointer meets criteria and is freshly down, notify the draglist
if (this.getDragState(pointer) !== 3)
{
return 0;
}
var list = this._drag[pointer.id];
for (var i = 0; i < list.length; i++)
{
var gameObject = list[i];
var input = gameObject.input;
input.dragState = 2;
input.dragStartX = gameObject.x;
input.dragStartY = gameObject.y;
input.dragStartXGlobal = pointer.worldX;
input.dragStartYGlobal = pointer.worldY;
input.dragX = input.dragStartXGlobal - input.dragStartX;
input.dragY = input.dragStartYGlobal - input.dragStartY;
gameObject.emit(Events.GAMEOBJECT_DRAG_START, pointer, input.dragX, input.dragY);
this.emit(Events.DRAG_START, pointer, gameObject);
}
this.setDragState(pointer, 4);
return list.length;
},
/**
* Processes a 'drag down' event for the given pointer. Checks the pointer state, builds-up the drag list
* and prepares them all for interaction.
*
* @method Phaser.Input.InputPlugin#processDragDownEvent
* @private
* @since 3.18.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer to process the drag event on.
*
* @return {integer} The number of items that were collected on the drag list.
*/
processDragDownEvent: function (pointer)
{
var currentlyOver = this._temp;
if (this._draggable.length === 0 || currentlyOver.length === 0 || !pointer.primaryDown || this.getDragState(pointer) !== 0)
{
// There are no draggable items, no over items or the pointer isn't down, so let's not even bother going further
return 0;
}
// 1 = Primary button down and objects below, so collect a draglist
this.setDragState(pointer, 1);
// Get draggable objects, sort them, pick the top (or all) and store them somewhere
var draglist = [];
for (var i = 0; i < currentlyOver.length; i++)
{
var gameObject = currentlyOver[i];
if (gameObject.input.draggable && (gameObject.input.dragState === 0))
{
draglist.push(gameObject);
}
}
if (draglist.length === 0)
{
this.setDragState(pointer, 0);
return 0;
}
else if (draglist.length > 1)
{
this.sortGameObjects(draglist);
if (this.topOnly)
{
draglist.splice(1);
}
}
// draglist now contains all potential candidates for dragging
this._drag[pointer.id] = draglist;
if (this.dragDistanceThreshold === 0 && this.dragTimeThreshold === 0)
{
// No drag criteria, so snap immediately to mode 3
this.setDragState(pointer, 3);
return this.processDragStartList(pointer);
}
else
{
// Check the distance / time on the next event
this.setDragState(pointer, 2);
return 0;
}
},
/**
* Processes a 'drag move' event for the given pointer.
*
* @method Phaser.Input.InputPlugin#processDragMoveEvent
* @private
* @fires Phaser.Input.Events#DRAG_ENTER
* @fires Phaser.Input.Events#DRAG
* @fires Phaser.Input.Events#DRAG_LEAVE
* @fires Phaser.Input.Events#DRAG_OVER
* @fires Phaser.Input.Events#GAMEOBJECT_DRAG_ENTER
* @fires Phaser.Input.Events#GAMEOBJECT_DRAG
* @fires Phaser.Input.Events#GAMEOBJECT_DRAG_LEAVE
* @fires Phaser.Input.Events#GAMEOBJECT_DRAG_OVER
* @since 3.18.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer to process the drag event on.
*
* @return {integer} The number of items that were updated by this drag event.
*/
processDragMoveEvent: function (pointer)
{
// 2 = Pointer being checked if meets drag criteria
if (this.getDragState(pointer) === 2)
{
this.processDragThresholdEvent(pointer, this.manager.game.loop.now);
}
if (this.getDragState(pointer) !== 4)
{
return 0;
}
// 4 = Pointer actively dragging the draglist and has moved
var dropZones = this._tempZones;
var list = this._drag[pointer.id];
for (var i = 0; i < list.length; i++)
{
var gameObject = list[i];
var input = gameObject.input;
var target = input.target;
// If this GO has a target then let's check it
if (target)
{
var index = dropZones.indexOf(target);
// Got a target, are we still over it?
if (index === 0)
{
// We're still over it, and it's still the top of the display list, phew ...
gameObject.emit(Events.GAMEOBJECT_DRAG_OVER, pointer, target);
this.emit(Events.DRAG_OVER, pointer, gameObject, target);
}
else if (index > 0)
{
// Still over it but it's no longer top of the display list (targets must always be at the top)
gameObject.emit(Events.GAMEOBJECT_DRAG_LEAVE, pointer, target);
this.emit(Events.DRAG_LEAVE, pointer, gameObject, target);
input.target = dropZones[0];
target = input.target;
gameObject.emit(Events.GAMEOBJECT_DRAG_ENTER, pointer, target);
this.emit(Events.DRAG_ENTER, pointer, gameObject, target);
}
else
{
// Nope, we've moved on (or the target has!), leave the old target
gameObject.emit(Events.GAMEOBJECT_DRAG_LEAVE, pointer, target);
this.emit(Events.DRAG_LEAVE, pointer, gameObject, target);
// Anything new to replace it?
// Yup!
if (dropZones[0])
{
input.target = dropZones[0];
target = input.target;
gameObject.emit(Events.GAMEOBJECT_DRAG_ENTER, pointer, target);
this.emit(Events.DRAG_ENTER, pointer, gameObject, target);
}
else
{
// Nope
input.target = null;
}
}
}
else if (!target && dropZones[0])
{
input.target = dropZones[0];
target = input.target;
gameObject.emit(Events.GAMEOBJECT_DRAG_ENTER, pointer, target);
this.emit(Events.DRAG_ENTER, pointer, gameObject, target);
}
var dragX;
var dragY;
if (!gameObject.parentContainer)
{
dragX = pointer.worldX - input.dragX;
dragY = pointer.worldY - input.dragY;
}
else
{
var dx = pointer.worldX - input.dragStartXGlobal;
var dy = pointer.worldY - input.dragStartYGlobal;
var rotation = gameObject.getParentRotation();
var dxRotated = dx * Math.cos(rotation) + dy * Math.sin(rotation);
var dyRotated = dy * Math.cos(rotation) - dx * Math.sin(rotation);
dxRotated *= (1 / gameObject.parentContainer.scaleX);
dyRotated *= (1 / gameObject.parentContainer.scaleY);
dragX = dxRotated + input.dragStartX;
dragY = dyRotated + input.dragStartY;
}
gameObject.emit(Events.GAMEOBJECT_DRAG, pointer, dragX, dragY);
this.emit(Events.DRAG, pointer, gameObject, dragX, dragY);
}
return list.length;
},
/**
* Processes a 'drag down' event for the given pointer. Checks the pointer state, builds-up the drag list
* and prepares them all for interaction.
*
* @method Phaser.Input.InputPlugin#processDragUpEvent
* @fires Phaser.Input.Events#DRAG_END
* @fires Phaser.Input.Events#DROP
* @fires Phaser.Input.Events#GAMEOBJECT_DRAG_END
* @fires Phaser.Input.Events#GAMEOBJECT_DROP
* @private
* @since 3.18.0
*
* @param {Phaser.Input.Pointer} pointer - The Pointer to process the drag event on.
*
* @return {integer} The number of items that were updated by this drag event.
*/
processDragUpEvent: function (pointer)
{
// 5 = Pointer was actively dragging but has been released, notify draglist
var list = this._drag[pointer.id];
for (var i = 0; i < list.length; i++)
{
var gameObject = list[i];
var input = gameObject.input;
if (input && input.dragState === 2)
{
input.dragState = 0;
input.dragX = input.localX - gameObject.displayOriginX;
input.dragY = input.localY - gameObject.displayOriginY;
var dropped = false;
var target = input.target;
if (target)
{
gameObject.emit(Events.GAMEOBJECT_DROP, pointer, target);
this.emit(Events.DROP, pointer, gameObject, target);
input.target = null;
dropped = true;
}
// And finally the dragend event
if (gameObject.input)
{
gameObject.emit(Events.GAMEOBJECT_DRAG_END, pointer, input.dragX, input.dragY, dropped);
this.emit(Events.DRAG_END, pointer, gameObject, dropped);
}
}
}
this.setDragState(pointer, 0);
list.splice(0);
return 0;
},
/**
* An internal method that handles the Pointer movement event.
*
* @method Phaser.Input.InputPlugin#processMoveEvents
* @private
* @fires Phaser.Input.Events#GAMEOBJECT_POINTER_MOVE
* @fires Phaser.Input.Events#GAMEOBJECT_MOVE
* @fires Phaser.Input.Events#POINTER_MOVE
* @since 3.0.0
*
* @param {Phaser.Input.Pointer} pointer - The pointer to check for events against.
*
* @return {integer} The total number of objects interacted with.
*/
processMoveEvents: function (pointer)
{
var total = 0;
var currentlyOver = this._temp;
var _eventData = this._eventData;
var _eventContainer = this._eventContainer;
_eventData.cancelled = false;
var aborted = false;
// Go through all objects the pointer was over and fire their events / callbacks
for (var i = 0; i < currentlyOver.length; i++)
{
var gameObject = currentlyOver[i];
if (!gameObject.input)
{
continue;
}
total++;
gameObject.emit(Events.GAMEOBJECT_POINTER_MOVE, pointer, gameObject.input.localX, gameObject.input.localY, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
this.emit(Events.GAMEOBJECT_MOVE, pointer, gameObject, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
if (this.topOnly)
{
break;
}
}
if (!aborted)
{
this.emit(Events.POINTER_MOVE, pointer, currentlyOver);
}
return total;
},
/**
* An internal method that handles a mouse wheel event.
*
* @method Phaser.Input.InputPlugin#processWheelEvent
* @private
* @fires Phaser.Input.Events#GAMEOBJECT_POINTER_WHEEL
* @fires Phaser.Input.Events#GAMEOBJECT_WHEEL
* @fires Phaser.Input.Events#POINTER_WHEEL
* @since 3.18.0
*
* @param {Phaser.Input.Pointer} pointer - The pointer to check for events against.
*
* @return {integer} The total number of objects interacted with.
*/
processWheelEvent: function (pointer)
{
var total = 0;
var currentlyOver = this._temp;
var _eventData = this._eventData;
var _eventContainer = this._eventContainer;
_eventData.cancelled = false;
var aborted = false;
var dx = pointer.deltaX;
var dy = pointer.deltaY;
var dz = pointer.deltaZ;
// Go through all objects the pointer was over and fire their events / callbacks
for (var i = 0; i < currentlyOver.length; i++)
{
var gameObject = currentlyOver[i];
if (!gameObject.input)
{
continue;
}
total++;
gameObject.emit(Events.GAMEOBJECT_POINTER_WHEEL, pointer, dx, dy, dz, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
this.emit(Events.GAMEOBJECT_WHEEL, pointer, gameObject, dx, dy, dz, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
}
if (!aborted)
{
this.emit(Events.POINTER_WHEEL, pointer, currentlyOver, dx, dy, dz);
}
return total;
},
/**
* An internal method that handles the Pointer over events.
* This is called when a touch input hits the canvas, having previously been off of it.
*
* @method Phaser.Input.InputPlugin#processOverEvents
* @private
* @fires Phaser.Input.Events#GAMEOBJECT_POINTER_OVER
* @fires Phaser.Input.Events#GAMEOBJECT_OVER
* @fires Phaser.Input.Events#POINTER_OVER
* @since 3.18.0
*
* @param {Phaser.Input.Pointer} pointer - The pointer to check for events against.
*
* @return {integer} The total number of objects interacted with.
*/
processOverEvents: function (pointer)
{
var currentlyOver = this._temp;
var totalInteracted = 0;
var total = currentlyOver.length;
var justOver = [];
if (total > 0)
{
var manager = this.manager;
var _eventData = this._eventData;
var _eventContainer = this._eventContainer;
_eventData.cancelled = false;
var aborted = false;
for (var i = 0; i < total; i++)
{
var gameObject = currentlyOver[i];
if (!gameObject.input)
{
continue;
}
justOver.push(gameObject);
manager.setCursor(gameObject.input);
gameObject.emit(Events.GAMEOBJECT_POINTER_OVER, pointer, gameObject.input.localX, gameObject.input.localY, _eventContainer);
totalInteracted++;
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
this.emit(Events.GAMEOBJECT_OVER, pointer, gameObject, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
}
if (!aborted)
{
this.emit(Events.POINTER_OVER, pointer, justOver);
}
}
// Then sort it into display list order
this._over[pointer.id] = justOver;
return totalInteracted;
},
/**
* An internal method that handles the Pointer out events.
* This is called when a touch input leaves the canvas, as it can never be 'over' in this case.
*
* @method Phaser.Input.InputPlugin#processOutEvents
* @private
* @fires Phaser.Input.Events#GAMEOBJECT_POINTER_OUT
* @fires Phaser.Input.Events#GAMEOBJECT_OUT
* @fires Phaser.Input.Events#POINTER_OUT
* @since 3.18.0
*
* @param {Phaser.Input.Pointer} pointer - The pointer to check for events against.
*
* @return {integer} The total number of objects interacted with.
*/
processOutEvents: function (pointer)
{
var previouslyOver = this._over[pointer.id];
var totalInteracted = 0;
var total = previouslyOver.length;
if (total > 0)
{
var manager = this.manager;
var _eventData = this._eventData;
var _eventContainer = this._eventContainer;
_eventData.cancelled = false;
var aborted = false;
this.sortGameObjects(previouslyOver);
for (var i = 0; i < total; i++)
{
var gameObject = previouslyOver[i];
// Call onOut for everything in the previouslyOver array
for (i = 0; i < total; i++)
{
gameObject = previouslyOver[i];
if (!gameObject.input)
{
continue;
}
manager.resetCursor(gameObject.input);
gameObject.emit(Events.GAMEOBJECT_POINTER_OUT, pointer, _eventContainer);
totalInteracted++;
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
this.emit(Events.GAMEOBJECT_OUT, pointer, gameObject, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
}
if (!aborted)
{
this.emit(Events.POINTER_OUT, pointer, previouslyOver);
}
}
this._over[pointer.id] = [];
}
return totalInteracted;
},
/**
* An internal method that handles the Pointer over and out events.
*
* @method Phaser.Input.InputPlugin#processOverOutEvents
* @private
* @fires Phaser.Input.Events#GAMEOBJECT_POINTER_OVER
* @fires Phaser.Input.Events#GAMEOBJECT_OVER
* @fires Phaser.Input.Events#POINTER_OVER
* @fires Phaser.Input.Events#GAMEOBJECT_POINTER_OUT
* @fires Phaser.Input.Events#GAMEOBJECT_OUT
* @fires Phaser.Input.Events#POINTER_OUT
* @since 3.0.0
*
* @param {Phaser.Input.Pointer} pointer - The pointer to check for events against.
*
* @return {integer} The total number of objects interacted with.
*/
processOverOutEvents: function (pointer)
{
var currentlyOver = this._temp;
var i;
var gameObject;
var justOut = [];
var justOver = [];
var stillOver = [];
var previouslyOver = this._over[pointer.id];
var currentlyDragging = this._drag[pointer.id];
var manager = this.manager;
// Go through all objects the pointer was previously over, and see if it still is.
// Splits the previouslyOver array into two parts: justOut and stillOver
for (i = 0; i < previouslyOver.length; i++)
{
gameObject = previouslyOver[i];
if (currentlyOver.indexOf(gameObject) === -1 && currentlyDragging.indexOf(gameObject) === -1)
{
// Not in the currentlyOver array, so must be outside of this object now
justOut.push(gameObject);
}
else
{
// In the currentlyOver array
stillOver.push(gameObject);
}
}
// Go through all objects the pointer is currently over (the hit test results)
// and if not in the previouslyOver array we know it's a new entry, so add to justOver
for (i = 0; i < currentlyOver.length; i++)
{
gameObject = currentlyOver[i];
// Is this newly over?
if (previouslyOver.indexOf(gameObject) === -1)
{
justOver.push(gameObject);
}
}
// By this point the arrays are filled, so now we can process what happened...
// Process the Just Out objects
var total = justOut.length;
var totalInteracted = 0;
var _eventData = this._eventData;
var _eventContainer = this._eventContainer;
_eventData.cancelled = false;
var aborted = false;
if (total > 0)
{
this.sortGameObjects(justOut);
// Call onOut for everything in the justOut array
for (i = 0; i < total; i++)
{
gameObject = justOut[i];
if (!gameObject.input)
{
continue;
}
// Reset cursor before we emit the event, in case they want to change it during the event
manager.resetCursor(gameObject.input);
gameObject.emit(Events.GAMEOBJECT_POINTER_OUT, pointer, _eventContainer);
totalInteracted++;
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
this.emit(Events.GAMEOBJECT_OUT, pointer, gameObject, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
}
if (!aborted)
{
this.emit(Events.POINTER_OUT, pointer, justOut);
}
}
// Process the Just Over objects
total = justOver.length;
_eventData.cancelled = false;
aborted = false;
if (total > 0)
{
this.sortGameObjects(justOver);
// Call onOver for everything in the justOver array
for (i = 0; i < total; i++)
{
gameObject = justOver[i];
if (!gameObject.input)
{
continue;
}
// Set cursor before we emit the event, in case they want to change it during the event
manager.setCursor(gameObject.input);
gameObject.emit(Events.GAMEOBJECT_POINTER_OVER, pointer, gameObject.input.localX, gameObject.input.localY, _eventContainer);
totalInteracted++;
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
this.emit(Events.GAMEOBJECT_OVER, pointer, gameObject, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
}
if (!aborted)
{
this.emit(Events.POINTER_OVER, pointer, justOver);
}
}
// Add the contents of justOver to the previously over array
previouslyOver = stillOver.concat(justOver);
// Then sort it into display list order
this._over[pointer.id] = this.sortGameObjects(previouslyOver);
return totalInteracted;
},
/**
* An internal method that handles the Pointer up events.
*
* @method Phaser.Input.InputPlugin#processUpEvents
* @private
* @fires Phaser.Input.Events#GAMEOBJECT_POINTER_UP
* @fires Phaser.Input.Events#GAMEOBJECT_UP
* @fires Phaser.Input.Events#POINTER_UP
* @fires Phaser.Input.Events#POINTER_UP_OUTSIDE
* @since 3.0.0
*
* @param {Phaser.Input.Pointer} pointer - The pointer to check for events against.
*
* @return {integer} The total number of objects interacted with.
*/
processUpEvents: function (pointer)
{
var currentlyOver = this._temp;
var _eventData = this._eventData;
var _eventContainer = this._eventContainer;
_eventData.cancelled = false;
var aborted = false;
// Go through all objects the pointer was over and fire their events / callbacks
for (var i = 0; i < currentlyOver.length; i++)
{
var gameObject = currentlyOver[i];
if (!gameObject.input)
{
continue;
}
gameObject.emit(Events.GAMEOBJECT_POINTER_UP, pointer, gameObject.input.localX, gameObject.input.localY, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
this.emit(Events.GAMEOBJECT_UP, pointer, gameObject, _eventContainer);
if (_eventData.cancelled || !gameObject.input)
{
aborted = true;
break;
}
}
// If they released outside the canvas, but pressed down inside it, we'll still dispatch the event.
if (!aborted && this.manager)
{
if (pointer.upElement === this.manager.game.canvas)
{
this.emit(Events.POINTER_UP, pointer, currentlyOver);
}
else
{
this.emit(Events.POINTER_UP_OUTSIDE, pointer);
}
}
return currentlyOver.length;
},
/**
* Queues a Game Object for insertion into this Input Plugin on the next update.
*
* @method Phaser.Input.InputPlugin#queueForInsertion
* @private
* @since 3.0.0
*
* @param {Phaser.GameObjects.GameObject} child - The Game Object to add.
*
* @return {this} This InputPlugin object.
*/
queueForInsertion: function (child)
{
if (this._pendingInsertion.indexOf(child) === -1 && this._list.indexOf(child) === -1)
{
this._pendingInsertion.push(child);
}
return this;
},
/**
* Queues a Game Object for removal from this Input Plugin on the next update.
*
* @method Phaser.Input.InputPlugin#queueForRemoval
* @private
* @since 3.0.0
*
* @param {Phaser.GameObjects.GameObject} child - The Game Object to remove.
*
* @return {this} This InputPlugin object.
*/
queueForRemoval: function (child)
{
this._pendingRemoval.push(child);
return this;
},
/**
* Sets the draggable state of the given array of Game Objects.
*
* They can either be set to be draggable, or can have their draggable state removed by passing `false`.
*
* A Game Object will not fire drag events unless it has been specifically enabled for drag.
*
* @method Phaser.Input.InputPlugin#setDraggable
* @since 3.0.0
*
* @param {(Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[])} gameObjects - An array of Game Objects to change the draggable state on.
* @param {boolean} [value=true] - Set to `true` if the Game Objects should be made draggable, `false` if they should be unset.
*
* @return {this} This InputPlugin object.
*/
setDraggable: function (gameObjects, value)
{
if (value === undefined) { value = true; }
if (!Array.isArray(gameObjects))
{
gameObjects = [ gameObjects ];
}
for (var i = 0; i < gameObjects.length; i++)
{
var gameObject = gameObjects[i];
gameObject.input.draggable = value;
var index = this._draggable.indexOf(gameObject);
if (value && index === -1)
{
this._draggable.push(gameObject);
}
else if (!value && index > -1)
{
this._draggable.splice(index, 1);
}
}
return this;
},
/**
* Creates a function that can be passed to `setInteractive`, `enable` or `setHitArea` that will handle
* pixel-perfect input detection on an Image or Sprite based Game Object, or any custom class that extends them.
*
* The following will create a sprite that is clickable on any pixel that has an alpha value >= 1.
*
* ```javascript
* this.add.sprite(x, y, key).setInteractive(this.input.makePixelPerfect());
* ```
*
* The following will create a sprite that is clickable on any pixel that has an alpha value >= 150.
*
* ```javascript
* this.add.sprite(x, y, key).setInteractive(this.input.makePixelPerfect(150));
* ```
*
* Once you have made an Interactive Object pixel perfect it impacts all input related events for it: down, up,
* dragstart, drag, etc.
*
* As a pointer interacts with the Game Object it will constantly poll the texture, extracting a single pixel from
* the given coordinates and checking its color values. This is an expensive process, so should only be enabled on
* Game Objects that really need it.
*
* You cannot make non-texture based Game Objects pixel perfect. So this will not work on Graphics, BitmapText,
* Render Textures, Text, Tilemaps, Containers or Particles.
*
* @method Phaser.Input.InputPlugin#makePixelPerfect
* @since 3.10.0
*
* @param {integer} [alphaTolerance=1] - The alpha level that the pixel should be above to be included as a successful interaction.
*
* @return {function} A Pixel Perfect Handler for use as a hitArea shape callback.
*/
makePixelPerfect: function (alphaTolerance)
{
if (alphaTolerance === undefined) { alphaTolerance = 1; }
var textureManager = this.systems.textures;
return CreatePixelPerfectHandler(textureManager, alphaTolerance);
},
/**
* Sets the hit area for the given array of Game Objects.
*
* A hit area is typically one of the geometric shapes Phaser provides, such as a `Phaser.Geom.Rectangle`
* or `Phaser.Geom.Circle`. However, it can be any object as long as it works with the provided callback.
*
* If no hit area is provided a Rectangle is created based on the size of the Game Object, if possible
* to calculate.
*
* The hit area callback is the function that takes an `x` and `y` coordinate and returns a boolean if
* those values fall within the area of the shape or not. All of the Phaser geometry objects provide this,
* such as `Phaser.Geom.Rectangle.Contains`.
*
* @method Phaser.Input.InputPlugin#setHitArea
* @since 3.0.0
*
* @param {(Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[])} gameObjects - An array of Game Objects to set the hit area on.
* @param {(Phaser.Types.Input.InputConfiguration|any)} [shape] - Either an input configuration object, or a geometric shape that defines the hit area for the Game Object. If not specified a Rectangle will be used.
* @param {Phaser.Types.Input.HitAreaCallback} [callback] - The 'contains' function to invoke to check if the pointer is within the hit area.
*
* @return {this} This InputPlugin object.
*/
setHitArea: function (gameObjects, shape, callback)
{
if (shape === undefined)
{
return this.setHitAreaFromTexture(gameObjects);
}
if (!Array.isArray(gameObjects))
{
gameObjects = [ gameObjects ];
}
var draggable = false;
var dropZone = false;
var cursor = false;
var useHandCursor = false;
var pixelPerfect = false;
var customHitArea = true;
// Config object?
if (IsPlainObject(shape))
{
var config = shape;
shape = GetFastValue(config, 'hitArea', null);
callback = GetFastValue(config, 'hitAreaCallback', null);
draggable = GetFastValue(config, 'draggable', false);
dropZone = GetFastValue(config, 'dropZone', false);
cursor = GetFastValue(config, 'cursor', false);
useHandCursor = GetFastValue(config, 'useHandCursor', false);
pixelPerfect = GetFastValue(config, 'pixelPerfect', false);
var alphaTolerance = GetFastValue(config, 'alphaTolerance', 1);
if (pixelPerfect)
{
shape = {};
callback = this.makePixelPerfect(alphaTolerance);
}
// Still no hitArea or callback?
if (!shape || !callback)
{
this.setHitAreaFromTexture(gameObjects);
customHitArea = false;
}
}
else if (typeof shape === 'function' && !callback)
{
callback = shape;
shape = {};
}
for (var i = 0; i < gameObjects.length; i++)
{
var gameObject = gameObjects[i];
if (pixelPerfect && gameObject.type === 'Container')
{
console.warn('Cannot pixelPerfect test a Container. Use a custom callback.');
continue;
}
var io = (!gameObject.input) ? CreateInteractiveObject(gameObject, shape, callback) : gameObject.input;
io.customHitArea = customHitArea;
io.dropZone = dropZone;
io.cursor = (useHandCursor) ? 'pointer' : cursor;
gameObject.input = io;
if (draggable)
{
this.setDraggable(gameObject);
}
this.queueForInsertion(gameObject);
}
return this;
},
/**
* Sets the hit area for an array of Game Objects to be a `Phaser.Geom.Circle` shape, using
* the given coordinates and radius to control its position and size.
*
* @method Phaser.Input.InputPlugin#setHitAreaCircle
* @since 3.0.0
*
* @param {(Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[])} gameObjects - An array of Game Objects to set as having a circle hit area.
* @param {number} x - The center of the circle.
* @param {number} y - The center of the circle.
* @param {number} radius - The radius of the circle.
* @param {Phaser.Types.Input.HitAreaCallback} [callback] - The hit area callback. If undefined it uses Circle.Contains.
*
* @return {this} This InputPlugin object.
*/
setHitAreaCircle: function (gameObjects, x, y, radius, callback)
{
if (callback === undefined) { callback = CircleContains; }
var shape = new Circle(x, y, radius);
return this.setHitArea(gameObjects, shape, callback);
},
/**
* Sets the hit area for an array of Game Objects to be a `Phaser.Geom.Ellipse` shape, using
* the given coordinates and dimensions to control its position and size.
*
* @method Phaser.Input.InputPlugin#setHitAreaEllipse
* @since 3.0.0
*
* @param {(Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[])} gameObjects - An array of Game Objects to set as having an ellipse hit area.
* @param {number} x - The center of the ellipse.
* @param {number} y - The center of the ellipse.
* @param {number} width - The width of the ellipse.
* @param {number} height - The height of the ellipse.
* @param {Phaser.Types.Input.HitAreaCallback} [callback] - The hit area callback. If undefined it uses Ellipse.Contains.
*
* @return {this} This InputPlugin object.
*/
setHitAreaEllipse: function (gameObjects, x, y, width, height, callback)
{
if (callback === undefined) { callback = EllipseContains; }
var shape = new Ellipse(x, y, width, height);
return this.setHitArea(gameObjects, shape, callback);
},
/**
* Sets the hit area for an array of Game Objects to be a `Phaser.Geom.Rectangle` shape, using
* the Game Objects texture frame to define the position and size of the hit area.
*
* @method Phaser.Input.InputPlugin#setHitAreaFromTexture
* @since 3.0.0
*
* @param {(Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[])} gameObjects - An array of Game Objects to set as having an ellipse hit area.
* @param {Phaser.Types.Input.HitAreaCallback} [callback] - The hit area callback. If undefined it uses Rectangle.Contains.
*
* @return {this} This InputPlugin object.
*/
setHitAreaFromTexture: function (gameObjects, callback)
{
if (callback === undefined) { callback = RectangleContains; }
if (!Array.isArray(gameObjects))
{
gameObjects = [ gameObjects ];
}
for (var i = 0; i < gameObjects.length; i++)
{
var gameObject = gameObjects[i];
var frame = gameObject.frame;
var width = 0;
var height = 0;
if (gameObject.width)
{
width = gameObject.width;
height = gameObject.height;
}
else if (frame)
{
width = frame.realWidth;
height = frame.realHeight;
}
if (gameObject.type === 'Container' && (width === 0 || height === 0))
{
console.warn('Container.setInteractive must specify a Shape or call setSize() first');
continue;
}
if (width !== 0 && height !== 0)
{
gameObject.input = CreateInteractiveObject(gameObject, new Rectangle(0, 0, width, height), callback);
this.queueForInsertion(gameObject);
}
}
return this;
},
/**
* Sets the hit area for an array of Game Objects to be a `Phaser.Geom.Rectangle` shape, using
* the given coordinates and dimensions to control its position and size.
*
* @method Phaser.Input.InputPlugin#setHitAreaRectangle
* @since 3.0.0
*
* @param {(Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[])} gameObjects - An array of Game Objects to set as having a rectangular hit area.
* @param {number} x - The top-left of the rectangle.
* @param {number} y - The top-left of the rectangle.
* @param {number} width - The width of the rectangle.
* @param {number} height - The height of the rectangle.
* @param {Phaser.Types.Input.HitAreaCallback} [callback] - The hit area callback. If undefined it uses Rectangle.Contains.
*
* @return {this} This InputPlugin object.
*/
setHitAreaRectangle: function (gameObjects, x, y, width, height, callback)
{
if (callback === undefined) { callback = RectangleContains; }
var shape = new Rectangle(x, y, width, height);
return this.setHitArea(gameObjects, shape, callback);
},
/**
* Sets the hit area for an array of Game Objects to be a `Phaser.Geom.Triangle` shape, using
* the given coordinates to control the position of its points.
*
* @method Phaser.Input.InputPlugin#setHitAreaTriangle
* @since 3.0.0
*
* @param {(Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[])} gameObjects - An array of Game Objects to set as having a triangular hit area.
* @param {number} x1 - The x coordinate of the first point of the triangle.
* @param {number} y1 - The y coordinate of the first point of the triangle.
* @param {number} x2 - The x coordinate of the second point of the triangle.
* @param {number} y2 - The y coordinate of the second point of the triangle.
* @param {number} x3 - The x coordinate of the third point of the triangle.
* @param {number} y3 - The y coordinate of the third point of the triangle.
* @param {Phaser.Types.Input.HitAreaCallback} [callback] - The hit area callback. If undefined it uses Triangle.Contains.
*
* @return {this} This InputPlugin object.
*/
setHitAreaTriangle: function (gameObjects, x1, y1, x2, y2, x3, y3, callback)
{
if (callback === undefined) { callback = TriangleContains; }
var shape = new Triangle(x1, y1, x2, y2, x3, y3);
return this.setHitArea(gameObjects, shape, callback);
},
/**
* Creates an Input Debug Shape for the given Game Object.
*
* The Game Object must have _already_ been enabled for input prior to calling this method.
*
* This is intended to assist you during development and debugging.
*
* Debug Shapes can only be created for Game Objects that are using standard Phaser Geometry for input,
* including: Circle, Ellipse, Line, Polygon, Rectangle and Triangle.
*
* Game Objects that are using their automatic hit areas are using Rectangles by default, so will also work.
*
* The Debug Shape is created and added to the display list and is then kept in sync with the Game Object
* it is connected with. Should you need to modify it yourself, such as to hide it, you can access it via
* the Game Object property: `GameObject.input.hitAreaDebug`.
*
* Calling this method on a Game Object that already has a Debug Shape will first destroy the old shape,
* before creating a new one. If you wish to remove the Debug Shape entirely, you should call the
* method `InputPlugin.removeDebug`.
*
* Note that the debug shape will only show the outline of the input area. If the input test is using a
* pixel perfect check, for example, then this is not displayed. If you are using a custom shape, that
* doesn't extend one of the base Phaser Geometry objects, as your hit area, then this method will not
* work.
*
* @method Phaser.Input.InputPlugin#enableDebug
* @since 3.19.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - The Game Object to create the input debug shape for.
* @param {number} [color=0x00ff00] - The outline color of the debug shape.
*
* @return {this} This Input Plugin.
*/
enableDebug: function (gameObject, color)
{
if (color === undefined) { color = 0x00ff00; }
var input = gameObject.input;
if (!input || !input.hitArea)
{
return this;
}
var shape = input.hitArea;
var shapeType = shape.type;
var debug = input.hitAreaDebug;
var factory = this.systems.add;
var updateList = this.systems.updateList;
if (debug)
{
updateList.remove(debug);
debug.destroy();
debug = null;
}
var offsetx = 0;
var offsety = 0;
switch (shapeType)
{
case GEOM_CONST.CIRCLE:
debug = factory.arc(0, 0, shape.radius);
offsetx = shape.x - shape.radius;
offsety = shape.y - shape.radius;
break;
case GEOM_CONST.ELLIPSE:
debug = factory.ellipse(0, 0, shape.width, shape.height);
offsetx = shape.x - shape.width / 2;
offsety = shape.y - shape.height / 2;
break;
case GEOM_CONST.LINE:
debug = factory.line(0, 0, shape.x1, shape.y1, shape.x2, shape.y2);
break;
case GEOM_CONST.POLYGON:
debug = factory.polygon(0, 0, shape.points);
break;
case GEOM_CONST.RECTANGLE:
debug = factory.rectangle(0, 0, shape.width, shape.height);
offsetx = shape.x;
offsety = shape.y;
break;
case GEOM_CONST.TRIANGLE:
debug = factory.triangle(0, 0, shape.x1, shape.y1, shape.x2, shape.y2, shape.x3, shape.y3);
break;
}
if (debug)
{
debug.isFilled = false;
debug.preUpdate = function ()
{
debug.setStrokeStyle(1 / gameObject.scale, color);
debug.setDisplayOrigin(gameObject.displayOriginX, gameObject.displayOriginY);
var x = gameObject.x;
var y = gameObject.y;
var rotation = gameObject.rotation;
var scaleX = gameObject.scaleX;
var scaleY = gameObject.scaleY;
if (gameObject.parentContainer)
{
var matrix = gameObject.getWorldTransformMatrix();
x = matrix.tx;
y = matrix.ty;
rotation = matrix.rotation;
scaleX = matrix.scaleX;
scaleY = matrix.scaleY;
}
debug.setRotation(rotation);
debug.setScale(scaleX, scaleY);
debug.setPosition(x + offsetx, y + offsety);
debug.setScrollFactor(gameObject.scrollFactorX, gameObject.scrollFactorY);
debug.setDepth(gameObject.depth);
};
updateList.add(debug);
input.hitAreaDebug = debug;
}
return this;
},
/**
* Removes an Input Debug Shape from the given Game Object.
*
* The shape is destroyed immediately and the `hitAreaDebug` property is set to `null`.
*
* @method Phaser.Input.InputPlugin#removeDebug
* @since 3.19.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - The Game Object to remove the input debug shape from.
*
* @return {this} This Input Plugin.
*/
removeDebug: function (gameObject)
{
var input = gameObject.input;
if (input && input.hitAreaDebug)
{
var debug = input.hitAreaDebug;
this.systems.updateList.remove(debug);
debug.destroy();
input.hitAreaDebug = null;
}
return this;
},
/**
* Sets the Pointers to always poll.
*
* When a pointer is polled it runs a hit test to see which Game Objects are currently below it,
* or being interacted with it, regardless if the Pointer has actually moved or not.
*
* You should enable this if you want objects in your game to fire over / out events, and the objects
* are constantly moving, but the pointer may not have. Polling every frame has additional computation
* costs, especially if there are a large number of interactive objects in your game.
*
* @method Phaser.Input.InputPlugin#setPollAlways
* @since 3.0.0
*
* @return {this} This InputPlugin object.
*/
setPollAlways: function ()
{
return this.setPollRate(0);
},
/**
* Sets the Pointers to only poll when they are moved or updated.
*
* When a pointer is polled it runs a hit test to see which Game Objects are currently below it,
* or being interacted with it.
*
* @method Phaser.Input.InputPlugin#setPollOnMove
* @since 3.0.0
*
* @return {this} This InputPlugin object.
*/
setPollOnMove: function ()
{
return this.setPollRate(-1);
},
/**
* Sets the poll rate value. This is the amount of time that should have elapsed before a pointer
* will be polled again. See the `setPollAlways` and `setPollOnMove` methods.
*
* @method Phaser.Input.InputPlugin#setPollRate
* @since 3.0.0
*
* @param {number} value - The amount of time, in ms, that should elapsed before re-polling the pointers.
*
* @return {this} This InputPlugin object.
*/
setPollRate: function (value)
{
this.pollRate = value;
this._pollTimer = 0;
return this;
},
/**
* When set to `true` the global Input Manager will emulate DOM behavior by only emitting events from
* the top-most Scene in the Scene List. By default, if a Scene receives an input event it will then stop the event
* from flowing down to any Scenes below it in the Scene list. To disable this behavior call this method with `false`.
*
* @method Phaser.Input.InputPlugin#setGlobalTopOnly
* @since 3.0.0
*
* @param {boolean} value - Set to `true` to stop processing input events on the Scene that receives it, or `false` to let the event continue down the Scene list.
*
* @return {this} This InputPlugin object.
*/
setGlobalTopOnly: function (value)
{
this.manager.globalTopOnly = value;
return this;
},
/**
* When set to `true` this Input Plugin will emulate DOM behavior by only emitting events from
* the top-most Game Objects in the Display List.
*
* If set to `false` it will emit events from all Game Objects below a Pointer, not just the top one.
*
* @method Phaser.Input.InputPlugin#setTopOnly
* @since 3.0.0
*
* @param {boolean} value - `true` to only include the top-most Game Object, or `false` to include all Game Objects in a hit test.
*
* @return {this} This InputPlugin object.
*/
setTopOnly: function (value)
{
this.topOnly = value;
return this;
},
/**
* Given an array of Game Objects, sort the array and return it, so that the objects are in depth index order
* with the lowest at the bottom.
*
* @method Phaser.Input.InputPlugin#sortGameObjects
* @since 3.0.0
*
* @param {Phaser.GameObjects.GameObject[]} gameObjects - An array of Game Objects to be sorted.
*
* @return {Phaser.GameObjects.GameObject[]} The sorted array of Game Objects.
*/
sortGameObjects: function (gameObjects)
{
if (gameObjects.length < 2)
{
return gameObjects;
}
this.scene.sys.depthSort();
return gameObjects.sort(this.sortHandlerGO.bind(this));
},
/**
* Return the child lowest down the display list (with the smallest index)
* Will iterate through all parent containers, if present.
*
* @method Phaser.Input.InputPlugin#sortHandlerGO
* @private
* @since 3.0.0
*
* @param {Phaser.GameObjects.GameObject} childA - The first Game Object to compare.
* @param {Phaser.GameObjects.GameObject} childB - The second Game Object to compare.
*
* @return {integer} Returns either a negative or positive integer, or zero if they match.
*/
sortHandlerGO: function (childA, childB)
{
if (!childA.parentContainer && !childB.parentContainer)
{
// Quick bail out when neither child has a container
return this.displayList.getIndex(childB) - this.displayList.getIndex(childA);
}
else if (childA.parentContainer === childB.parentContainer)
{
// Quick bail out when both children have the same container
return childB.parentContainer.getIndex(childB) - childA.parentContainer.getIndex(childA);
}
else if (childA.parentContainer === childB)
{
// Quick bail out when childA is a child of childB
return -1;
}
else if (childB.parentContainer === childA)
{
// Quick bail out when childA is a child of childB
return 1;
}
else
{
// Container index check
var listA = childA.getIndexList();
var listB = childB.getIndexList();
var len = Math.min(listA.length, listB.length);
for (var i = 0; i < len; i++)
{
var indexA = listA[i];
var indexB = listB[i];
if (indexA === indexB)
{
// Go to the next level down
continue;
}
else
{
// Non-matching parents, so return
return indexB - indexA;
}
}
return listB.length - listA.length;
}
// Technically this shouldn't happen, but ...
// eslint-disable-next-line no-unreachable
return 0;
},
/**
* This method should be called from within an input event handler, such as `pointerdown`.
*
* When called, it stops the Input Manager from allowing _this specific event_ to be processed by any other Scene
* not yet handled in the scene list.
*
* @method Phaser.Input.InputPlugin#stopPropagation
* @since 3.0.0
*
* @return {this} This InputPlugin object.
*/
stopPropagation: function ()
{
this.manager._tempSkip = true;
return this;
},
/**
* Adds new Pointer objects to the Input Manager.
*
* By default Phaser creates 2 pointer objects: `mousePointer` and `pointer1`.
*
* You can create more either by calling this method, or by setting the `input.activePointers` property
* in the Game Config, up to a maximum of 10 pointers.
*
* The first 10 pointers are available via the `InputPlugin.pointerX` properties, once they have been added
* via this method.
*
* @method Phaser.Input.InputPlugin#addPointer
* @since 3.10.0
*
* @param {integer} [quantity=1] The number of new Pointers to create. A maximum of 10 is allowed in total.
*
* @return {Phaser.Input.Pointer[]} An array containing all of the new Pointer objects that were created.
*/
addPointer: function (quantity)
{
return this.manager.addPointer(quantity);
},
/**
* Tells the Input system to set a custom cursor.
*
* This cursor will be the default cursor used when interacting with the game canvas.
*
* If an Interactive Object also sets a custom cursor, this is the cursor that is reset after its use.
*
* Any valid CSS cursor value is allowed, including paths to image files, i.e.:
*
* ```javascript
* this.input.setDefaultCursor('url(assets/cursors/sword.cur), pointer');
* ```
*
* Please read about the differences between browsers when it comes to the file formats and sizes they support:
*
* https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_User_Interface/Using_URL_values_for_the_cursor_property
*
* It's up to you to pick a suitable cursor format that works across the range of browsers you need to support.
*
* @method Phaser.Input.InputPlugin#setDefaultCursor
* @since 3.10.0
*
* @param {string} cursor - The CSS to be used when setting the default cursor.
*
* @return {this} This Input instance.
*/
setDefaultCursor: function (cursor)
{
this.manager.setDefaultCursor(cursor);
return this;
},
/**
* The Scene that owns this plugin is transitioning in.
*
* @method Phaser.Input.InputPlugin#transitionIn
* @private
* @since 3.5.0
*/
transitionIn: function ()
{
this.enabled = this.settings.transitionAllowInput;
},
/**
* The Scene that owns this plugin has finished transitioning in.
*
* @method Phaser.Input.InputPlugin#transitionComplete
* @private
* @since 3.5.0
*/
transitionComplete: function ()
{
if (!this.settings.transitionAllowInput)
{
this.enabled = true;
}
},
/**
* The Scene that owns this plugin is transitioning out.
*
* @method Phaser.Input.InputPlugin#transitionOut
* @private
* @since 3.5.0
*/
transitionOut: function ()
{
this.enabled = this.settings.transitionAllowInput;
},
/**
* The Scene that owns this plugin is shutting down.
* We need to kill and reset all internal properties as well as stop listening to Scene events.
*
* @method Phaser.Input.InputPlugin#shutdown
* @fires Phaser.Input.Events#SHUTDOWN
* @private
* @since 3.0.0
*/
shutdown: function ()
{
// Registered input plugins listen for this
this.pluginEvents.emit(Events.SHUTDOWN);
this._temp.length = 0;
this._list.length = 0;
this._draggable.length = 0;
this._pendingRemoval.length = 0;
this._pendingInsertion.length = 0;
this._dragState.length = 0;
for (var i = 0; i < 10; i++)
{
this._drag[i] = [];
this._over[i] = [];
}
this.removeAllListeners();
var eventEmitter = this.systems.events;
eventEmitter.off(SceneEvents.TRANSITION_START, this.transitionIn, this);
eventEmitter.off(SceneEvents.TRANSITION_OUT, this.transitionOut, this);
eventEmitter.off(SceneEvents.TRANSITION_COMPLETE, this.transitionComplete, this);
eventEmitter.off(SceneEvents.PRE_UPDATE, this.preUpdate, this);
this.manager.events.off(Events.GAME_OUT, this.onGameOut, this);
this.manager.events.off(Events.GAME_OVER, this.onGameOver, this);
eventEmitter.off(SceneEvents.SHUTDOWN, this.shutdown, this);
},
/**
* The Scene that owns this plugin is being destroyed.
* We need to shutdown and then kill off all external references.
*
* @method Phaser.Input.InputPlugin#destroy
* @fires Phaser.Input.Events#DESTROY
* @private
* @since 3.0.0
*/
destroy: function ()
{
this.shutdown();
// Registered input plugins listen for this
this.pluginEvents.emit(Events.DESTROY);
this.pluginEvents.removeAllListeners();
this.scene.sys.events.off(SceneEvents.START, this.start, this);
this.scene = null;
this.cameras = null;
this.manager = null;
this.events = null;
this.mouse = null;
},
/**
* The x coordinates of the ActivePointer based on the first camera in the camera list.
* This is only safe to use if your game has just 1 non-transformed camera and doesn't use multi-touch.
*
* @name Phaser.Input.InputPlugin#x
* @type {number}
* @readonly
* @since 3.0.0
*/
x: {
get: function ()
{
return this.manager.activePointer.x;
}
},
/**
* The y coordinates of the ActivePointer based on the first camera in the camera list.
* This is only safe to use if your game has just 1 non-transformed camera and doesn't use multi-touch.
*
* @name Phaser.Input.InputPlugin#y
* @type {number}
* @readonly
* @since 3.0.0
*/
y: {
get: function ()
{
return this.manager.activePointer.y;
}
},
/**
* Are any mouse or touch pointers currently over the game canvas?
*
* @name Phaser.Input.InputPlugin#isOver
* @type {boolean}
* @readonly
* @since 3.16.0
*/
isOver: {
get: function ()
{
return this.manager.isOver;
}
},
/**
* The mouse has its own unique Pointer object, which you can reference directly if making a _desktop specific game_.
* If you are supporting both desktop and touch devices then do not use this property, instead use `activePointer`
* which will always map to the most recently interacted pointer.
*
* @name Phaser.Input.InputPlugin#mousePointer
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
mousePointer: {
get: function ()
{
return this.manager.mousePointer;
}
},
/**
* The current active input Pointer.
*
* @name Phaser.Input.InputPlugin#activePointer
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.0.0
*/
activePointer: {
get: function ()
{
return this.manager.activePointer;
}
},
/**
* A touch-based Pointer object.
* This will be `undefined` by default unless you add a new Pointer using `addPointer`.
*
* @name Phaser.Input.InputPlugin#pointer1
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
pointer1: {
get: function ()
{
return this.manager.pointers[1];
}
},
/**
* A touch-based Pointer object.
* This will be `undefined` by default unless you add a new Pointer using `addPointer`.
*
* @name Phaser.Input.InputPlugin#pointer2
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
pointer2: {
get: function ()
{
return this.manager.pointers[2];
}
},
/**
* A touch-based Pointer object.
* This will be `undefined` by default unless you add a new Pointer using `addPointer`.
*
* @name Phaser.Input.InputPlugin#pointer3
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
pointer3: {
get: function ()
{
return this.manager.pointers[3];
}
},
/**
* A touch-based Pointer object.
* This will be `undefined` by default unless you add a new Pointer using `addPointer`.
*
* @name Phaser.Input.InputPlugin#pointer4
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
pointer4: {
get: function ()
{
return this.manager.pointers[4];
}
},
/**
* A touch-based Pointer object.
* This will be `undefined` by default unless you add a new Pointer using `addPointer`.
*
* @name Phaser.Input.InputPlugin#pointer5
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
pointer5: {
get: function ()
{
return this.manager.pointers[5];
}
},
/**
* A touch-based Pointer object.
* This will be `undefined` by default unless you add a new Pointer using `addPointer`.
*
* @name Phaser.Input.InputPlugin#pointer6
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
pointer6: {
get: function ()
{
return this.manager.pointers[6];
}
},
/**
* A touch-based Pointer object.
* This will be `undefined` by default unless you add a new Pointer using `addPointer`.
*
* @name Phaser.Input.InputPlugin#pointer7
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
pointer7: {
get: function ()
{
return this.manager.pointers[7];
}
},
/**
* A touch-based Pointer object.
* This will be `undefined` by default unless you add a new Pointer using `addPointer`.
*
* @name Phaser.Input.InputPlugin#pointer8
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
pointer8: {
get: function ()
{
return this.manager.pointers[8];
}
},
/**
* A touch-based Pointer object.
* This will be `undefined` by default unless you add a new Pointer using `addPointer`.
*
* @name Phaser.Input.InputPlugin#pointer9
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
pointer9: {
get: function ()
{
return this.manager.pointers[9];
}
},
/**
* A touch-based Pointer object.
* This will be `undefined` by default unless you add a new Pointer using `addPointer`.
*
* @name Phaser.Input.InputPlugin#pointer10
* @type {Phaser.Input.Pointer}
* @readonly
* @since 3.10.0
*/
pointer10: {
get: function ()
{
return this.manager.pointers[10];
}
}
});
PluginCache.register('InputPlugin', InputPlugin, 'input');
module.exports = InputPlugin;