UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

314 lines (313 loc) 9.51 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Debug } from "../../core/debug.js"; import { EventHandler } from "../../core/event-handler.js"; import { KeyboardEvent } from "./keyboard-event.js"; const _keyboardEvent = new KeyboardEvent(); function makeKeyboardEvent(event) { _keyboardEvent.key = event.keyCode; _keyboardEvent.element = event.target; _keyboardEvent.event = event; return _keyboardEvent; } function toKeyCode(s) { if (typeof s === "string") { return s.toUpperCase().charCodeAt(0); } return s; } const _keyCodeToKeyIdentifier = { "9": "Tab", "13": "Enter", "16": "Shift", "17": "Control", "18": "Alt", "27": "Escape", "37": "Left", "38": "Up", "39": "Right", "40": "Down", "46": "Delete", "91": "Win" }; class Keyboard extends EventHandler { /** * Create a new Keyboard instance. * * @param {Element|Window} [element] - Element to attach Keyboard to. Note that elements like * &lt;div&gt; can't accept focus by default. To use keyboard events on an element like this it * must have a value of 'tabindex' e.g. tabindex="0". See * [here](https://www.w3.org/WAI/GL/WCAG20/WD-WCAG20-TECHS/SCR29.html) for more details. * @param {object} [options] - Optional options object. * @param {boolean} [options.preventDefault] - Call preventDefault() in key event handlers. * This stops the default action of the event occurring. e.g. Ctrl+T will not open a new * browser tab. * @param {boolean} [options.stopPropagation] - Call stopPropagation() in key event handlers. * This stops the event bubbling up the DOM so no parent handlers will be notified of the * event. * @example * // attach keyboard listeners to the window * const keyboard = new pc.Keyboard(window); */ constructor(element, options = {}) { super(); /** @private */ __publicField(this, "_element", null); /** @private */ __publicField(this, "_keymap", {}); /** @private */ __publicField(this, "_lastmap", {}); /** * @type {(event: globalThis.KeyboardEvent) => void} * @private */ __publicField(this, "_keyDownHandler"); /** * @type {(event: globalThis.KeyboardEvent) => void} * @private */ __publicField(this, "_keyUpHandler"); /** * @type {(event: globalThis.KeyboardEvent) => void} * @private */ __publicField(this, "_keyPressHandler"); /** * @type {() => void} * @private */ __publicField(this, "_visibilityChangeHandler"); /** * @type {() => void} * @private */ __publicField(this, "_windowBlurHandler"); /** * Call preventDefault() in key event handlers. * * @type {boolean} */ __publicField(this, "preventDefault"); /** * Call stopPropagation() in key event handlers. * * @type {boolean} */ __publicField(this, "stopPropagation"); this._keyDownHandler = this._handleKeyDown.bind(this); this._keyUpHandler = this._handleKeyUp.bind(this); this._keyPressHandler = this._handleKeyPress.bind(this); this._visibilityChangeHandler = this._handleVisibilityChange.bind(this); this._windowBlurHandler = this._handleWindowBlur.bind(this); if (element) { this.attach(element); } this.preventDefault = options.preventDefault || false; this.stopPropagation = options.stopPropagation || false; } /** * Attach the keyboard event handlers to an Element. * * @param {Element|Window} element - The element to listen for keyboard events on. */ attach(element) { if (this._element) { this.detach(); } this._element = element; this._element.addEventListener("keydown", this._keyDownHandler, false); this._element.addEventListener("keypress", this._keyPressHandler, false); this._element.addEventListener("keyup", this._keyUpHandler, false); document.addEventListener("visibilitychange", this._visibilityChangeHandler, false); window.addEventListener("blur", this._windowBlurHandler, false); } /** * Detach the keyboard event handlers from the element it is attached to. */ detach() { if (!this._element) { Debug.warn("Unable to detach keyboard. It is not attached to an element."); return; } this._element.removeEventListener("keydown", this._keyDownHandler); this._element.removeEventListener("keypress", this._keyPressHandler); this._element.removeEventListener("keyup", this._keyUpHandler); this._element = null; document.removeEventListener("visibilitychange", this._visibilityChangeHandler, false); window.removeEventListener("blur", this._windowBlurHandler, false); } /** * Convert a key code into a key identifier. * * @param {number} keyCode - The key code. * @returns {string} The key identifier. * @private */ toKeyIdentifier(keyCode) { keyCode = toKeyCode(keyCode); const id = _keyCodeToKeyIdentifier[keyCode.toString()]; if (id) { return id; } let hex = keyCode.toString(16).toUpperCase(); const length = hex.length; for (let count = 0; count < 4 - length; count++) { hex = `0${hex}`; } return `U+${hex}`; } /** * Process the browser keydown event. * * @param {globalThis.KeyboardEvent} event - The browser keyboard event. * @private */ _handleKeyDown(event) { const code = event.keyCode || event.charCode; if (code === void 0) return; const id = this.toKeyIdentifier(code); this._keymap[id] = true; this.fire("keydown", makeKeyboardEvent(event)); if (this.preventDefault) { event.preventDefault(); } if (this.stopPropagation) { event.stopPropagation(); } } /** * Process the browser keyup event. * * @param {globalThis.KeyboardEvent} event - The browser keyboard event. * @private */ _handleKeyUp(event) { const code = event.keyCode || event.charCode; if (code === void 0) return; const id = this.toKeyIdentifier(code); delete this._keymap[id]; this.fire("keyup", makeKeyboardEvent(event)); if (this.preventDefault) { event.preventDefault(); } if (this.stopPropagation) { event.stopPropagation(); } } /** * Process the browser keypress event. * * @param {globalThis.KeyboardEvent} event - The browser keyboard event. * @private */ _handleKeyPress(event) { this.fire("keypress", makeKeyboardEvent(event)); if (this.preventDefault) { event.preventDefault(); } if (this.stopPropagation) { event.stopPropagation(); } } /** * Handle the browser visibilitychange event. * * @private */ _handleVisibilityChange() { if (document.visibilityState === "hidden") { this._handleWindowBlur(); } } /** * Handle the browser blur event. * * @private */ _handleWindowBlur() { this._keymap = {}; this._lastmap = {}; } /** * Called once per frame to update internal state. * * @ignore */ update() { for (const prop in this._lastmap) { delete this._lastmap[prop]; } for (const prop in this._keymap) { if (this._keymap.hasOwnProperty(prop)) { this._lastmap[prop] = this._keymap[prop]; } } } /** * Return true if the key is currently down. * * @param {number} key - The keyCode of the key to test. See the KEY_* constants. * @returns {boolean} True if the key was pressed, false if not. */ isPressed(key) { const keyCode = toKeyCode(key); const id = this.toKeyIdentifier(keyCode); return !!this._keymap[id]; } /** * Returns true if the key was pressed since the last update. * * @param {number} key - The keyCode of the key to test. See the KEY_* constants. * @returns {boolean} True if the key was pressed. */ wasPressed(key) { const keyCode = toKeyCode(key); const id = this.toKeyIdentifier(keyCode); return !!this._keymap[id] && !!!this._lastmap[id]; } /** * Returns true if the key was released since the last update. * * @param {number} key - The keyCode of the key to test. See the KEY_* constants. * @returns {boolean} True if the key was pressed. */ wasReleased(key) { const keyCode = toKeyCode(key); const id = this.toKeyIdentifier(keyCode); return !!!this._keymap[id] && !!this._lastmap[id]; } } /** * Fired when a key is pressed. The handler is passed a {@link KeyboardEvent}. * * @event * @example * const onKeyDown = (e) => { * if (e.key === pc.KEY_SPACE) { * // space key pressed * } * e.event.preventDefault(); // Use original browser event to prevent browser action. * }; * * app.keyboard.on('keydown', onKeyDown, this); */ __publicField(Keyboard, "EVENT_KEYDOWN", "keydown"); /** * Fired when a key is released. The handler is passed a {@link KeyboardEvent}. * * @event * @example * const onKeyUp = (e) => { * if (e.key === pc.KEY_SPACE) { * // space key released * } * e.event.preventDefault(); // Use original browser event to prevent browser action. * }; * * app.keyboard.on('keyup', onKeyUp, this); */ __publicField(Keyboard, "EVENT_KEYUP", "keyup"); export { Keyboard };