mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -05:00
Better glows (#152)
* Strip out old script glowing in thread management * Add new tracking mechanism for glowing scripts * Track parents and use them to determine script glows * Use top-block for a thread if there's nothing on the stack * Remove `console.log`
This commit is contained in:
parent
797f844de3
commit
5df0acc895
7 changed files with 107 additions and 28 deletions
|
@ -31,7 +31,7 @@ function domToBlocks (blocksDOM) {
|
||||||
}
|
}
|
||||||
var tagName = block.name.toLowerCase();
|
var tagName = block.name.toLowerCase();
|
||||||
if (tagName == 'block' || tagName == 'shadow') {
|
if (tagName == 'block' || tagName == 'shadow') {
|
||||||
domToBlock(block, blocks, true);
|
domToBlock(block, blocks, true, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Flatten blocks object into a list.
|
// Flatten blocks object into a list.
|
||||||
|
@ -48,8 +48,9 @@ function domToBlocks (blocksDOM) {
|
||||||
* @param {Element} blockDOM DOM tree for an individual block.
|
* @param {Element} blockDOM DOM tree for an individual block.
|
||||||
* @param {Object} blocks Collection of blocks to add to.
|
* @param {Object} blocks Collection of blocks to add to.
|
||||||
* @param {Boolean} isTopBlock Whether blocks at this level are "top blocks."
|
* @param {Boolean} isTopBlock Whether blocks at this level are "top blocks."
|
||||||
|
* @param {?string} parent Parent block ID.
|
||||||
*/
|
*/
|
||||||
function domToBlock (blockDOM, blocks, isTopBlock) {
|
function domToBlock (blockDOM, blocks, isTopBlock, parent) {
|
||||||
// Block skeleton.
|
// Block skeleton.
|
||||||
var block = {
|
var block = {
|
||||||
id: blockDOM.attribs.id, // Block ID
|
id: blockDOM.attribs.id, // Block ID
|
||||||
|
@ -58,6 +59,7 @@ function domToBlock (blockDOM, blocks, isTopBlock) {
|
||||||
fields: {}, // Fields on this block and their values.
|
fields: {}, // Fields on this block and their values.
|
||||||
next: null, // Next block in the stack, if one exists.
|
next: null, // Next block in the stack, if one exists.
|
||||||
topLevel: isTopBlock, // If this block starts a stack.
|
topLevel: isTopBlock, // If this block starts a stack.
|
||||||
|
parent: parent, // Parent block ID, if available.
|
||||||
shadow: blockDOM.name == 'shadow', // If this represents a shadow/slot.
|
shadow: blockDOM.name == 'shadow', // If this represents a shadow/slot.
|
||||||
x: blockDOM.attribs.x, // X position of script, if top-level.
|
x: blockDOM.attribs.x, // X position of script, if top-level.
|
||||||
y: blockDOM.attribs.y // Y position of script, if top-level.
|
y: blockDOM.attribs.y // Y position of script, if top-level.
|
||||||
|
@ -113,10 +115,10 @@ function domToBlock (blockDOM, blocks, isTopBlock) {
|
||||||
case 'value':
|
case 'value':
|
||||||
case 'statement':
|
case 'statement':
|
||||||
// Recursively generate block structure for input block.
|
// Recursively generate block structure for input block.
|
||||||
domToBlock(childBlockNode, blocks, false);
|
domToBlock(childBlockNode, blocks, false, block.id);
|
||||||
if (childShadowNode && childBlockNode != childShadowNode) {
|
if (childShadowNode && childBlockNode != childShadowNode) {
|
||||||
// Also generate the shadow block.
|
// Also generate the shadow block.
|
||||||
domToBlock(childShadowNode, blocks, false);
|
domToBlock(childShadowNode, blocks, false, block.id);
|
||||||
}
|
}
|
||||||
// Link this block's input to the child block.
|
// Link this block's input to the child block.
|
||||||
var inputName = xmlChild.attribs.name;
|
var inputName = xmlChild.attribs.name;
|
||||||
|
@ -132,7 +134,7 @@ function domToBlock (blockDOM, blocks, isTopBlock) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Recursively generate block structure for next block.
|
// Recursively generate block structure for next block.
|
||||||
domToBlock(childBlockNode, blocks, false);
|
domToBlock(childBlockNode, blocks, false, block.id);
|
||||||
// Link next block to this block.
|
// Link next block to this block.
|
||||||
block.next = childBlockNode.attribs.id;
|
block.next = childBlockNode.attribs.id;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -115,6 +115,20 @@ Blocks.prototype.getInputs = function (id) {
|
||||||
return inputs;
|
return inputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the top-level script for a given block.
|
||||||
|
* @param {?string} id ID of block to query.
|
||||||
|
* @return {?string} ID of top-level script block.
|
||||||
|
*/
|
||||||
|
Blocks.prototype.getTopLevelScript = function (id) {
|
||||||
|
if (typeof this._blocks[id] === 'undefined') return null;
|
||||||
|
var block = this._blocks[id];
|
||||||
|
while (block.parent !== null) {
|
||||||
|
block = this._blocks[block.parent];
|
||||||
|
}
|
||||||
|
return block.id;
|
||||||
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -237,6 +251,7 @@ Blocks.prototype.moveBlock = function (e) {
|
||||||
// This block was connected to the old parent's next connection.
|
// This block was connected to the old parent's next connection.
|
||||||
oldParent.next = null;
|
oldParent.next = null;
|
||||||
}
|
}
|
||||||
|
this._blocks[e.id].parent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has the block become a top-level block?
|
// Has the block become a top-level block?
|
||||||
|
@ -262,6 +277,7 @@ Blocks.prototype.moveBlock = function (e) {
|
||||||
// Moved to the new parent's next connection.
|
// Moved to the new parent's next connection.
|
||||||
this._blocks[e.newParent].next = e.id;
|
this._blocks[e.newParent].next = e.id;
|
||||||
}
|
}
|
||||||
|
this._blocks[e.id].parent = e.newParent;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ var execute = function (sequencer, thread) {
|
||||||
}
|
}
|
||||||
// Skip through the block.
|
// Skip through the block.
|
||||||
// (either hat with no predicate, or missing op).
|
// (either hat with no predicate, or missing op).
|
||||||
|
thread.requestScriptGlowInFrame = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +111,12 @@ var execute = function (sequencer, thread) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (typeof primitiveReportedValue === 'undefined') {
|
||||||
|
// No value reported - potentially a command block.
|
||||||
|
// Edge-activated hats don't request a glow; all commands do.
|
||||||
|
thread.requestScriptGlowInFrame = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle any reported value from the primitive, either directly returned
|
* Handle any reported value from the primitive, either directly returned
|
||||||
* or after a promise resolves.
|
* or after a promise resolves.
|
||||||
|
|
|
@ -57,28 +57,31 @@ function Runtime () {
|
||||||
'keyboard': new Keyboard(this),
|
'keyboard': new Keyboard(this),
|
||||||
'mouse': new Mouse()
|
'mouse': new Mouse()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._scriptGlowsPreviousFrame = [];
|
||||||
|
this._editingTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event name for glowing a stack
|
* Event name for glowing a script.
|
||||||
* @const {string}
|
* @const {string}
|
||||||
*/
|
*/
|
||||||
Runtime.STACK_GLOW_ON = 'STACK_GLOW_ON';
|
Runtime.SCRIPT_GLOW_ON = 'STACK_GLOW_ON';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event name for unglowing a stack
|
* Event name for unglowing a script.
|
||||||
* @const {string}
|
* @const {string}
|
||||||
*/
|
*/
|
||||||
Runtime.STACK_GLOW_OFF = 'STACK_GLOW_OFF';
|
Runtime.SCRIPT_GLOW_OFF = 'STACK_GLOW_OFF';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event name for glowing a block
|
* Event name for glowing a block.
|
||||||
* @const {string}
|
* @const {string}
|
||||||
*/
|
*/
|
||||||
Runtime.BLOCK_GLOW_ON = 'BLOCK_GLOW_ON';
|
Runtime.BLOCK_GLOW_ON = 'BLOCK_GLOW_ON';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event name for unglowing a block
|
* Event name for unglowing a block.
|
||||||
* @const {string}
|
* @const {string}
|
||||||
*/
|
*/
|
||||||
Runtime.BLOCK_GLOW_OFF = 'BLOCK_GLOW_OFF';
|
Runtime.BLOCK_GLOW_OFF = 'BLOCK_GLOW_OFF';
|
||||||
|
@ -196,7 +199,6 @@ Runtime.prototype.clearEdgeActivatedValues = function () {
|
||||||
*/
|
*/
|
||||||
Runtime.prototype._pushThread = function (id) {
|
Runtime.prototype._pushThread = function (id) {
|
||||||
var thread = new Thread(id);
|
var thread = new Thread(id);
|
||||||
this.glowScript(id, true);
|
|
||||||
thread.pushStack(id);
|
thread.pushStack(id);
|
||||||
this.threads.push(thread);
|
this.threads.push(thread);
|
||||||
return thread;
|
return thread;
|
||||||
|
@ -209,7 +211,6 @@ Runtime.prototype._pushThread = function (id) {
|
||||||
Runtime.prototype._removeThread = function (thread) {
|
Runtime.prototype._removeThread = function (thread) {
|
||||||
var i = this.threads.indexOf(thread);
|
var i = this.threads.indexOf(thread);
|
||||||
if (i > -1) {
|
if (i > -1) {
|
||||||
this.glowScript(thread.topBlock, false);
|
|
||||||
this.threads.splice(i, 1);
|
this.threads.splice(i, 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -341,11 +342,6 @@ Runtime.prototype.stopAll = function () {
|
||||||
var threadsCopy = this.threads.slice();
|
var threadsCopy = this.threads.slice();
|
||||||
while (threadsCopy.length > 0) {
|
while (threadsCopy.length > 0) {
|
||||||
var poppedThread = threadsCopy.pop();
|
var poppedThread = threadsCopy.pop();
|
||||||
// Unglow any blocks on this thread's stack.
|
|
||||||
for (var i = 0; i < poppedThread.stack.length; i++) {
|
|
||||||
this.glowBlock(poppedThread.stack[i], false);
|
|
||||||
}
|
|
||||||
// Actually remove the thread.
|
|
||||||
this._removeThread(poppedThread);
|
this._removeThread(poppedThread);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -363,11 +359,55 @@ Runtime.prototype._step = function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var inactiveThreads = this.sequencer.stepThreads(this.threads);
|
var inactiveThreads = this.sequencer.stepThreads(this.threads);
|
||||||
|
this._updateScriptGlows();
|
||||||
for (var i = 0; i < inactiveThreads.length; i++) {
|
for (var i = 0; i < inactiveThreads.length; i++) {
|
||||||
this._removeThread(inactiveThreads[i]);
|
this._removeThread(inactiveThreads[i]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Runtime.prototype.setEditingTarget = function (editingTarget) {
|
||||||
|
this._scriptGlowsPreviousFrame = [];
|
||||||
|
this._editingTarget = editingTarget;
|
||||||
|
this._updateScriptGlows();
|
||||||
|
};
|
||||||
|
|
||||||
|
Runtime.prototype._updateScriptGlows = function () {
|
||||||
|
// Set of scripts that request a glow this frame.
|
||||||
|
var requestedGlowsThisFrame = [];
|
||||||
|
// Final set of scripts glowing during this frame.
|
||||||
|
var finalScriptGlows = [];
|
||||||
|
// Find all scripts that should be glowing.
|
||||||
|
for (var i = 0; i < this.threads.length; i++) {
|
||||||
|
var thread = this.threads[i];
|
||||||
|
var target = this.targetForThread(thread);
|
||||||
|
if (thread.requestScriptGlowInFrame && target == this._editingTarget) {
|
||||||
|
var blockForThread = thread.peekStack() || thread.topBlock;
|
||||||
|
var script = target.blocks.getTopLevelScript(blockForThread);
|
||||||
|
requestedGlowsThisFrame.push(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Compare to previous frame.
|
||||||
|
for (var j = 0; j < this._scriptGlowsPreviousFrame.length; j++) {
|
||||||
|
var previousFrameGlow = this._scriptGlowsPreviousFrame[j];
|
||||||
|
if (requestedGlowsThisFrame.indexOf(previousFrameGlow) < 0) {
|
||||||
|
// Glow turned off.
|
||||||
|
this.glowScript(previousFrameGlow, false);
|
||||||
|
} else {
|
||||||
|
// Still glowing.
|
||||||
|
finalScriptGlows.push(previousFrameGlow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var k = 0; k < requestedGlowsThisFrame.length; k++) {
|
||||||
|
var currentFrameGlow = requestedGlowsThisFrame[k];
|
||||||
|
if (this._scriptGlowsPreviousFrame.indexOf(currentFrameGlow) < 0) {
|
||||||
|
// Glow turned on.
|
||||||
|
this.glowScript(currentFrameGlow, true);
|
||||||
|
finalScriptGlows.push(currentFrameGlow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._scriptGlowsPreviousFrame = finalScriptGlows;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit feedback for block glowing (used in the sequencer).
|
* Emit feedback for block glowing (used in the sequencer).
|
||||||
* @param {?string} blockId ID for the block to update glow
|
* @param {?string} blockId ID for the block to update glow
|
||||||
|
@ -388,9 +428,9 @@ Runtime.prototype.glowBlock = function (blockId, isGlowing) {
|
||||||
*/
|
*/
|
||||||
Runtime.prototype.glowScript = function (topBlockId, isGlowing) {
|
Runtime.prototype.glowScript = function (topBlockId, isGlowing) {
|
||||||
if (isGlowing) {
|
if (isGlowing) {
|
||||||
this.emit(Runtime.STACK_GLOW_ON, topBlockId);
|
this.emit(Runtime.SCRIPT_GLOW_ON, topBlockId);
|
||||||
} else {
|
} else {
|
||||||
this.emit(Runtime.STACK_GLOW_OFF, topBlockId);
|
this.emit(Runtime.SCRIPT_GLOW_OFF, topBlockId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,12 @@ function Thread (firstBlock) {
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.status = 0; /* Thread.STATUS_RUNNING */
|
this.status = 0; /* Thread.STATUS_RUNNING */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the thread requests its script to glow during this frame.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.requestScriptGlowInFrame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -115,6 +115,7 @@ function parseScripts (scripts, blocks) {
|
||||||
parsedBlockList[0].x = scriptX * 1.1;
|
parsedBlockList[0].x = scriptX * 1.1;
|
||||||
parsedBlockList[0].y = scriptY * 1.1;
|
parsedBlockList[0].y = scriptY * 1.1;
|
||||||
parsedBlockList[0].topLevel = true;
|
parsedBlockList[0].topLevel = true;
|
||||||
|
parsedBlockList[0].parent = null;
|
||||||
}
|
}
|
||||||
// Flatten children and create add the blocks.
|
// Flatten children and create add the blocks.
|
||||||
var convertedBlocks = flatten(parsedBlockList);
|
var convertedBlocks = flatten(parsedBlockList);
|
||||||
|
@ -139,6 +140,7 @@ function parseBlockList (blockList) {
|
||||||
var block = blockList[i];
|
var block = blockList[i];
|
||||||
var parsedBlock = parseBlock(block);
|
var parsedBlock = parseBlock(block);
|
||||||
if (previousBlock) {
|
if (previousBlock) {
|
||||||
|
parsedBlock.parent = previousBlock.id;
|
||||||
previousBlock.next = parsedBlock.id;
|
previousBlock.next = parsedBlock.id;
|
||||||
}
|
}
|
||||||
previousBlock = parsedBlock;
|
previousBlock = parsedBlock;
|
||||||
|
@ -217,6 +219,9 @@ function parseBlock (sb2block) {
|
||||||
// Single block occupies the input.
|
// Single block occupies the input.
|
||||||
innerBlocks = [parseBlock(providedArg)];
|
innerBlocks = [parseBlock(providedArg)];
|
||||||
}
|
}
|
||||||
|
for (var j = 0; j < innerBlocks.length; j++) {
|
||||||
|
innerBlocks[j].parent = activeBlock.id;
|
||||||
|
}
|
||||||
// Obscures any shadow.
|
// Obscures any shadow.
|
||||||
shadowObscured = true;
|
shadowObscured = true;
|
||||||
activeBlock.inputs[expectedArg.inputName].block = (
|
activeBlock.inputs[expectedArg.inputName].block = (
|
||||||
|
@ -271,6 +276,7 @@ function parseBlock (sb2block) {
|
||||||
fields: fields,
|
fields: fields,
|
||||||
next: null,
|
next: null,
|
||||||
topLevel: false,
|
topLevel: false,
|
||||||
|
parent: activeBlock.id,
|
||||||
shadow: true
|
shadow: true
|
||||||
});
|
});
|
||||||
activeBlock.inputs[expectedArg.inputName].shadow = inputUid;
|
activeBlock.inputs[expectedArg.inputName].shadow = inputUid;
|
||||||
|
|
18
src/index.js
18
src/index.js
|
@ -31,11 +31,11 @@ function VirtualMachine () {
|
||||||
*/
|
*/
|
||||||
instance.editingTarget = null;
|
instance.editingTarget = null;
|
||||||
// Runtime emits are passed along as VM emits.
|
// Runtime emits are passed along as VM emits.
|
||||||
instance.runtime.on(Runtime.STACK_GLOW_ON, function (id) {
|
instance.runtime.on(Runtime.SCRIPT_GLOW_ON, function (id) {
|
||||||
instance.emit(Runtime.STACK_GLOW_ON, {id: id});
|
instance.emit(Runtime.SCRIPT_GLOW_ON, {id: id});
|
||||||
});
|
});
|
||||||
instance.runtime.on(Runtime.STACK_GLOW_OFF, function (id) {
|
instance.runtime.on(Runtime.SCRIPT_GLOW_OFF, function (id) {
|
||||||
instance.emit(Runtime.STACK_GLOW_OFF, {id: id});
|
instance.emit(Runtime.SCRIPT_GLOW_OFF, {id: id});
|
||||||
});
|
});
|
||||||
instance.runtime.on(Runtime.BLOCK_GLOW_ON, function (id) {
|
instance.runtime.on(Runtime.BLOCK_GLOW_ON, function (id) {
|
||||||
instance.emit(Runtime.BLOCK_GLOW_ON, {id: id});
|
instance.emit(Runtime.BLOCK_GLOW_ON, {id: id});
|
||||||
|
@ -114,6 +114,7 @@ VirtualMachine.prototype.loadProject = function (json) {
|
||||||
// Update the VM user's knowledge of targets and blocks on the workspace.
|
// Update the VM user's knowledge of targets and blocks on the workspace.
|
||||||
this.emitTargetsUpdate();
|
this.emitTargetsUpdate();
|
||||||
this.emitWorkspaceUpdate();
|
this.emitWorkspaceUpdate();
|
||||||
|
this.runtime.setEditingTarget(this.editingTarget);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,6 +136,7 @@ VirtualMachine.prototype.setEditingTarget = function (targetId) {
|
||||||
// Emit appropriate UI updates.
|
// Emit appropriate UI updates.
|
||||||
this.emitTargetsUpdate();
|
this.emitTargetsUpdate();
|
||||||
this.emitWorkspaceUpdate();
|
this.emitWorkspaceUpdate();
|
||||||
|
this.runtime.setEditingTarget(target);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -233,11 +235,11 @@ if (ENV_WORKER) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Bind runtime's emitted events to postmessages.
|
// Bind runtime's emitted events to postmessages.
|
||||||
self.vmInstance.runtime.on(Runtime.STACK_GLOW_ON, function (id) {
|
self.vmInstance.runtime.on(Runtime.SCRIPT_GLOW_ON, function (id) {
|
||||||
self.postMessage({method: Runtime.STACK_GLOW_ON, id: id});
|
self.postMessage({method: Runtime.SCRIPT_GLOW_ON, id: id});
|
||||||
});
|
});
|
||||||
self.vmInstance.runtime.on(Runtime.STACK_GLOW_OFF, function (id) {
|
self.vmInstance.runtime.on(Runtime.SCRIPT_GLOW_OFF, function (id) {
|
||||||
self.postMessage({method: Runtime.STACK_GLOW_OFF, id: id});
|
self.postMessage({method: Runtime.SCRIPT_GLOW_OFF, id: id});
|
||||||
});
|
});
|
||||||
self.vmInstance.runtime.on(Runtime.BLOCK_GLOW_ON, function (id) {
|
self.vmInstance.runtime.on(Runtime.BLOCK_GLOW_ON, function (id) {
|
||||||
self.postMessage({method: Runtime.BLOCK_GLOW_ON, id: id});
|
self.postMessage({method: Runtime.BLOCK_GLOW_ON, id: id});
|
||||||
|
|
Loading…
Reference in a new issue