API Docs for: 1.0.0
Show:

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 || this.owner.willRender === 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;

        }

    }

}