File: src\components\Input.ts
/**
*
* @module Kiwi
* @submodule Components
*
*/
module Kiwi.Components {
/**
* The Input Component is used on GameObjects in which the user may interactive with via a Mouse or Touch
* and as such this class contains useful methods and callbacks you can subscribe to.
* By default the Input component is disabled (this is because when enabled the input component can be process intensive)
* but you can enabled it yourself (which is recommened) BUT in case you forget the input component will automagically
* be enabled once you access a Signal on this class.
*
* @class Input
* @extends Kiwi.Component
* @namespace Kiwi.Components
* @constructor
* @param owner {Object} The Object that this Component is on. Generally this will be a Entity.
* @param box {Kiwi.Components.Box} The box which contains the worldHitbox that is to be used for the event firing.
* @param [enabled=false] {boolean} If this input component should be enabled or not.
* @return {Kiwi.Components.Input}
*/
export class Input extends Component {
constructor(owner: Kiwi.IChild, box:Kiwi.Components.Box, enabled:boolean=false) {
super(owner,'Input');
// Signals
this._onEntered = new Kiwi.Signal();
this._onLeft = new Kiwi.Signal();
this._onDown = new Kiwi.Signal();
this._onUp = new Kiwi.Signal();
this._onDragStarted = new Kiwi.Signal();
this._onDragStopped = new Kiwi.Signal();
// Properties
this._box = box;
this._distance = new Kiwi.Geom.Point();
this._withinBounds = null;
this._outsideBounds = true;
this._isUp = true;
this._isDown = null;
this._isDragging = null;
this._justEntered = false;
this._tempDragDisabled = false;
this._tempPoint = new Kiwi.Geom.Point();
this._tempCircle = new Kiwi.Geom.Circle(0, 0, 0);
this.enabled = enabled;
}
/**
* The type of object this input is.
* @method objType
* @return {string} "Input"
* @public
*/
public objType() {
return "Input";
}
/**
* The bounding box that is being used for the 'hitarea'.
* @property _box
* @type Kiwi.Components.Box
* @private
*/
private _box: Kiwi.Components.Box;
/**
* Kiwi Signal for firing callbacks when a pointer is active and has entered the entities hitbox.
* @property _onEntered
* @type Kiwi.Signal
* @private
*/
private _onEntered: Kiwi.Signal;
/**
* Kiwi Signal for firing callbacks when a pointer is active and has left the entities hit box.
* @property _onLeft
* @type Kiwi.Signal
* @private
*/
private _onLeft: Kiwi.Signal;
/**
* Kiwi Signal for firing callbacks when a pointer is active and has pressed down on the entity.
* @property _onDown
* @type Kiwi.Signal
* @private
*/
private _onDown: Kiwi.Signal;
/**
* Kiwi Signal for firing callbacks when a pointer just released from either being above the entity or the pointer was initally pressed on it.
* @property _onUp
* @type Kiwi.Signal
* @private
*/
private _onUp: Kiwi.Signal;
/**
* Kiwi Signal for firing callbacks a entity starts being dragged.
* @property _onDragStarted
* @type Kiwi.Signal
* @private
*/
private _onDragStarted: Kiwi.Signal;
/**
* Kiwi Signal for firing callbacks a entity stops being dragged. Like on release.
* @property _onDragStopped
* @type Kiwi.Signal
* @private
*/
private _onDragStopped: Kiwi.Signal;
/**
* A Temporary Point object which is used whilst checking to see if there is any overlap.
* @property _tempPoint
* @type Kiwi.Geom.Point
* @private
*/
private _tempPoint: Kiwi.Geom.Point;
/**
* A Temporary Circle object which is used whilst checking to see if there is any overlap.
* @property _tempCircle
* @type Kiwi.Geom.Circle
* @private
*/
private _tempCircle: Kiwi.Geom.Circle;
/**
* Returns the onEntered Signal, that fires events when a pointer enters the hitbox of a entity.
* Note: Accessing this signal enables the input.
* This is READ ONLY.
* @property onEntered
* @type Kiwi.Signal
* @public
*/
public get onEntered(): Kiwi.Signal {
if (this.enabled == false) this.enabled = true;
return this._onEntered;
}
/**
* Returns the onLeft Signal, that fires events when a pointer leaves the hitbox of a entity.
* Note: Accessing this signal enables the input.
* This is READ ONLY.
* @property onLeft
* @type Kiwi.Signal
* @public
*/
public get onLeft(): Kiwi.Signal {
if (this.enabled == false) this.enabled = true;
return this._onLeft;
}
/**
* Returns the onDown Signal, that fires events when a pointer is pressed within the bounds of the signal.
* Note: Accessing this signal enables the input.
* This is READ ONLY.
* @property onDown
* @type Kiwi.Signal
* @public
*/
public get onDown(): Kiwi.Signal {
if (this.enabled == false) this.enabled = true;
return this._onDown;
}
/**
* Returns the onUp Signal, that fires events when a pointer is released either within the bounds or was pressed initially within the bounds..
* Note: Accessing this signal enables the input.
* This is READ ONLY.
* @property onUp
* @type Kiwi.Signal
* @public
*/
public get onUp(): Kiwi.Signal {
if (this.enabled == false) this.enabled = true;
return this._onUp;
}
/**
* Returns the onDragStarted Signal.
* This is READ ONLY.
* @property onDragStarted
* @type Kiwi.Signal
* @public
*/
public get onDragStarted(): Kiwi.Signal { return this._onDragStarted; }
/**
* Returns the onDragStopped Signal.
* This is READ ONLY.
* @property onDragStopped
* @type Kiwi.Signal
* @public
*/
public get onDragStopped(): Kiwi.Signal { return this._onDragStopped; }
/**
* A alias for the on release signal.
* This is READ ONLY.
* @property onRelease
* @type Kiwi.Signal
* @public
*/
public get onRelease():Kiwi.Signal {
return this.onUp;
}
/**
* A alias for the on press signal.
* This is READ ONLY.
* @property onPress
* @type Kiwi.Signal
* @public
*/
public get onPress(): Kiwi.Signal {
return this.onDown;
}
/**
* If this input is enabled or not.
* @property _enabled
* @type boolean
* @default false
* @private
*/
private _enabled: boolean;
/**
* Get if the input is enabled or not. Note: Inputs should only be enabled when needed, otherwise unnecessary processing does occur which can result in a slower game.
* @property enabled
* @type boolean
* @public
*/
public get enabled(): boolean {
return this._enabled;
}
public set enabled(val: boolean) {//perhaps later the signals should only be set if the input is enabled.
this._enabled = val;
}
/**
* If a pointer is current pressing down on the input, this will be a reference to that pointer. Otherwise it will be null.
* @property _isDown
* @type Kiwi.Input.Pointer
* @private
*/
private _isDown: Kiwi.Input.Pointer;
/**
* A boolean that indicates if no pointer is currently on the pointer
* @property _isUp
* @type boolean
* @default true
* @private
*/
private _isUp: boolean;
/**
* Indicates if a pointer is within the bounds or not. If one is then it referers to the pointer that is. Other it will be null.
* @property _withinBounds
* @type Kiwi.Input.Pointer
* @private
*/
private _withinBounds: Kiwi.Input.Pointer;
/**
* boolean indicating if every pointer is currently outside of the bounds.
* @property _outsideBounds
* @type boolean
* @default true
* @private
*/
private _outsideBounds: boolean;
/**
* If a pointer just entered the input. Used for mouse's to indicate when to appropriately fire the down event.
* Note: Could be removed once mouse version of update gets updated.
* @property _justEntered
* @type boolean
* @default false
* @private
*/
private _justEntered: boolean;
/**
* Used to see if a pointer is currently on this input. Returns a boolean indicating either true or false.
* This is READ ONLY.
* @property isDown
* @type boolean
* @public
*/
public get isDown(): boolean {
return (this._isDown !== null);
}
/**
* Used to see if no pointer is on this input (so it is up).
* This is READ ONLY.
* @property isUp
* @type boolean
* @public
*/
public get isUp(): boolean {
return this._isUp;
}
/**
* Check to see if any pointer is within the bounds of this input.
* This is READ ONLY.
* @property withinBounds
* @type boolean
* @public
*/
public get withinBounds(): boolean {
return (this._withinBounds !== null);
}
/**
* See if no pointers are within the bounds of this entity.
* This is READ ONLY.
* @property outsideBounds
* @type boolean
* @public
*/
public get outsideBounds(): boolean {
return this._outsideBounds;
}
/**
* A reference to the pointer that is currently 'dragging' this Object.
* If not dragging then this is null.
* @property _isDragging
* @type Kiwi.Input.Pointer
* @default null
* @private
*/
private _isDragging: Kiwi.Input.Pointer = null;
/**
* The distance between the top left corner of this Objects parent and the coordinates of a Pointer.
* @property _distance
* @type Kiwi.Geom.Point
* @private
*/
private _distance: Kiwi.Geom.Point;
/**
* A boolean indicating if dragging is temporarly disabled. Internal use only to stop events from misfiring.
* @property _tempDragDisabled
* @type boolean
* @private
*/
private _tempDragDisabled: boolean;
/**
* Indicates if dragging is currently enabled.
* @property _dragEnabled
* @type boolean
* @default false
* @private
*/
private _dragEnabled: boolean = false;
/**
* This is used while dragging so that you can make the IChild 'snap' to specific numbers to give a 'grid like' effect.
* E.g. If you had a 32 by 32 grid down and you wanted to make an element draggable but snap to the grid you can set this to 32.
* Default value is one.
* @property _dragDistance
* @type number
* @default 1
* @private
*/
private _dragDistance: number;
/**
* If when dragging, the IChild should snap to the center of the pointer it is being dragged by.
* @property _dragSnapToCenter
* @type boolean
* @default false
* @private
*/
private _dragSnapToCenter: boolean = false;
/**
* Returns a boolean indicating if this is currently dragging something.
* This is READ ONLY.
* @property isDragging
* @type boolean
* @public
*/
public get isDragging(): boolean { return (this._isDragging !== null); }
/**
* The drag distance that is used when dragging this object. See _dragDistance for more information.
* @property dragDistance
* @type number
* @public
*/
public get dragDistance(): number {
return this._dragDistance;
}
public set dragDistance(val: number) {
this._dragDistance = val;
}
/**
* Temporary property that gets updated everyframe with the pointer that is currently 'down' on this entity.
* @property _nowDown
* @type Kiwi.Input.Pointer
* @default null
* @private
*/
private _nowDown: Kiwi.Input.Pointer = null;
/**
* Temporary property that gets updated everyframe with the pointer that was just 'released' from being down on this entity
* @property _nowUp
* @type Kiwi.Input.Pointer
* @default null
* @private
*/
private _nowUp: Kiwi.Input.Pointer = null;
/**
* Temporary property of the pointer that is now within the bounds of the entity
* @property _nowEntered
* @type Kiwi.Input.Pointer
* @default null
* @private
*/
private _nowEntered: Kiwi.Input.Pointer = null;
/**
* Temporary property of the pointer that just left the bounds of the entity.
* @property _nowLeft
* @type Kiwi.Input.Pointer
* @default null
* @private
*/
private _nowLeft: Kiwi.Input.Pointer = null;
/**
* Temporary property of the pointer that just started draggging the entity.
* @property _nowDragging
* @type Kiwi.Input.Pointer
* @default null
* @private
*/
private _nowDragging: Kiwi.Input.Pointer = null;
/**
* Enables the dragging of this entity.
* @method enableDrag
* @param [snapToCenter=false] {boolean} If when dragging the Entity should snap to the center of the pointer.
* @param [distance=1] {number} If when dragging the Entity should snap to numbers divisible by this amount.
* @public
*/
public enableDrag(snapToCenter:boolean = false, distance:number = 1) {
if (this.enabled == false) this.enabled = true;
this._dragEnabled = true;
this._dragSnapToCenter = snapToCenter;
this._dragDistance = distance;
this._isDragging = null;
}
/**
* Disables the dragging of this entity.
* @method disableDrag
* @public
*/
public disableDrag() {
this._dragEnabled = false;
this._isDragging = null;
}
/**
* The update loop for the input.
* @method update
* @protected
*/
public update() {
if (this.enabled === false || !this.game || this.owner.active === false) {
return;
}
// Reset the temporary properties
this._nowDown = null;
this._nowUp = null;
this._nowEntered = null;
this._nowLeft = null;
this._nowDragging = null;
//Use the appropriate method of checking.
if (Kiwi.DEVICE.touch) {
this._updateTouch();
} else {
this._updateMouse();
}
//If the entity is dragging.
if (this.isDragging) {
this._tempPoint = this.game.cameras.defaultCamera.transformPoint(this._isDragging.point);
if (this._dragSnapToCenter === false) {
this.owner.transform.x = Kiwi.Utils.GameMath.snapTo((this._tempPoint.x - this._box.hitboxOffset.x - this._distance.x), this._dragDistance);
this.owner.transform.y = Kiwi.Utils.GameMath.snapTo((this._tempPoint.y - this._box.hitboxOffset.y - this._distance.y), this._dragDistance);
} else {
this.owner.transform.x = Kiwi.Utils.GameMath.snapTo((this._tempPoint.x - this._box.hitboxOffset.x - this._box.worldHitbox.width / 2), this._dragDistance);
this.owner.transform.y = Kiwi.Utils.GameMath.snapTo((this._tempPoint.y - this._box.hitboxOffset.y - this._box.worldHitbox.height / 2), this._dragDistance);
}
}
}
/**
* The update loop that gets executed when the game is using the touch manager.
* @method _updateTouch
* @private
*/
private _updateTouch() {
for (var i = 0; i < this.game.input.touch.maximumPointers; i++) {
//if that pointer is active then see where it is
if (this.game.input.touch.fingers[i].active === true) {
this._evaluateTouchPointer(this.game.input.touch.fingers[i]);
}
//if the pointer is inactive check to see if it was just down
else if (this.isDown === true && this._isDown.id === this.game.input.touch.fingers[i].id) {
this._nowUp = this.game.input.touch.fingers[i];
}
//if the pointer is not active but was within the bounds check to see if it is now outside
else if (this.isDown === false && this._nowUp === null && this.withinBounds === true && this._withinBounds.id === this.game.input.touch.fingers[i].id) {
this._nowUp = this.game.input.touch.fingers[i];
}
}
//Fire the events. LOTS OF CONDITIONS
if (this._nowEntered !== null && this.withinBounds === false) {
this._withinBounds = this._nowEntered;
this._outsideBounds = false;
this._onEntered.dispatch(this.owner, this._nowEntered);
}
if (this._nowLeft !== null && this.withinBounds === true) {
this._withinBounds = null;
this._outsideBounds = true;
this._onLeft.dispatch(this.owner, this._nowLeft);
}
if (this._nowDown !== null && this.isDown === false) {
this._onDown.dispatch(this.owner, this._nowDown);
this._isDown = this._nowDown;
this._isUp = false;
this._withinBounds = this._nowDown;
this._outsideBounds = false;
}
if (this._dragEnabled == true && this.isDragging === false && this._nowDragging !== null) {
this._onDragStarted.dispatch(this.owner, this._nowDragging);
this._isDragging = this._nowDragging;
}
if (this._nowUp !== null) {
this._onUp.dispatch(this.owner, this._nowUp);
this._isDown = null;
this._isUp = true;
this._withinBounds = null;
this._outsideBounds = true;
//dispatch drag event
if (this.isDragging === true && this._isDragging.id == this._nowUp.id) {
this._isDragging = null;
this._onDragStopped.dispatch(this.owner, this._nowUp);
}
}
}
/**
* A private method for checking to see if a touch pointer should activate any events.
* @method _evaluateTouchPointer
* @param pointer {Kiwi.Input.Finger} The pointer you are checking against.
* @private
*/
private _evaluateTouchPointer(pointer:Kiwi.Input.Finger) {
//if nothing isdown or what is down is the current pointer
if (this.isDown === false || this._isDown.id === pointer.id) {
this._tempPoint = this.game.cameras.defaultCamera.transformPoint( pointer.point );
this._tempCircle.setTo(this._tempPoint.x, this._tempPoint.y, pointer.circle.diameter);
if (Kiwi.Geom.Intersect.circleToRectangle(this._tempCircle, this._box.worldHitbox).result) {
if (this.isDown === true && this._isDown.id === pointer.id || this.isDown === false && pointer.duration > 1) {
this._nowEntered = pointer;
}
if (this.isDown === false && pointer.frameDuration < 2) {
this._nowDown = pointer;
}
if (this._dragEnabled && this.isDragging == false && this.isDown == true) {
this._distance.x = this._tempPoint.x - this._box.worldHitbox.left;
this._distance.y = this._tempPoint.y - this._box.worldHitbox.top;
this._nowDragging = pointer;
}
} else {
if (this.isDown === true) {
this._nowLeft = pointer;
} else if(this.withinBounds === true && this._withinBounds.id == pointer.id) {
this._nowLeft = pointer;
}
}
}
}
/**
* The update loop that runs when the mouse manager is the method for interacting with the screen.
* @method _updateMouse
* @private
*/
private _updateMouse() {
this._evaluateMousePointer(this.game.input.mouse.cursor);
//dispatch the events
if (this._nowLeft !== null) {
this._onLeft.dispatch(this.owner, this._nowLeft);
}
if (this._nowEntered !== null) {
this._onEntered.dispatch(this.owner, this._nowEntered);
}
if (this._nowDown !== null && this.isDown === false) {
this._onDown.dispatch(this.owner, this._nowDown);
this._isDown = this._nowDown;
this._isUp = false;
}
if (this._dragEnabled == true && this.isDragging === false && this._nowDragging !== null) {
this._onDragStarted.dispatch(this.owner, this._nowDragging);
this._isDragging = this._nowDragging;
}
if (this.isDown === true && this._nowUp !== null && this._isDown.id === this._nowUp.id) {
this._onUp.dispatch(this.owner, this._nowUp);
// Dispatch drag event
if (this.isDragging === true && this._isDragging.id == this._nowUp.id) {
this._isDragging = null;
this._onDragStopped.dispatch(this.owner, this._nowUp);
}
this._isDown = null;
this._isUp = true;
}
}
/**
* Evaluates where and what the mouse cursor is doing in relation to this box. Needs a little bit more love.
* @method _evaluateMousePointer
* @param pointer {Kiwi.Input.MouseCursor}
* @private
*/
private _evaluateMousePointer(pointer:Kiwi.Input.MouseCursor) {
this._tempPoint = this.game.cameras.defaultCamera.transformPoint(pointer.point);
if (Kiwi.Geom.Intersect.pointToRectangle(this._tempPoint, this._box.worldHitbox).result) {
if (this._dragEnabled && this.isDragging === false) {
this._distance.x = this._tempPoint.x - this._box.worldHitbox.left;
this._distance.y = this._tempPoint.y - this._box.worldHitbox.top;
}
// Has it just moved inside?
if (this.withinBounds === false) {
this._nowEntered = pointer;
this._withinBounds = pointer;
this._outsideBounds = false;
this._justEntered = true;
}
} else {
// It's outside the bounds now, was it previously in?
if (this.withinBounds === true && this.isDragging === false) {
this._nowLeft = pointer;
this._withinBounds = null;
this._outsideBounds = true;
}
}
// Input is down (click/touch)
if (pointer.isDown === true) {
//if is was a mouse, did it just enter?
if (this._justEntered) {
this._isDown = pointer;
this._isUp = false;
this._tempDragDisabled = true;
}
// Within bounds?
if (this.withinBounds === true && this.isDown === false && this._nowDown === null) {
this._nowDown = pointer;
}
if (this._dragEnabled === true && this.isDragging == false && this._tempDragDisabled === false) {
if(this.isDown == true) {
this._nowDragging = pointer;
}
}
} else {
if (this._tempDragDisabled === true) this._tempDragDisabled = false;
if (this.isDown === true) {
this._nowUp = pointer;
}
}
if (this._justEntered) this._justEntered = false;
}
/**
* Destroys the input.
* @method destory
* @public
*/
public destroy() {
super.destroy();
this.enabled = false;
delete this._box;
delete this._isDown;
delete this._isUp;
delete this._isDragging;
delete this._dragEnabled;
if(this._onDown) this._onDown.dispose();
delete this._onDown;
if(this._onDragStarted) this._onDragStarted.dispose();
delete this._onDragStarted;
if (this._onUp) this._onUp.dispose();
delete this._onUp;
if (this._onLeft) this._onLeft.dispose();
delete this._onLeft;
if (this._onEntered) this._onEntered.dispose();
delete this._onEntered;
if (this._onDragStopped) this._onDragStopped.dispose();
delete this._onDragStopped;
delete this._dragDistance;
}
}
}