diff --git a/src/input/keyboard/KeyboardManager.js b/src/input/keyboard/KeyboardManager.js index c7f9a8c04..9e658ee67 100644 --- a/src/input/keyboard/KeyboardManager.js +++ b/src/input/keyboard/KeyboardManager.js @@ -4,17 +4,19 @@ * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ +var ArrayRemove = require('../../utils/array/Remove'); var Class = require('../../utils/Class'); -var Features = require('../../device/Features'); +var KeyCodes = require('../../input/keyboard/keys/KeyCodes'); var NOOP = require('../../utils/Class'); /** * @classdesc - * The Keyboard Manager is a helper class that belongs to the Input Manager. + * The Keyboard Manager is a helper class that belongs to the global Input Manager. * - * Its role is to listen for native DOM Keyboard Events and then pass them onto the Input Manager for further processing. + * Its role is to listen for native DOM Keyboard Events and then store them for further processing by the Keyboard Plugin. * - * You do not need to create this class directly, the Input Manager will create an instance of it automatically. + * You do not need to create this class directly, the Input Manager will create an instance of it automatically if keyboard + * input has been enabled in the Game Config. * * @class KeyboardManager * @memberof Phaser.Input.Keyboard @@ -39,14 +41,59 @@ var KeyboardManager = new Class({ this.manager = inputManager; /** - * If true the DOM keyboard events will have event.preventDefault applied to them, if false they will propagate fully. + * An internal event queue. * - * @name Phaser.Input.Keyboard.KeyboardManager#capture - * @type {boolean} - * @default true + * @name Phaser.Input.Keyboard.KeyboardManager#queue + * @type {KeyboardEvent[]} + * @private * @since 3.16.0 */ - this.capture = true; + this.queue = []; + + /** + * A flag that controls if the non-modified keys, matching those stored in the `captures` array, + * have `preventDefault` called on them or not. + * + * A non-modified key is one that doesn't have a modifier key held down with it. The modifier keys are + * shift, control, alt and the meta key (Command on a Mac, the Windows Key on Windows). + * Therefore, if the user presses shift + r, it won't prevent this combination, because of the modifier. + * However, if the user presses just the r key on its own, it will have its event prevented. + * + * If you wish to stop capturing the keys, for example switching out to a DOM based element, then + * you can toggle this property at run-time. + * + * @name Phaser.Input.Keyboard.KeyboardManager#preventDefault + * @type {boolean} + * @since 3.16.0 + */ + this.preventDefault = true; + + /** + * An array of Key Code values that will automatically have `preventDefault` called on them, + * as long as the `KeyboardManager.preventDefault` boolean is set to `true`. + * + * By default the array contains: The Space Key, the Cursor Keys, 0 to 9 and A to Z. + * + * The key must be non-modified when pressed in order to be captured. + * + * A non-modified key is one that doesn't have a modifier key held down with it. The modifier keys are + * shift, control, alt and the meta key (Command on a Mac, the Windows Key on Windows). + * Therefore, if the user presses shift + r, it won't prevent this combination, because of the modifier. + * However, if the user presses just the r key on its own, it will have its event prevented. + * + * If you wish to stop capturing the keys, for example switching out to a DOM based element, then + * you can toggle the `KeyboardManager.preventDefault` boolean at run-time. + * + * If you need more specific control, you can create Key objects and set the flag on each of those instead. + * + * This array can be populated via the Game Config by setting the `input.keyboard.capture` array, or you + * can call the `addCapture` method. See also `removeCapture` and `clearCaptures`. + * + * @name Phaser.Input.Keyboard.KeyboardManager#captures + * @type {integer[]} + * @since 3.16.0 + */ + this.captures = []; /** * A boolean that controls if the Keyboard Manager is enabled or not. @@ -61,7 +108,7 @@ var KeyboardManager = new Class({ /** * The Keyboard Event target, as defined in the Game Config. - * Typically the canvas to which the game is rendering, but can be any interactive DOM element. + * Typically the window in which the game is rendering, but can be any interactive DOM element. * * @name Phaser.Input.Keyboard.KeyboardManager#target * @type {any} @@ -105,23 +152,26 @@ var KeyboardManager = new Class({ { var config = this.manager.config; - // this.enabled = config.inputMouse; - // this.target = config.inputMouseEventTarget; - // this.capture = config.inputMouseCapture; + this.enabled = config.inputKeyboard; + this.target = config.inputKeyboardEventTarget; - if (!this.target) + this.addCapture(config.inputKeyboardCapture); + + if (!this.target && window) { - this.target = this.manager.game.canvas; + this.target = window; } if (this.enabled && this.target) { this.startListeners(); } + + this.manager.game.events.on('poststep', this.postUpdate, this); }, /** - * Starts the Mouse Event listeners running. + * Starts the Keyboard Event listeners running. * This is called automatically and does not need to be manually invoked. * * @method Phaser.Input.Keyboard.KeyboardManager#startListeners @@ -130,25 +180,26 @@ var KeyboardManager = new Class({ startListeners: function () { var _this = this; - var canvas = this.manager.canvas; - this.onMouseDown = function (event) + this.onKeyDown = function (event) { if (event.defaultPrevented || !_this.enabled || !_this.manager) { // Do nothing if event already handled return; } + + _this.queue.push(event); - _this.manager.queueMouseDown(event); - - if (_this.capture && event.target === canvas) + var modified = (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey); + + if (_this.preventDefault && !modified && _this.captures.indexOf(event.keyCode) > -1) { event.preventDefault(); } }; - this.onMouseUp = function (event) + this.onKeyUp = function (event) { if (event.defaultPrevented || !_this.enabled || !_this.manager) { @@ -156,9 +207,11 @@ var KeyboardManager = new Class({ return; } - _this.manager.queueMouseUp(event); + _this.queue.push(event); - if (_this.capture && event.target === canvas) + var modified = (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey); + + if (_this.preventDefault && !modified && _this.captures.indexOf(event.keyCode) > -1) { event.preventDefault(); } @@ -166,28 +219,17 @@ var KeyboardManager = new Class({ var target = this.target; - if (!target) + if (target) { - return; + target.addEventListener('keydown', this.onKeyDown, false); + target.addEventListener('keyup', this.onKeyUp, false); + + this.enabled = true; } - - var passive = { passive: true }; - var nonPassive = { passive: false }; - - target.addEventListener('mousedown', this.onMouseDown, (this.capture) ? nonPassive : passive); - target.addEventListener('mouseup', this.onMouseUp, (this.capture) ? nonPassive : passive); - - if (window) - { - window.addEventListener('mousedown', this.onMouseDown, nonPassive); - window.addEventListener('mouseup', this.onMouseUp, nonPassive); - } - - this.enabled = true; }, /** - * Stops the Mouse Event listeners. + * Stops the Key Event listeners. * This is called automatically and does not need to be manually invoked. * * @method Phaser.Input.Keyboard.KeyboardManager#stopListeners @@ -197,18 +239,175 @@ var KeyboardManager = new Class({ { var target = this.target; - target.removeEventListener('mousedown', this.onMouseDown); - target.removeEventListener('mouseup', this.onMouseUp); + target.removeEventListener('keydown', this.onKeyDown, false); + target.removeEventListener('keyup', this.onKeyUp, false); - if (window) - { - window.removeEventListener('mousedown', this.onMouseDown); - window.removeEventListener('mouseup', this.onMouseUp); - } + this.enabled = false; }, /** - * Destroys this Mouse Manager instance. + * Clears the event queue. + * Called automatically by the Input Manager. + * + * @method Phaser.Input.Keyboard.KeyboardManager#postUpdate + * @private + * @since 3.16.0 + */ + postUpdate: function () + { + this.queue = []; + }, + + /** + * By default when a key is pressed Phaser will not stop the event from propagating up to the browser. + * There are some keys this can be annoying for, like the arrow keys or space bar, which make the browser window scroll. + * + * This `addCapture` method enables consuming keyboard event for specific keys so it doesn't bubble up to the the browser + * and cause the default browser behavior. + * + * Please note that keyboard captures are global. This means that if you call this method from within a Scene, to say prevent + * the SPACE BAR from triggering a page scroll, then it will prevent it for any Scene in your game, not just the calling one. + * + * You can pass in a single key code value, or an array of key codes, or a string: + * + * ```javascript + * this.input.keyboard.addCapture(62); + * ``` + * + * An array of key codes: + * + * ```javascript + * this.input.keyboard.addCapture([ 62, 63, 64 ]); + * ``` + * + * Or a string: + * + * ```javascript + * this.input.keyboard.addCapture('W,S,A,D'); + * ``` + * + * To use non-alpha numeric keys, use a string, such as 'UP', 'SPACE' or 'LEFT'. + * + * You can also provide an array mixing both strings and key code integers. + * + * If there are active captures after calling this method, the `preventDefault` property is set to `true`. + * + * @method Phaser.Input.Keyboard.KeyboardManager#addCapture + * @since 3.16.0 + * + * @param {(string|integer|integer[]|any[])} keycode - The Key Codes to enable capture for, preventing them reaching the browser. + */ + addCapture: function (keycode) + { + if (typeof keycode === 'string') + { + keycode = keycode.split(','); + } + + if (!Array.isArray(keycode)) + { + keycode = [ keycode ]; + } + + var captures = this.captures; + + for (var i = 0; i < keycode.length; i++) + { + var code = keycode[i]; + + if (typeof code === 'string') + { + code = KeyCodes[code.toUpperCase()]; + } + + if (captures.indexOf(code) === -1) + { + captures.push(code); + } + } + + this.preventDefault = captures.length > 0; + }, + + /** + * Removes an existing key capture. + * + * Please note that keyboard captures are global. This means that if you call this method from within a Scene, to remove + * the capture of a key, then it will remove it for any Scene in your game, not just the calling one. + * + * You can pass in a single key code value, or an array of key codes, or a string: + * + * ```javascript + * this.input.keyboard.removeCapture(62); + * ``` + * + * An array of key codes: + * + * ```javascript + * this.input.keyboard.removeCapture([ 62, 63, 64 ]); + * ``` + * + * Or a string: + * + * ```javascript + * this.input.keyboard.removeCapture('W,S,A,D'); + * ``` + * + * To use non-alpha numeric keys, use a string, such as 'UP', 'SPACE' or 'LEFT'. + * + * You can also provide an array mixing both strings and key code integers. + * + * If there are no captures left after calling this method, the `preventDefault` property is set to `false`. + * + * @method Phaser.Input.Keyboard.KeyboardManager#removeCapture + * @since 3.16.0 + * + * @param {(string|integer|integer[]|any[])} keycode - The Key Codes to disable capture for, allowing them reaching the browser again. + */ + removeCapture: function (keycode) + { + if (typeof keycode === 'string') + { + keycode = keycode.split(','); + } + + if (!Array.isArray(keycode)) + { + keycode = [ keycode ]; + } + + var captures = this.captures; + + for (var i = 0; i < keycode.length; i++) + { + var code = keycode[i]; + + if (typeof code === 'string') + { + code = KeyCodes[code.toUpperCase()]; + } + + ArrayRemove(captures, code); + } + + this.preventDefault = captures.length > 0; + }, + + /** + * Removes all keyboard captures and sets the `preventDefault` property to `false`. + * + * @method Phaser.Input.Keyboard.KeyboardManager#clearCaptures + * @since 3.16.0 + */ + clearCaptures: function () + { + this.captures = []; + + this.preventDefault = false; + }, + + /** + * Destroys this Keyboard Manager instance. * * @method Phaser.Input.Keyboard.KeyboardManager#destroy * @since 3.16.0 @@ -217,6 +416,12 @@ var KeyboardManager = new Class({ { this.stopListeners(); + this.clearCaptures(); + + this.queue = []; + + this.manager.game.events.off('poststep', this.postUpdate, this); + this.target = null; this.enabled = false; this.manager = null;