Re-write keyboard IO for droppability support

This commit is contained in:
Eric Rosenbaum 2018-04-25 11:58:50 -04:00
parent 2590cf53e7
commit 999fb10d15

View file

@ -3,8 +3,13 @@ const Cast = require('../util/cast');
class Keyboard { class Keyboard {
constructor (runtime) { constructor (runtime) {
/** /**
* List of currently pressed keys. * List of currently pressed scratch keys.
* @type{Array.<number>} * A scratch key is:
* A key you can press on a keyboard, excluding modifier keys.
* An uppercase string of length one;
* except for special key names for arrow keys and space (e.g. 'left arrow').
* Can be a non-english unicode letter like: æ ø ש נ 廿.
* @type{Array.<string>}
*/ */
this._keysPressed = []; this._keysPressed = [];
/** /**
@ -16,31 +21,76 @@ class Keyboard {
} }
/** /**
* Convert a Scratch key name to a DOM keyCode. * Names used for a set of special keys in Scratch.
* @param {Any} keyName Scratch key argument. * @type {Array.<string>}
* @return {number} Key code corresponding to a DOM event.
* @private
*/ */
_scratchKeyToKeyCode (keyName) { static get SPECIAL_KEY_NAMES () {
const keyString = Cast.toString(keyName); return [
switch (keyString) { 'space',
case ' ': return 'space'; 'left arrow',
case 'ArrowLeft': return 'left arrow'; 'up arrow',
case 'ArrowRight': return 'right arrow'; 'right arrow',
case 'ArrowUp': return 'up arrow'; 'down arrow'
case 'ArrowDown': return 'down arrow'; ];
}
return keyName;
} }
/** /**
* Convert a DOM keyCode into a Scratch key name. * Convert from a keyboard event key name to a Scratch key name.
* @param {number} keyCode Key code from DOM event. * @param {string} keyString the input key string.
* @return {Any} Scratch key argument. * @return {string} the corresponding Scratch key, or an empty string.
* @private
*/ */
_keyCodeToScratchKey (keyCode) { _keyStringToScratchKey (keyString) {
return keyCode; keyString = Cast.toString(keyString);
// Convert space and arrow keys to their Scratch key names.
switch (keyString) {
case ' ': return 'space';
case 'ArrowLeft': return 'left arrow';
case 'ArrowUp': return 'up arrow';
case 'ArrowRight': return 'right arrow';
case 'ArrowDown': return 'down arrow';
}
// Ignore modifier keys
if (keyString.length > 1) {
return '';
}
return keyString.toUpperCase();
}
/**
* Convert from a block argument to a Scratch key name.
* @param {string} keyArg the input arg.
* @return {string} the corresponding Scratch key.
*/
_keyArgToScratchKey (keyArg) {
// If a number was dropped in, try to convert from ASCII to Scratch key.
if (typeof keyArg === 'number') {
// Check for the ASCII range containing numbers, some punctuation,
// and uppercase letters.
if (keyArg >= 48 && keyArg <= 90) {
return String.fromCharCode(keyArg);
}
switch (keyArg) {
case 32: return 'space';
case 37: return 'left arrow';
case 38: return 'up arrow';
case 39: return 'right arrow';
case 40: return 'down arrow';
}
}
keyArg = Cast.toString(keyArg);
// If the arg matches a special key name, return it.
if (Keyboard.SPECIAL_KEY_NAMES.includes(keyArg)) {
return keyArg;
}
// Use only the first character.
if (keyArg.length > 1) {
keyArg = keyArg[0];
}
return keyArg.toUpperCase();
} }
/** /**
@ -48,39 +98,39 @@ class Keyboard {
* @param {object} data Data from DOM event. * @param {object} data Data from DOM event.
*/ */
postData (data) { postData (data) {
const keyCode = this._scratchKeyToKeyCode(data.key); if (!data.key) return;
if (keyCode) { const scratchKey = this._keyStringToScratchKey(data.key);
const index = this._keysPressed.indexOf(keyCode); if (scratchKey === '') return;
if (data.isDown) { const index = this._keysPressed.indexOf(scratchKey);
// If not already present, add to the list. if (data.isDown) {
if (index < 0) { // If not already present, add to the list.
this._keysPressed.push(keyCode); if (index < 0) {
} this._keysPressed.push(scratchKey);
// Always trigger hats, even if it was already pressed.
this.runtime.startHats('event_whenkeypressed', {
KEY_OPTION: this._keyCodeToScratchKey(keyCode)
});
this.runtime.startHats('event_whenkeypressed', {
KEY_OPTION: 'any'
});
} else if (index > -1) {
// If already present, remove from the list.
this._keysPressed.splice(index, 1);
} }
// Always trigger hats, even if it was already pressed.
this.runtime.startHats('event_whenkeypressed', {
KEY_OPTION: scratchKey
});
this.runtime.startHats('event_whenkeypressed', {
KEY_OPTION: 'any'
});
} else if (index > -1) {
// If already present, remove from the list.
this._keysPressed.splice(index, 1);
} }
} }
/** /**
* Get key down state for a specified Scratch key name. * Get key down state for a specified key.
* @param {Any} key Scratch key argument. * @param {Any} keyArg key argument.
* @return {boolean} Is the specified key down? * @return {boolean} Is the specified key down?
*/ */
getKeyIsDown (key) { getKeyIsDown (keyArg) {
if (key === 'any') { if (keyArg === 'any') {
return this._keysPressed.length > 0; return this._keysPressed.length > 0;
} }
const keyCode = this._scratchKeyToKeyCode(key); const scratchKey = this._keyArgToScratchKey(keyArg);
return this._keysPressed.indexOf(keyCode) > -1; return this._keysPressed.indexOf(scratchKey) > -1;
} }
} }