Source: interaction/KeyboardManager.js

var EventEmitter = require('eventemitter3');

/**
 * The keyboard manager deals with key events. Any DisplayObject can be interactive
 * if its interactive parameter is set to true (similar to the InteractionManager
 * in PIXI)
 *
 * @class
 * @extends EventEmitter
 * @memberof GOWN.interaction
 * @param renderer A reference to the current renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer}
 * @param [options] {object}
 * @param [options.autoPreventDefault=false] {boolean} Should the manager automatically prevent default browser actions.
 */
// TODO (maybe): move this to an own external lib for PIXI-Keyboard interaction
// TODO: show keyboard in Cocoon.io - see Cocoon.Dialog.showKeyboard
function KeyboardManager(renderer, options) {
    EventEmitter.call(this);

    options = options || {};

    /**
     * The renderer this interaction manager works for.
     *
     * @type PIXI.SystemRenderer
     */
    this.renderer = renderer;

    /**
     * Should default browser actions automatically be prevented.
     *
     * @type bool
     * @default false
     */
    this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : false;

    /**
     * An event data object to handle all the event tracking/dispatching
     *
     * @type Object
     */
    this.eventData = {
        stopped: false,
        target: null,
        type: null,
        data: {},
        stopPropagation:function(){
            this.stopped = true;
        }
    };

    this.onKeyUp = this.onKeyUp.bind(this);
    this.keyUpProcess = this.keyUpProcess.bind(this);

    this.onKeyDown = this.onKeyDown.bind(this);
    this.keyDownProcess = this.keyDownProcess.bind(this);

    this.addEvents();
}

KeyboardManager.prototype = Object.create(EventEmitter.prototype);
KeyboardManager.prototype.constructor = KeyboardManager;
module.exports = KeyboardManager;

 /**
 * Registers all the DOM events
 *
 * @private
 */
KeyboardManager.prototype.addEvents = function () {
    if (window.document.body) {
        // Ineternet Explorer only listens to key-events on a dom-object,
        // it ignores them when we listen on document
        window.document.body.addEventListener('keydown', this.onKeyDown, true);
        window.document.body.addEventListener('keyup', this.onKeyUp, true);
    } else {
        window.document.addEventListener('keydown', this.onKeyDown, true);
        window.document.addEventListener('keyup', this.onKeyUp, true);
    }

    this.eventsAdded = true;
};

/**
 * Removes all the DOM events that were previously registered
 *
 * @private
 */
KeyboardManager.prototype.removeEvents = function () {
    if (window.document.body) {

        window.document.body.removeEventListener('keydown', this.onKeyDown, true);
        window.document.body.removeEventListener('keyup', this.onKeyUp, true);
    } else {
        window.document.removeEventListener('keydown', this.onKeyDown, true);
        window.document.removeEventListener('keyup', this.onKeyUp, true);
    }
    this.eventsAdded = false;
};

/**
 * Is called when the key is pressed down
 *
 * @param event The DOM event of a key being pressed down {Event}
 * @private
 */
KeyboardManager.prototype.onKeyDown = function (event) {
    if (this.autoPreventDefault) {
        event.preventDefault();
    }
    this._keyEvent(event);
    this.processInteractive(this.renderer._lastObjectRendered, this.keyDownProcess);
    this.emit('keydown', this.eventData);
};

/**
 * Is called when the key is released
 *
 * @param event The DOM event of a key being released {Event}
 * @private
 */
KeyboardManager.prototype.onKeyUp = function (event) {
    if (this.autoPreventDefault) {
        event.preventDefault();
    }
    this._keyEvent(event);
    this.processInteractive(this.renderer._lastObjectRendered, this.keyUpProcess);
    this.emit('keyup', this.eventData);
};

/**
 * Handle original key event and forward it to gown
 *
 * @param event The DOM event of a key being released {Event}
 * @private
 */
KeyboardManager.prototype._keyEvent = function(event) {

    this.eventData.stopped = false;
    this.eventData.originalEvent = event;
    this.eventData.data = this.getKeyData(event);

    if (this.autoPreventDefault) {
        event.preventDefault();
    }
};

/**
 * Grabs the data from the keystroke
 *
 * @param event The event to get the key data from {Event}
 * @return {Object}
 * @private
 */
KeyboardManager.prototype.getKeyData = function (event) {
    return {
        altKey: event.altKey,
        ctrlKey: event.ctrlKey,
        shiftKey: event.shiftKey,
        key: event.key,
        originalEvent: event
    };
};

/**
 * Dispatches a key up event
 *
 * @param displayObject The object to dispatch the key event for {PIXI.DisplayObject}
 * @private
 */
KeyboardManager.prototype.keyUpProcess = function(displayObject) {
    this.dispatchEvent( displayObject, 'keyup', this.eventData );
};

/**
 * Dispatches a key down event
 *
 * @param displayObject The object to dispatch the key event for {PIXI.DisplayObject}
 * @private
 */
KeyboardManager.prototype.keyDownProcess = function(displayObject) {
    this.dispatchEvent( displayObject, 'keydown', this.eventData );
};

/**
 * Traverse through the scene graph to call the given function on all displayObjects
 * that can receive keys
 *
 * @param displayObject The displayObject that will be resized (recurcsivly crawls its children)
 * {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite}
 * @param [func] The function that will be called on each resizable object.
 * The displayObject will be passed to the function {Function}
 */
KeyboardManager.prototype.processInteractive = function (displayObject, func) {
    if(!displayObject || !displayObject.visible || displayObject.enabled === false)
    {
        return false;
    }

    var children = displayObject.children;

    for (var i = children.length-1; i >= 0; i--) {
        // unlike the InteractionManager we iterate over ALL children
        // and check every one if it is resizable, because
        // we assume that resize is something that could affect every component
        // not only the one that has focus.
        var child = children[i];
        this.processInteractive(child, func);
    }

    // only the ones who can receive keys (e.g. InputControl) will listen to
    if (displayObject.receiveKeys) {
        func(displayObject);
    }
};

/**
 * Dispatches an event on the display object that has resizable set to true
 *
 * @param displayObject The display object in question {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite}
 * @param eventString The name of the event (e.g, resize or orientation) {String}
 * @param eventData The event data object {Object}
 * @private
 */
KeyboardManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData )
{
    if(!eventData.stopped)
    {
        eventData.target = displayObject;
        eventData.type = eventString;

        displayObject.emit( eventString, eventData );

        if( displayObject[eventString] )
        {
            displayObject[eventString]( eventData );
        }
    }
};

/**
 * Remove events and listener etc.
 */
KeyboardManager.prototype.destroy = function(){
    this.removeEvents();
    this.removeAllListeners();
    this.renderer = null;
    this.eventData = null;
    this.onKeyUp = null;
    this.keyUpProcess = null;
    this.onKeyDown = null;
    this.keyDownProcess = null;
};

PIXI.WebGLRenderer.registerPlugin('keyboard', KeyboardManager);
PIXI.CanvasRenderer.registerPlugin('keyboard', KeyboardManager);