Source: input/controller.js

Object.assign(pc, function () {

    /**
     * @constructor
     * @name pc.Controller
     * @classdesc A general input handler which handles both mouse and keyboard input assigned to named actions.
     * This allows you to define input handlers separately to defining keyboard/mouse configurations.
     * @description Create a new instance of a Controller.
     * @param {Element} [element] Element to attach Controller to.
     * @param {Object} [options] Optional arguments.
     * @param {pc.Keyboard} [options.keyboard] A Keyboard object to use.
     * @param {pc.Mouse} [options.mouse] A Mouse object to use.
     * @param {pc.GamePads} [options.gamepads] A Gamepads object to use.
     * @example
     * var c = new pc.Controller(document)
     *
     * // Register the "fire" action and assign it to both the Enter key and the Spacebar.
     * c.registerKeys("fire", [pc.KEY_ENTER, pc.KEY_SPACE]);
     */
    var Controller = function (element, options) {
        options = options || {};
        this._keyboard = options.keyboard || null;
        this._mouse = options.mouse || null;
        this._gamepads = options.gamepads || null;

        this._element = null;

        this._actions = {};
        this._axes = {};
        this._axesValues = {};

        if (element) {
            this.attach(element);
        }
    };

    /**
     * @function
     * @name pc.Controller#attach
     * @description Attach Controller to a Element, this is required before you can monitor for key/mouse inputs.
     * @param {Element} element The element to attach mouse and keyboard event handler too
     */
    Controller.prototype.attach = function (element) {
        this._element = element;
        if (this._keyboard) {
            this._keyboard.attach(element);
        }

        if (this._mouse) {
            this._mouse.attach(element);
        }
    };

    /**
     * @function
     * @name pc.Controller#detach
     * @description Detach Controller from an Element, this should be done before the Controller is destroyed
     */
    Controller.prototype.detach = function () {
        if (this._keyboard) {
            this._keyboard.detach();
        }
        if (this._mouse) {
            this._mouse.detach();
        }
        this._element = null;
    };

    /**
     * @function
     * @name pc.Controller#disableContextMenu
     * @description Disable the context menu usually activated with the right mouse button.
     */
    Controller.prototype.disableContextMenu = function () {
        if (!this._mouse) {
            this._enableMouse();
        }

        this._mouse.disableContextMenu();
    };

    /**
     * @function
     * @name pc.Controller#enableContextMenu
     * @description Enable the context menu usually activated with the right mouse button. This is enabled by default.
     */
    Controller.prototype.enableContextMenu = function () {
        if (!this._mouse) {
            this._enableMouse();
        }

        this._mouse.enableContextMenu();
    };

    /**
     * @function
     * @name pc.Controller#update
     * @description Update the Keyboard and Mouse handlers
     * @param {Object} dt The time since the last frame
     */
    Controller.prototype.update = function (dt) {
        if (this._keyboard) {
            this._keyboard.update(dt);
        }

        if (this._mouse) {
            this._mouse.update(dt);
        }

        if (this._gamepads) {
            this._gamepads.update(dt);
        }

        // clear axes values
        this._axesValues = {};
        for (var key in this._axes) {
            this._axesValues[key] = [];
        }
    };

    /**
     * @function
     * @name pc.Controller#registerKeys
     * @description Create or update a action which is enabled when the supplied keys are pressed.
     * @param {String} action The name of the action
     * @param {Number[]} keys A list of keycodes
     */
    Controller.prototype.registerKeys = function (action, keys) {
        if (!this._keyboard) {
            this._enableKeyboard();
        }
        if (this._actions[action]) {
            throw new Error(pc.string.format("Action: {0} already registered", action));
        }

        if (keys === undefined) {
            throw new Error('Invalid button');
        }

        // convert to an array
        if (!keys.length) {
            keys = [keys];
        }

        if (this._actions[action]) {
            this._actions[action].push({
                type: pc.ACTION_KEYBOARD,
                keys: keys
            });
        } else {
            this._actions[action] = [{
                type: pc.ACTION_KEYBOARD,
                keys: keys
            }];
        }
    };

    /**
     * @function
     * @name pc.Controller#registerMouse
     * @description Create or update an action which is enabled when the supplied mouse button is pressed
     * @param {String} action The name of the action
     * @param {Number} button The mouse button
     */
    Controller.prototype.registerMouse = function (action, button) {
        if (!this._mouse) {
            this._enableMouse();
        }

        if (button === undefined) {
            throw new Error('Invalid button');
        }

        // Mouse actions are stored as negative numbers to prevent clashing with keycodes.
        if (this._actions[action]) {
            this._actions[action].push({
                type: pc.ACTION_MOUSE,
                button: button
            });
        } else {
            this._actions[action] = [{
                type: pc.ACTION_MOUSE,
                button: -button
            }];
        }
    };

    /**
     * @function
     * @name pc.Controller#registerPadButton
     * @description Create or update an action which is enabled when the gamepad button is pressed
     * @param {String} action The name of the action
     * @param {Number} pad The index of the pad to register (use pc.PAD_1, etc)
     * @param {Number} button The pad button
     */
    Controller.prototype.registerPadButton = function (action, pad, button) {
        if (button === undefined) {
            throw new Error('Invalid button');
        }
        // Mouse actions are stored as negative numbers to prevent clashing with keycodes.
        if (this._actions[action]) {
            this._actions[action].push({
                type: pc.ACTION_GAMEPAD,
                button: button,
                pad: pad
            });
        } else {
            this._actions[action] = [{
                type: pc.ACTION_GAMEPAD,
                button: button,
                pad: pad
            }];
        }
    };

    /**
     * @function
     * @name pc.Controller#registerAxis
     * @param {Object} [options] Optional options object.
     * @param {Object} [options.pad] The index of the game pad to register for (use pc.PAD_1, etc)
     */
    Controller.prototype.registerAxis = function (options) {
        var name = options.name;
        if (!this._axes[name]) {
            this._axes[name] = [];
        }
        var i = this._axes[name].push(name);

        //
        options = options || {};
        options.pad = options.pad || pc.PAD_1;

        var bind = function (controller, source, value, key) {
            switch (source) {
                case 'mousex':
                    controller._mouse.on(pc.EVENT_MOUSEMOVE, function (e) {
                        controller._axesValues[name][i] = e.dx / 10;
                    });
                    break;
                case 'mousey':
                    controller._mouse.on(pc.EVENT_MOUSEMOVE, function (e) {
                        controller._axesValues[name][i] = e.dy / 10;
                    });
                    break;
                case 'key':
                    controller._axes[name].push(function () {
                        return controller._keyboard.isPressed(key) ? value : 0;
                    });
                    break;
                case 'padrx':
                    controller._axes[name].push(function () {
                        return controller._gamepads.getAxis(options.pad, pc.PAD_R_STICK_X);
                    });
                    break;
                case 'padry':
                    controller._axes[name].push(function () {
                        return controller._gamepads.getAxis(options.pad, pc.PAD_R_STICK_Y);
                    });
                    break;
                case 'padlx':
                    controller._axes[name].push(function () {
                        return controller._gamepads.getAxis(options.pad, pc.PAD_L_STICK_X);
                    });
                    break;
                case 'padly':
                    controller._axes[name].push(function () {
                        return controller._gamepads.getAxis(options.pad, pc.PAD_L_STICK_Y);
                    });
                    break;
                default:
                    throw new Error('Unknown axis');
            }
        };

        bind(this, options.positive, 1, options.positiveKey);
        if (options.negativeKey || options.negative !== options.positive) {
            bind(this, options.negative, -1, options.negativeKey);
        }

    };

    /**
     * @function
     * @name pc.Controller#isPressed
     * @description Returns true if the current action is enabled.
     * @param {String} actionName The name of the action.
     * @returns {Boolean} True if the action is enabled.
     */
    Controller.prototype.isPressed = function (actionName) {
        if (!this._actions[actionName]) {
            return false;
        }

        var action;
        var index = 0;
        var length = this._actions[actionName].length;

        for (index = 0; index < length; ++index) {
            action = this._actions[actionName][index];
            switch (action.type) {
                case pc.ACTION_KEYBOARD:
                    if (this._keyboard) {
                        var i, len = action.keys.length;
                        for (i = 0; i < len; i++) {
                            if (this._keyboard.isPressed(action.keys[i])) {
                                return true;
                            }
                        }
                    }
                    break;
                case pc.ACTION_MOUSE:
                    if (this._mouse && this._mouse.isPressed(action.button)) {
                        return true;
                    }
                    break;
                case pc.ACTION_GAMEPAD:
                    if (this._gamepads && this._gamepads.isPressed(action.pad, action.button)) {
                        return true;
                    }
                    break;
            }
        }
        return false;
    };

    /**
     * @function
     * @name pc.Controller#wasPressed
     * @description Returns true if the action was enabled this since the last update.
     * @param {String} actionName The name of the action.
     * @returns {Boolean} True if the action was enabled this since the last update.
     */
    Controller.prototype.wasPressed = function (actionName) {
        if (!this._actions[actionName]) {
            return false;
        }

        var index = 0;
        var length = this._actions[actionName].length;

        for (index = 0; index < length; ++index) {
            var action = this._actions[actionName][index];
            switch (action.type) {
                case pc.ACTION_KEYBOARD:
                    if (this._keyboard) {
                        var i, len = action.keys.length;
                        for (i = 0; i < len; i++) {
                            if (this._keyboard.wasPressed(action.keys[i])) {
                                return true;
                            }
                        }
                    }
                    break;
                case pc.ACTION_MOUSE:
                    if (this._mouse && this._mouse.wasPressed(action.button)) {
                        return true;
                    }
                    break;
                case pc.ACTION_GAMEPAD:
                    if (this._gamepads && this._gamepads.wasPressed(action.pad, action.button)) {
                        return true;
                    }
                    break;
            }
        }
        return false;
    };

    Controller.prototype.getAxis = function (name) {
        var value = 0;

        if (this._axes[name]) {
            var i, len = this._axes[name].length;
            for (i = 0; i < len; i++) {
                if (pc.type(this._axes[name][i]) === 'function') {
                    var v = this._axes[name][i]();
                    if (Math.abs(v) > Math.abs(value)) {
                        value = v;
                    }
                } else if (this._axesValues[name]) {
                    if (Math.abs(this._axesValues[name][i]) > Math.abs(value)) {
                        value = this._axesValues[name][i];
                    }
                }
            }
        }

        return value;
    };

    Controller.prototype._enableMouse = function () {
        this._mouse = new pc.Mouse();
        if (!this._element) {
            throw new Error("Controller must be attached to an Element");
        }
        this._mouse.attach(this._element);
    };

    Controller.prototype._enableKeyboard = function () {
        this._keyboard = new pc.Keyboard();
        if (!this._element) {
            throw new Error("Controller must be attached to an Element");
        }
        this._keyboard.attach(this._element);
    };

    return {
        Controller: Controller
    };
}());