diff --git a/playground/playground.js b/playground/playground.js index a2c456e15..3592df1d3 100644 --- a/playground/playground.js +++ b/playground/playground.js @@ -166,6 +166,31 @@ window.onload = function() { e.preventDefault(); }); + // Feed keyboard events as VM I/O events. + document.addEventListener('keydown', function (e) { + // Don't capture keys intended for Blockly inputs. + if (e.target != document && e.target != document.body) { + return; + } + window.vm.postIOData('keyboard', { + keyCode: e.keyCode, + isDown: true + }); + e.preventDefault(); + }); + document.addEventListener('keyup', function(e) { + // Always capture up events, + // even those that have switched to other targets. + window.vm.postIOData('keyboard', { + keyCode: e.keyCode, + isDown: false + }); + // E.g., prevent scroll. + if (e.target != document && e.target != document.body) { + e.preventDefault(); + } + }); + // Run threads vm.start(); diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js index c1ac19c29..f868a4d3a 100644 --- a/src/blocks/scratch3_sensing.js +++ b/src/blocks/scratch3_sensing.js @@ -17,6 +17,8 @@ Scratch3SensingBlocks.prototype.getPrimitives = function() { 'sensing_mousex': this.getMouseX, 'sensing_mousey': this.getMouseY, 'sensing_mousedown': this.getMouseDown, + 'sensing_keyoptions': this.keyOptions, + 'sensing_keypressed': this.getKeyPressed, 'sensing_current': this.current, 'sensing_currentmenu': this.currentMenu }; @@ -59,4 +61,12 @@ Scratch3SensingBlocks.prototype.currentMenu = function (args) { return args.CURRENTMENU.toLowerCase(); }; +Scratch3SensingBlocks.prototype.keyOptions = function (args) { + return args.KEY_OPTION.toLowerCase(); +}; + +Scratch3SensingBlocks.prototype.getKeyPressed = function (args, util) { + return util.ioQuery('keyboard', 'getKeyIsDown', args.KEY_OPTIONS); +}; + module.exports = Scratch3SensingBlocks; diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 2ebd9de53..fe539dfcd 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -5,6 +5,7 @@ var util = require('util'); // Virtual I/O devices. var Clock = require('../io/clock'); +var Keyboard = require('../io/keyboard'); var Mouse = require('../io/mouse'); var defaultBlockPackages = { @@ -53,6 +54,7 @@ function Runtime () { this.ioDevices = { 'clock': new Clock(), + 'keyboard': new Keyboard(), 'mouse': new Mouse() }; } diff --git a/src/io/keyboard.js b/src/io/keyboard.js new file mode 100644 index 000000000..b3a08bae6 --- /dev/null +++ b/src/io/keyboard.js @@ -0,0 +1,57 @@ +var Cast = require('../util/cast'); + +function Keyboard () { + /** + * List of currently pressed keys. + * @type{Array.} + */ + this._keysPressed = []; +} + +/** + * Convert a Scratch key name to a DOM keyCode. + * @param {Any} keyName Scratch key argument. + * @return {number} Key code corresponding to a DOM event. + */ +Keyboard.prototype._scratchKeyToKeyCode = function (keyName) { + if (typeof keyName == 'number') { + // Key codes placed in with number blocks. + return keyName; + } + var keyString = Cast.toString(keyName); + switch (keyString) { + case 'space': return 32; + case 'left arrow': return 37; + case 'up arrow': return 38; + case 'right arrow': return 39; + case 'down arrow': return 40; + // @todo: Consider adding other special keys here. + } + // Keys reported by DOM keyCode are upper case. + return keyString.toUpperCase().charCodeAt(0); +}; + +Keyboard.prototype.postData = function (data) { + if (data.keyCode) { + var index = this._keysPressed.indexOf(data.keyCode); + if (data.isDown) { + // If not already present, add to the list. + if (index < 0) { + this._keysPressed.push(data.keyCode); + } + } else if (index > -1) { + // If already present, remove from the list. + this._keysPressed.splice(index, 1); + } + } +}; + +Keyboard.prototype.getKeyIsDown = function (key) { + if (key == 'any') { + return this._keysPressed.length > 0; + } + var keyCode = this._scratchKeyToKeyCode(key); + return this._keysPressed.indexOf(keyCode) > -1; +}; + +module.exports = Keyboard;