UNPKG

coherent-gameface-interaction-manager

Version:
170 lines (139 loc) 6.29 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Coherent Labs AD. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import IM from '../utils/global-object'; import mappings from '../utils/keyboard-mappings'; import Actions from './actions'; /** * Keyboard class that handles all keyboard interactions */ class Keyboard { /* eslint-disable-next-line require-jsdoc */ constructor() { this.mappings = mappings; this.eventListenerAttached = false; this.keysPressed = new Set(); this.onKeyDown = this.onKeyDown.bind(this); this.onKeyUp = this.onKeyUp.bind(this); if (!window.KEYS) window.KEYS = mappings; } /** * @param {Object} options * @param {string[]} options.keys - Array of keys you want to use, allows only combination of modifier and regular keys * @param {function | string} options.callback - Function or action to be executed on the key combination * @param {string[]} options.type - Type of key action you want to use. * @returns {void} */ on(options) { // Remove duplicate keys. For example if someone write keys: ['A', 'A'] we'll treat it like ['A'] options.keys = [ ...new Set( options.keys.map((key) => { key = typeof key === 'number' ? this.keyCodeToString(key) : key; return key.toUpperCase(); }) ), ]; const incorrectKeys = options.keys.filter(key => !this.mappings[key]); if (incorrectKeys.length > 0) return console.error(`The following keys [${incorrectKeys.join(', ')}] you have entered are incorrect! `); if (!this.eventListenerAttached) { document.addEventListener('keydown', this.onKeyDown); document.addEventListener('keyup', this.onKeyUp); this.eventListenerAttached = true; } if (!Array.isArray(options.type)) options.type = [options.type]; options.type.forEach((type) => { const registeredKeys = IM.getKeys(options.keys); if (registeredKeys.length > 0 && registeredKeys.some(key => key.type === type)) { return console.error('You are trying to overwrite an existing key combination! To do that, first remove it with .off([keys]) then add it again'); } if (type === 'lift' && options.keys.length > 1) return console.error('You can only have a single key trigger an action on lift'); _IM.keyboardFunctions.push({ ...options, type }); }); } /** * * @param {string[]} keys - Key combination you want to remove from the listener * @returns {void} */ off(keys) { keys = [ ...new Set( keys.map((key) => { key = typeof key === 'number' ? this.keyCodeToString(key) : key; return key.toUpperCase(); }) ), ]; let keyCombinationCount = IM.getKeys(keys).length; if (keyCombinationCount === 0) return console.error('You are trying to remove a non-existent key combination!'); while (keyCombinationCount > 0) { const keyCombinationIndex = IM.getKeysIndex(keys); _IM.keyboardFunctions.splice(keyCombinationIndex, 1); keyCombinationCount--; } if (_IM.keyboardFunctions.length === 0) { document.removeEventListener('keydown', this.onKeyDown); document.removeEventListener('keyup', this.onKeyUp); this.eventListenerAttached = false; } } /** * Handles when key is pressed * @param {KeyboardEvent} event * @returns {void} * @private */ onKeyDown(event) { const keyPressed = this.keyCodeToString(event.keyCode); this.keysPressed.add(keyPressed); const registeredKeys = IM.getKeys([...this.keysPressed]); if (registeredKeys.length === 0) return; registeredKeys.forEach((key) => { if (key.type === 'press' && event.repeat) return; if (key.type !== 'press' && key.type !== 'hold') return; if (key.type === 'hold' && !event.repeat) return; this.executeCallback(event, key); }); } /** * Handles when key is released * @param {KeyboardEvent} event * @returns {void} * @private */ onKeyUp(event) { const keyPressed = this.keyCodeToString(event.keyCode); this.keysPressed.delete(keyPressed); const registeredKeys = IM.getKeys(keyPressed); if (registeredKeys.length === 0) return; registeredKeys.forEach((key) => { if (key.type === 'lift' && key.keys.indexOf(keyPressed) !== -1) this.executeCallback(event, key); }); } /** * Convert keyCode to string representing key * @param {number} code * @returns {string} * @private */ keyCodeToString(code) { return Object.keys(this.mappings).find(key => this.mappings[key] === code); } /** * Executes the registered callbacks. Has to be invoked from the onKeyDown and onKeyUp functions * @param {KeyboardEvent} event * @param {Object} registeredKeys * @param {string[]} registeredKeys.keys - Array of keys you want to use, allows only combination of modifier and regular keys * @param {function | string} registeredKeys.callback - Function or action to be executed on the key combination * @param {('press'|'hold'|'lift')} registeredKeys.type - Type of key action you want to use. * @return {void} * @private */ executeCallback(event, registeredKeys) { if (typeof registeredKeys.callback === 'string') return Actions.execute(registeredKeys.callback, event); registeredKeys.callback(event); } } export default new Keyboard();