2017-04-17 15:10:04 -04:00
|
|
|
const Cast = require('../util/cast');
|
2016-09-02 11:23:09 -04:00
|
|
|
|
2017-04-17 19:42:48 -04:00
|
|
|
class Keyboard {
|
|
|
|
constructor (runtime) {
|
|
|
|
/**
|
2018-04-25 11:58:50 -04:00
|
|
|
* List of currently pressed scratch keys.
|
|
|
|
* 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>}
|
2017-04-17 19:42:48 -04:00
|
|
|
*/
|
|
|
|
this._keysPressed = [];
|
|
|
|
/**
|
|
|
|
* Reference to the owning Runtime.
|
|
|
|
* Can be used, for example, to activate hats.
|
|
|
|
* @type{!Runtime}
|
|
|
|
*/
|
|
|
|
this.runtime = runtime;
|
|
|
|
}
|
|
|
|
|
2016-09-03 16:33:45 -04:00
|
|
|
/**
|
2018-04-25 11:58:50 -04:00
|
|
|
* Names used for a set of special keys in Scratch.
|
|
|
|
* @type {Array.<string>}
|
2016-09-03 16:33:45 -04:00
|
|
|
*/
|
2018-04-25 11:58:50 -04:00
|
|
|
static get SPECIAL_KEY_NAMES () {
|
|
|
|
return [
|
|
|
|
'space',
|
|
|
|
'left arrow',
|
|
|
|
'up arrow',
|
|
|
|
'right arrow',
|
|
|
|
'down arrow'
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert from a keyboard event key name to a Scratch key name.
|
|
|
|
* @param {string} keyString the input key string.
|
|
|
|
* @return {string} the corresponding Scratch key, or an empty string.
|
|
|
|
*/
|
|
|
|
_keyStringToScratchKey (keyString) {
|
|
|
|
keyString = Cast.toString(keyString);
|
|
|
|
// Convert space and arrow keys to their Scratch key names.
|
2017-04-17 19:42:48 -04:00
|
|
|
switch (keyString) {
|
2018-04-23 17:49:01 -04:00
|
|
|
case ' ': return 'space';
|
|
|
|
case 'ArrowLeft': return 'left arrow';
|
|
|
|
case 'ArrowUp': return 'up arrow';
|
2018-04-25 11:58:50 -04:00
|
|
|
case 'ArrowRight': return 'right arrow';
|
2018-04-23 17:49:01 -04:00
|
|
|
case 'ArrowDown': return 'down arrow';
|
2017-04-17 19:42:48 -04:00
|
|
|
}
|
2018-04-25 11:58:50 -04:00
|
|
|
// Ignore modifier keys
|
|
|
|
if (keyString.length > 1) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
return keyString.toUpperCase();
|
2016-09-02 11:23:09 -04:00
|
|
|
}
|
|
|
|
|
2017-04-17 19:42:48 -04:00
|
|
|
/**
|
2018-04-25 11:58:50 -04:00
|
|
|
* Convert from a block argument to a Scratch key name.
|
|
|
|
* @param {string} keyArg the input arg.
|
|
|
|
* @return {string} the corresponding Scratch key.
|
2017-04-17 19:42:48 -04:00
|
|
|
*/
|
2018-04-25 11:58:50 -04:00
|
|
|
_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];
|
|
|
|
}
|
|
|
|
|
2018-04-30 17:05:33 -04:00
|
|
|
// Check for the space character.
|
|
|
|
if (keyArg === ' ') {
|
|
|
|
return 'space';
|
|
|
|
}
|
|
|
|
|
2018-04-25 11:58:50 -04:00
|
|
|
return keyArg.toUpperCase();
|
2016-09-03 16:33:45 -04:00
|
|
|
}
|
|
|
|
|
2017-04-17 19:42:48 -04:00
|
|
|
/**
|
|
|
|
* Keyboard DOM event handler.
|
|
|
|
* @param {object} data Data from DOM event.
|
|
|
|
*/
|
|
|
|
postData (data) {
|
2018-04-25 11:58:50 -04:00
|
|
|
if (!data.key) return;
|
|
|
|
const scratchKey = this._keyStringToScratchKey(data.key);
|
|
|
|
if (scratchKey === '') return;
|
|
|
|
const index = this._keysPressed.indexOf(scratchKey);
|
|
|
|
if (data.isDown) {
|
|
|
|
// If not already present, add to the list.
|
|
|
|
if (index < 0) {
|
|
|
|
this._keysPressed.push(scratchKey);
|
2016-09-02 11:23:09 -04:00
|
|
|
}
|
2018-04-25 11:58:50 -04:00
|
|
|
// 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);
|
2016-09-02 11:23:09 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-17 19:42:48 -04:00
|
|
|
/**
|
2018-04-25 11:58:50 -04:00
|
|
|
* Get key down state for a specified key.
|
|
|
|
* @param {Any} keyArg key argument.
|
2017-04-17 19:42:48 -04:00
|
|
|
* @return {boolean} Is the specified key down?
|
|
|
|
*/
|
2018-04-25 11:58:50 -04:00
|
|
|
getKeyIsDown (keyArg) {
|
|
|
|
if (keyArg === 'any') {
|
2017-04-17 19:42:48 -04:00
|
|
|
return this._keysPressed.length > 0;
|
|
|
|
}
|
2018-04-25 11:58:50 -04:00
|
|
|
const scratchKey = this._keyArgToScratchKey(keyArg);
|
|
|
|
return this._keysPressed.indexOf(scratchKey) > -1;
|
2016-09-02 11:23:09 -04:00
|
|
|
}
|
2017-04-17 19:42:48 -04:00
|
|
|
}
|
2016-09-02 11:23:09 -04:00
|
|
|
|
|
|
|
module.exports = Keyboard;
|