mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-07-04 02:00:29 -04:00
Merge branch 'develop' into greenkeeper/tap-11.0.1
This commit is contained in:
commit
a9dd658445
18 changed files with 492 additions and 119 deletions
|
@ -59,6 +59,7 @@
|
||||||
"socket.io-client": "2.0.4",
|
"socket.io-client": "2.0.4",
|
||||||
"stats.js": "^0.17.0",
|
"stats.js": "^0.17.0",
|
||||||
"tap": "^11.0.1",
|
"tap": "^11.0.1",
|
||||||
|
"text-encoding": "0.6.4",
|
||||||
"tiny-worker": "^2.1.1",
|
"tiny-worker": "^2.1.1",
|
||||||
"webpack": "^3.10.0",
|
"webpack": "^3.10.0",
|
||||||
"webpack-dev-server": "^2.4.1",
|
"webpack-dev-server": "^2.4.1",
|
||||||
|
|
|
@ -233,21 +233,19 @@ class Scratch3LooksBlocks {
|
||||||
looks_cleargraphiceffects: this.clearEffects,
|
looks_cleargraphiceffects: this.clearEffects,
|
||||||
looks_changesizeby: this.changeSize,
|
looks_changesizeby: this.changeSize,
|
||||||
looks_setsizeto: this.setSize,
|
looks_setsizeto: this.setSize,
|
||||||
looks_gotofront: this.goToFront,
|
looks_gotofrontback: this.goToFrontBack,
|
||||||
looks_gobacklayers: this.goBackLayers,
|
looks_goforwardbackwardlayers: this.goForwardBackwardLayers,
|
||||||
looks_size: this.getSize,
|
looks_size: this.getSize,
|
||||||
looks_costumeorder: this.getCostumeIndex,
|
looks_costumenumbername: this.getCostumeNumberName,
|
||||||
looks_backdroporder: this.getBackdropIndex,
|
looks_backdropnumbername: this.getBackdropNumberName
|
||||||
looks_backdropname: this.getBackdropName
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getMonitored () {
|
getMonitored () {
|
||||||
return {
|
return {
|
||||||
looks_size: {isSpriteSpecific: true},
|
looks_size: {isSpriteSpecific: true},
|
||||||
looks_costumeorder: {isSpriteSpecific: true},
|
looks_costumenumbername: {isSpriteSpecific: true},
|
||||||
looks_backdroporder: {},
|
looks_backdropnumbername: {}
|
||||||
looks_backdropname: {}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,32 +407,45 @@ class Scratch3LooksBlocks {
|
||||||
util.target.setSize(size);
|
util.target.setSize(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
goToFront (args, util) {
|
goToFrontBack (args, util) {
|
||||||
if (!util.target.isStage) {
|
if (!util.target.isStage) {
|
||||||
util.target.goToFront();
|
if (args.FRONT_BACK === 'front') {
|
||||||
|
util.target.goToFront();
|
||||||
|
} else {
|
||||||
|
util.target.goToBack();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
goBackLayers (args, util) {
|
goForwardBackwardLayers (args, util) {
|
||||||
util.target.goBackLayers(args.NUM);
|
if (!util.target.isStage) {
|
||||||
|
if (args.FORWARD_BACKWARD === 'forward') {
|
||||||
|
util.target.goForwardLayers(Cast.toNumber(args.NUM));
|
||||||
|
} else {
|
||||||
|
util.target.goBackwardLayers(Cast.toNumber(args.NUM));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSize (args, util) {
|
getSize (args, util) {
|
||||||
return Math.round(util.target.size);
|
return Math.round(util.target.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBackdropIndex () {
|
getBackdropNumberName (args) {
|
||||||
const stage = this.runtime.getTargetForStage();
|
|
||||||
return stage.currentCostume + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
getBackdropName () {
|
|
||||||
const stage = this.runtime.getTargetForStage();
|
const stage = this.runtime.getTargetForStage();
|
||||||
|
if (args.NUMBER_NAME === 'number') {
|
||||||
|
return stage.currentCostume + 1;
|
||||||
|
}
|
||||||
|
// Else return name
|
||||||
return stage.sprite.costumes[stage.currentCostume].name;
|
return stage.sprite.costumes[stage.currentCostume].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCostumeIndex (args, util) {
|
getCostumeNumberName (args, util) {
|
||||||
return util.target.currentCostume + 1;
|
if (args.NUMBER_NAME === 'number') {
|
||||||
|
return util.target.currentCostume + 1;
|
||||||
|
}
|
||||||
|
// Else return name
|
||||||
|
return util.target.sprite.costumes[util.target.currentCostume].name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,8 +64,8 @@ class Scratch3MotionBlocks {
|
||||||
let targetX = 0;
|
let targetX = 0;
|
||||||
let targetY = 0;
|
let targetY = 0;
|
||||||
if (targetName === '_mouse_') {
|
if (targetName === '_mouse_') {
|
||||||
targetX = util.ioQuery('mouse', 'getX');
|
targetX = util.ioQuery('mouse', 'getScratchX');
|
||||||
targetY = util.ioQuery('mouse', 'getY');
|
targetY = util.ioQuery('mouse', 'getScratchY');
|
||||||
} else if (targetName === '_random_') {
|
} else if (targetName === '_random_') {
|
||||||
const stageWidth = this.runtime.constructor.STAGE_WIDTH;
|
const stageWidth = this.runtime.constructor.STAGE_WIDTH;
|
||||||
const stageHeight = this.runtime.constructor.STAGE_HEIGHT;
|
const stageHeight = this.runtime.constructor.STAGE_HEIGHT;
|
||||||
|
@ -106,8 +106,8 @@ class Scratch3MotionBlocks {
|
||||||
let targetX = 0;
|
let targetX = 0;
|
||||||
let targetY = 0;
|
let targetY = 0;
|
||||||
if (args.TOWARDS === '_mouse_') {
|
if (args.TOWARDS === '_mouse_') {
|
||||||
targetX = util.ioQuery('mouse', 'getX');
|
targetX = util.ioQuery('mouse', 'getScratchX');
|
||||||
targetY = util.ioQuery('mouse', 'getY');
|
targetY = util.ioQuery('mouse', 'getScratchY');
|
||||||
} else {
|
} else {
|
||||||
const pointTarget = this.runtime.getSpriteTargetByName(args.TOWARDS);
|
const pointTarget = this.runtime.getSpriteTargetByName(args.TOWARDS);
|
||||||
if (!pointTarget) return;
|
if (!pointTarget) return;
|
||||||
|
@ -155,7 +155,7 @@ class Scratch3MotionBlocks {
|
||||||
util.yield();
|
util.yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glideTo (args, util) {
|
glideTo (args, util) {
|
||||||
const targetXY = this.getTargetXY(args.TO, util);
|
const targetXY = this.getTargetXY(args.TO, util);
|
||||||
if (targetXY) {
|
if (targetXY) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ class Scratch3SensingBlocks {
|
||||||
sensing_of: this.getAttributeOf,
|
sensing_of: this.getAttributeOf,
|
||||||
sensing_mousex: this.getMouseX,
|
sensing_mousex: this.getMouseX,
|
||||||
sensing_mousey: this.getMouseY,
|
sensing_mousey: this.getMouseY,
|
||||||
|
sensing_setdragmode: this.setDragMode,
|
||||||
sensing_mousedown: this.getMouseDown,
|
sensing_mousedown: this.getMouseDown,
|
||||||
sensing_keypressed: this.getKeyPressed,
|
sensing_keypressed: this.getKeyPressed,
|
||||||
sensing_current: this.current,
|
sensing_current: this.current,
|
||||||
|
@ -55,7 +56,6 @@ class Scratch3SensingBlocks {
|
||||||
sensing_answer: {},
|
sensing_answer: {},
|
||||||
sensing_loudness: {},
|
sensing_loudness: {},
|
||||||
sensing_timer: {},
|
sensing_timer: {},
|
||||||
sensing_of: {},
|
|
||||||
sensing_current: {}
|
sensing_current: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -119,8 +119,8 @@ class Scratch3SensingBlocks {
|
||||||
touchingObject (args, util) {
|
touchingObject (args, util) {
|
||||||
const requestedObject = args.TOUCHINGOBJECTMENU;
|
const requestedObject = args.TOUCHINGOBJECTMENU;
|
||||||
if (requestedObject === '_mouse_') {
|
if (requestedObject === '_mouse_') {
|
||||||
const mouseX = util.ioQuery('mouse', 'getX');
|
const mouseX = util.ioQuery('mouse', 'getClientX');
|
||||||
const mouseY = util.ioQuery('mouse', 'getY');
|
const mouseY = util.ioQuery('mouse', 'getClientY');
|
||||||
return util.target.isTouchingPoint(mouseX, mouseY);
|
return util.target.isTouchingPoint(mouseX, mouseY);
|
||||||
} else if (requestedObject === '_edge_') {
|
} else if (requestedObject === '_edge_') {
|
||||||
return util.target.isTouchingEdge();
|
return util.target.isTouchingEdge();
|
||||||
|
@ -146,8 +146,8 @@ class Scratch3SensingBlocks {
|
||||||
let targetX = 0;
|
let targetX = 0;
|
||||||
let targetY = 0;
|
let targetY = 0;
|
||||||
if (args.DISTANCETOMENU === '_mouse_') {
|
if (args.DISTANCETOMENU === '_mouse_') {
|
||||||
targetX = util.ioQuery('mouse', 'getX');
|
targetX = util.ioQuery('mouse', 'getScratchX');
|
||||||
targetY = util.ioQuery('mouse', 'getY');
|
targetY = util.ioQuery('mouse', 'getScratchY');
|
||||||
} else {
|
} else {
|
||||||
const distTarget = this.runtime.getSpriteTargetByName(
|
const distTarget = this.runtime.getSpriteTargetByName(
|
||||||
args.DISTANCETOMENU
|
args.DISTANCETOMENU
|
||||||
|
@ -162,6 +162,10 @@ class Scratch3SensingBlocks {
|
||||||
return Math.sqrt((dx * dx) + (dy * dy));
|
return Math.sqrt((dx * dx) + (dy * dy));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDragMode (args, util) {
|
||||||
|
util.target.setDraggable(args.DRAG_MODE === 'draggable');
|
||||||
|
}
|
||||||
|
|
||||||
getTimer (args, util) {
|
getTimer (args, util) {
|
||||||
return util.ioQuery('clock', 'projectTimer');
|
return util.ioQuery('clock', 'projectTimer');
|
||||||
}
|
}
|
||||||
|
@ -171,11 +175,11 @@ class Scratch3SensingBlocks {
|
||||||
}
|
}
|
||||||
|
|
||||||
getMouseX (args, util) {
|
getMouseX (args, util) {
|
||||||
return util.ioQuery('mouse', 'getX');
|
return util.ioQuery('mouse', 'getScratchX');
|
||||||
}
|
}
|
||||||
|
|
||||||
getMouseY (args, util) {
|
getMouseY (args, util) {
|
||||||
return util.ioQuery('mouse', 'getY');
|
return util.ioQuery('mouse', 'getScratchY');
|
||||||
}
|
}
|
||||||
|
|
||||||
getMouseDown (args, util) {
|
getMouseDown (args, util) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ const mutationAdapter = require('./mutation-adapter');
|
||||||
const xmlEscape = require('../util/xml-escape');
|
const xmlEscape = require('../util/xml-escape');
|
||||||
const MonitorRecord = require('./monitor-record');
|
const MonitorRecord = require('./monitor-record');
|
||||||
const Clone = require('../util/clone');
|
const Clone = require('../util/clone');
|
||||||
|
const {Map} = require('immutable');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
|
@ -382,22 +383,38 @@ class Blocks {
|
||||||
block.fields[args.name].id = args.value;
|
block.fields[args.name].id = args.value;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Changing the value in a dropdown
|
||||||
block.fields[args.name].value = args.value;
|
block.fields[args.name].value = args.value;
|
||||||
|
|
||||||
|
if (!optRuntime){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const flyoutBlock = block.shadow && block.parent ? this._blocks[block.parent] : block;
|
||||||
|
if (flyoutBlock.isMonitored) {
|
||||||
|
optRuntime.requestUpdateMonitor(Map({
|
||||||
|
id: flyoutBlock.id,
|
||||||
|
params: this._getBlockParams(flyoutBlock)
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'mutation':
|
case 'mutation':
|
||||||
block.mutation = mutationAdapter(args.value);
|
block.mutation = mutationAdapter(args.value);
|
||||||
break;
|
break;
|
||||||
case 'checkbox':
|
case 'checkbox': {
|
||||||
block.isMonitored = args.value;
|
block.isMonitored = args.value;
|
||||||
if (optRuntime) {
|
if (!optRuntime) {
|
||||||
const isSpriteSpecific = optRuntime.monitorBlockInfo.hasOwnProperty(block.opcode) &&
|
break;
|
||||||
optRuntime.monitorBlockInfo[block.opcode].isSpriteSpecific;
|
|
||||||
block.targetId = isSpriteSpecific ? optRuntime.getEditingTarget().id : null;
|
|
||||||
}
|
}
|
||||||
if (optRuntime && wasMonitored && !block.isMonitored) {
|
|
||||||
|
const isSpriteSpecific = optRuntime.monitorBlockInfo.hasOwnProperty(block.opcode) &&
|
||||||
|
optRuntime.monitorBlockInfo[block.opcode].isSpriteSpecific;
|
||||||
|
block.targetId = isSpriteSpecific ? optRuntime.getEditingTarget().id : null;
|
||||||
|
|
||||||
|
if (wasMonitored && !block.isMonitored) {
|
||||||
optRuntime.requestRemoveMonitor(block.id);
|
optRuntime.requestRemoveMonitor(block.id);
|
||||||
} else if (optRuntime && !wasMonitored && block.isMonitored) {
|
} else if (!wasMonitored && block.isMonitored) {
|
||||||
optRuntime.requestAddMonitor(MonitorRecord({
|
optRuntime.requestAddMonitor(MonitorRecord({
|
||||||
// @todo(vm#564) this will collide if multiple sprites use same block
|
// @todo(vm#564) this will collide if multiple sprites use same block
|
||||||
id: block.id,
|
id: block.id,
|
||||||
|
@ -411,6 +428,7 @@ class Blocks {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.resetCache();
|
this.resetCache();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ const BlockUtility = require('./block-utility');
|
||||||
const log = require('../util/log');
|
const log = require('../util/log');
|
||||||
const Thread = require('./thread');
|
const Thread = require('./thread');
|
||||||
const {Map} = require('immutable');
|
const {Map} = require('immutable');
|
||||||
|
const cast = require('../util/cast');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single BlockUtility instance reused by execute for every pritimive ran.
|
* Single BlockUtility instance reused by execute for every pritimive ran.
|
||||||
|
@ -211,7 +212,30 @@ const execute = function (sequencer, thread) {
|
||||||
currentStackFrame.waitingReporter = null;
|
currentStackFrame.waitingReporter = null;
|
||||||
thread.popStack();
|
thread.popStack();
|
||||||
}
|
}
|
||||||
argValues[inputName] = currentStackFrame.reported[inputName];
|
const inputValue = currentStackFrame.reported[inputName];
|
||||||
|
if (inputName === 'BROADCAST_INPUT') {
|
||||||
|
const broadcastInput = inputs[inputName];
|
||||||
|
// Check if something is plugged into the broadcast block, or
|
||||||
|
// if the shadow dropdown menu is being used.
|
||||||
|
if (broadcastInput.block === broadcastInput.shadow) {
|
||||||
|
// Shadow dropdown menu is being used.
|
||||||
|
// Get the appropriate information out of it.
|
||||||
|
const shadow = blockContainer.getBlock(broadcastInput.shadow);
|
||||||
|
const broadcastField = shadow.fields.BROADCAST_OPTION;
|
||||||
|
argValues.BROADCAST_OPTION = {
|
||||||
|
id: broadcastField.id,
|
||||||
|
name: broadcastField.value
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Something is plugged into the broadcast input.
|
||||||
|
// Cast it to a string. We don't need an id here.
|
||||||
|
argValues.BROADCAST_OPTION = {
|
||||||
|
name: cast.toString(inputValue)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
argValues[inputName] = inputValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add any mutation to args (e.g., for procedures).
|
// Add any mutation to args (e.g., for procedures).
|
||||||
|
|
|
@ -98,12 +98,19 @@ class Target extends EventEmitter {
|
||||||
* if it exists.
|
* if it exists.
|
||||||
* @param {string} id Id of the variable.
|
* @param {string} id Id of the variable.
|
||||||
* @param {string} name Name of the variable.
|
* @param {string} name Name of the variable.
|
||||||
* @return {!Variable} Variable object.
|
* @return {?Variable} Variable object.
|
||||||
*/
|
*/
|
||||||
lookupBroadcastMsg (id, name) {
|
lookupBroadcastMsg (id, name) {
|
||||||
const broadcastMsg = this.lookupVariableById(id);
|
let broadcastMsg;
|
||||||
|
if (id) {
|
||||||
|
broadcastMsg = this.lookupVariableById(id);
|
||||||
|
} else if (name) {
|
||||||
|
broadcastMsg = this.lookupBroadcastByInputValue(name);
|
||||||
|
} else {
|
||||||
|
log.error('Cannot find broadcast message if neither id nor name are provided.');
|
||||||
|
}
|
||||||
if (broadcastMsg) {
|
if (broadcastMsg) {
|
||||||
if (broadcastMsg.name !== name) {
|
if (name && (broadcastMsg.name.toLowerCase() !== name.toLowerCase())) {
|
||||||
log.error(`Found broadcast message with id: ${id}, but` +
|
log.error(`Found broadcast message with id: ${id}, but` +
|
||||||
`its name, ${broadcastMsg.name} did not match expected name ${name}.`);
|
`its name, ${broadcastMsg.name} did not match expected name ${name}.`);
|
||||||
}
|
}
|
||||||
|
@ -115,6 +122,23 @@ class Target extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up a broadcast message with the given name and return the variable
|
||||||
|
* if it exists. Does not create a new broadcast message variable if
|
||||||
|
* it doesn't exist.
|
||||||
|
* @param {string} name Name of the variable.
|
||||||
|
* @return {?Variable} Variable object.
|
||||||
|
*/
|
||||||
|
lookupBroadcastByInputValue (name) {
|
||||||
|
const vars = this.variables;
|
||||||
|
for (const propName in vars) {
|
||||||
|
if ((vars[propName].type === Variable.BROADCAST_MESSAGE_TYPE) &&
|
||||||
|
(vars[propName].name.toLowerCase() === name.toLowerCase())) {
|
||||||
|
return vars[propName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up a variable object.
|
* Look up a variable object.
|
||||||
* Search begins for local variables; then look for globals.
|
* Search begins for local variables; then look for globals.
|
||||||
|
@ -141,7 +165,7 @@ class Target extends EventEmitter {
|
||||||
* Search begins for local lists; then look for globals.
|
* Search begins for local lists; then look for globals.
|
||||||
* @param {!string} id Id of the list.
|
* @param {!string} id Id of the list.
|
||||||
* @param {!string} name Name of the list.
|
* @param {!string} name Name of the list.
|
||||||
* @return {!List} List object.
|
* @return {!Varible} Variable object representing the found/created list.
|
||||||
*/
|
*/
|
||||||
lookupOrCreateList (id, name) {
|
lookupOrCreateList (id, name) {
|
||||||
const list = this.lookupVariableById(id);
|
const list = this.lookupVariableById(id);
|
||||||
|
|
|
@ -181,14 +181,18 @@ class Scratch3PenBlocks {
|
||||||
* @param {RenderedTarget} target - the target which has moved.
|
* @param {RenderedTarget} target - the target which has moved.
|
||||||
* @param {number} oldX - the previous X position.
|
* @param {number} oldX - the previous X position.
|
||||||
* @param {number} oldY - the previous Y position.
|
* @param {number} oldY - the previous Y position.
|
||||||
|
* @param {boolean} isForce - whether the movement was forced.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onTargetMoved (target, oldX, oldY) {
|
_onTargetMoved (target, oldX, oldY, isForce) {
|
||||||
const penSkinId = this._getPenLayerID();
|
// Only move the pen if the movement isn't forced (ie. dragged).
|
||||||
if (penSkinId >= 0) {
|
if (!isForce) {
|
||||||
const penState = this._getPenState(target);
|
const penSkinId = this._getPenLayerID();
|
||||||
this.runtime.renderer.penLine(penSkinId, penState.penAttributes, oldX, oldY, target.x, target.y);
|
if (penSkinId >= 0) {
|
||||||
this.runtime.requestRedraw();
|
const penState = this._getPenState(target);
|
||||||
|
this.runtime.renderer.penLine(penSkinId, penState.penAttributes, oldX, oldY, target.x, target.y);
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,7 +450,7 @@ class Scratch3PenBlocks {
|
||||||
blockType: BlockType.COMMAND,
|
blockType: BlockType.COMMAND,
|
||||||
text: formatMessage({
|
text: formatMessage({
|
||||||
id: 'pen.setHue',
|
id: 'pen.setHue',
|
||||||
default: 'set pen hue to [HUE]',
|
default: 'set pen color to [HUE]',
|
||||||
description: 'legacy pen blocks - set pen color to number'
|
description: 'legacy pen blocks - set pen color to number'
|
||||||
}),
|
}),
|
||||||
arguments: {
|
arguments: {
|
||||||
|
@ -462,7 +466,7 @@ class Scratch3PenBlocks {
|
||||||
blockType: BlockType.COMMAND,
|
blockType: BlockType.COMMAND,
|
||||||
text: formatMessage({
|
text: formatMessage({
|
||||||
id: 'pen.changeHue',
|
id: 'pen.changeHue',
|
||||||
default: 'change pen hue by [HUE]',
|
default: 'change pen color by [HUE]',
|
||||||
description: 'legacy pen blocks - change pen color'
|
description: 'legacy pen blocks - change pen color'
|
||||||
}),
|
}),
|
||||||
arguments: {
|
arguments: {
|
||||||
|
@ -673,6 +677,8 @@ class Scratch3PenBlocks {
|
||||||
const hueValue = Cast.toNumber(args.HUE);
|
const hueValue = Cast.toNumber(args.HUE);
|
||||||
const colorValue = hueValue / 2;
|
const colorValue = hueValue / 2;
|
||||||
this._setOrChangeColorParam(ColorParam.COLOR, colorValue, penState, false);
|
this._setOrChangeColorParam(ColorParam.COLOR, colorValue, penState, false);
|
||||||
|
|
||||||
|
this._legacyUpdatePenColor(penState);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -686,6 +692,8 @@ class Scratch3PenBlocks {
|
||||||
const hueChange = Cast.toNumber(args.HUE);
|
const hueChange = Cast.toNumber(args.HUE);
|
||||||
const colorChange = hueChange / 2;
|
const colorChange = hueChange / 2;
|
||||||
this._setOrChangeColorParam(ColorParam.COLOR, colorChange, penState, true);
|
this._setOrChangeColorParam(ColorParam.COLOR, colorChange, penState, true);
|
||||||
|
|
||||||
|
this._legacyUpdatePenColor(penState);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -705,25 +713,10 @@ class Scratch3PenBlocks {
|
||||||
newShade = newShade % 200;
|
newShade = newShade % 200;
|
||||||
if (newShade < 0) newShade += 200;
|
if (newShade < 0) newShade += 200;
|
||||||
|
|
||||||
// Create the new color in RGB using the scratch 2 "shade" model
|
|
||||||
let rgb = Color.hsvToRgb({h: penState.color * 360 / 100, s: 1, v: 1});
|
|
||||||
const shade = (newShade > 100) ? 200 - newShade : newShade;
|
|
||||||
if (shade < 50) {
|
|
||||||
rgb = Color.mixRgb(Color.RGB_BLACK, rgb, (10 + shade) / 60);
|
|
||||||
} else {
|
|
||||||
rgb = Color.mixRgb(rgb, Color.RGB_WHITE, (shade - 50) / 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the pen state according to new color
|
|
||||||
const hsv = Color.rgbToHsv(rgb);
|
|
||||||
penState.color = 100 * hsv.h / 360;
|
|
||||||
penState.saturation = 100 * hsv.s;
|
|
||||||
penState.brightness = 100 * hsv.v;
|
|
||||||
|
|
||||||
// And store the shade that was used to compute this new color for later use.
|
// And store the shade that was used to compute this new color for later use.
|
||||||
penState._shade = newShade;
|
penState._shade = newShade;
|
||||||
|
|
||||||
this._updatePenColor(penState);
|
this._legacyUpdatePenColor(penState);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -739,6 +732,29 @@ class Scratch3PenBlocks {
|
||||||
this.setPenShadeToNumber({SHADE: penState._shade + shadeChange}, util);
|
this.setPenShadeToNumber({SHADE: penState._shade + shadeChange}, util);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the pen state's color from its hue & shade values, Scratch 2.0 style.
|
||||||
|
* @param {object} penState - update the HSV & RGB values in this pen state from its hue & shade values.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_legacyUpdatePenColor (penState) {
|
||||||
|
// Create the new color in RGB using the scratch 2 "shade" model
|
||||||
|
let rgb = Color.hsvToRgb({h: penState.color * 360 / 100, s: 1, v: 1});
|
||||||
|
const shade = (penState._shade > 100) ? 200 - penState._shade : penState._shade;
|
||||||
|
if (shade < 50) {
|
||||||
|
rgb = Color.mixRgb(Color.RGB_BLACK, rgb, (10 + shade) / 60);
|
||||||
|
} else {
|
||||||
|
rgb = Color.mixRgb(rgb, Color.RGB_WHITE, (shade - 50) / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the pen state according to new color
|
||||||
|
const hsv = Color.rgbToHsv(rgb);
|
||||||
|
penState.color = 100 * hsv.h / 360;
|
||||||
|
penState.saturation = 100 * hsv.s;
|
||||||
|
penState.brightness = 100 * hsv.v;
|
||||||
|
|
||||||
|
this._updatePenColor(penState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Scratch3PenBlocks;
|
module.exports = Scratch3PenBlocks;
|
||||||
|
|
|
@ -40,10 +40,20 @@ class Mouse {
|
||||||
*/
|
*/
|
||||||
postData (data) {
|
postData (data) {
|
||||||
if (data.x) {
|
if (data.x) {
|
||||||
this._x = data.x - (data.canvasWidth / 2);
|
this._clientX = data.x;
|
||||||
|
this._scratchX = MathUtil.clamp(
|
||||||
|
480 * ((data.x / data.canvasWidth) - 0.5),
|
||||||
|
-240,
|
||||||
|
240
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (data.y) {
|
if (data.y) {
|
||||||
this._y = data.y - (data.canvasHeight / 2);
|
this._clientY = data.y;
|
||||||
|
this._scratchY = MathUtil.clamp(
|
||||||
|
-360 * ((data.y / data.canvasHeight) - 0.5),
|
||||||
|
-180,
|
||||||
|
180
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (typeof data.isDown !== 'undefined') {
|
if (typeof data.isDown !== 'undefined') {
|
||||||
this._isDown = data.isDown;
|
this._isDown = data.isDown;
|
||||||
|
@ -54,19 +64,35 @@ class Mouse {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the X position of the mouse.
|
* Get the X position of the mouse in client coordinates.
|
||||||
* @return {number} Clamped X position of the mouse cursor.
|
* @return {number} Non-clamped X position of the mouse cursor.
|
||||||
*/
|
*/
|
||||||
getX () {
|
getClientX () {
|
||||||
return MathUtil.clamp(this._x, -240, 240);
|
return this._clientX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the Y position of the mouse.
|
* Get the Y position of the mouse in client coordinates.
|
||||||
|
* @return {number} Non-clamped Y position of the mouse cursor.
|
||||||
|
*/
|
||||||
|
getClientY () {
|
||||||
|
return this._clientY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the X position of the mouse in scratch coordinates.
|
||||||
|
* @return {number} Clamped X position of the mouse cursor.
|
||||||
|
*/
|
||||||
|
getScratchX () {
|
||||||
|
return this._scratchX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Y position of the mouse in scratch coordinates.
|
||||||
* @return {number} Clamped Y position of the mouse cursor.
|
* @return {number} Clamped Y position of the mouse cursor.
|
||||||
*/
|
*/
|
||||||
getY () {
|
getScratchY () {
|
||||||
return MathUtil.clamp(-this._y, -180, 180);
|
return this._scratchY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -170,14 +170,23 @@ const generateVariableIdGetter = (function () {
|
||||||
|
|
||||||
const globalBroadcastMsgStateGenerator = (function () {
|
const globalBroadcastMsgStateGenerator = (function () {
|
||||||
let broadcastMsgNameMap = {};
|
let broadcastMsgNameMap = {};
|
||||||
|
const allBroadcastFields = [];
|
||||||
|
const emptyStringName = uid();
|
||||||
return function (topLevel) {
|
return function (topLevel) {
|
||||||
if (topLevel) broadcastMsgNameMap = {};
|
if (topLevel) broadcastMsgNameMap = {};
|
||||||
return {
|
return {
|
||||||
broadcastMsgMapUpdater: function (name) {
|
broadcastMsgMapUpdater: function (name, field) {
|
||||||
|
name = name.toLowerCase();
|
||||||
|
if (name === '') {
|
||||||
|
name = emptyStringName;
|
||||||
|
}
|
||||||
broadcastMsgNameMap[name] = `broadcastMsgId-${name}`;
|
broadcastMsgNameMap[name] = `broadcastMsgId-${name}`;
|
||||||
|
allBroadcastFields.push(field);
|
||||||
return broadcastMsgNameMap[name];
|
return broadcastMsgNameMap[name];
|
||||||
},
|
},
|
||||||
globalBroadcastMsgs: broadcastMsgNameMap
|
globalBroadcastMsgs: broadcastMsgNameMap,
|
||||||
|
allBroadcastFields: allBroadcastFields,
|
||||||
|
emptyMsgName: emptyStringName
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
@ -340,6 +349,31 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
|
||||||
// all other targets have finished processing.
|
// all other targets have finished processing.
|
||||||
if (target.isStage) {
|
if (target.isStage) {
|
||||||
const allBroadcastMsgs = globalBroadcastMsgObj.globalBroadcastMsgs;
|
const allBroadcastMsgs = globalBroadcastMsgObj.globalBroadcastMsgs;
|
||||||
|
const allBroadcastMsgFields = globalBroadcastMsgObj.allBroadcastFields;
|
||||||
|
const oldEmptyMsgName = globalBroadcastMsgObj.emptyMsgName;
|
||||||
|
if (allBroadcastMsgs[oldEmptyMsgName]) {
|
||||||
|
// Find a fresh 'messageN'
|
||||||
|
let currIndex = 1;
|
||||||
|
while (allBroadcastMsgs[`message${currIndex}`]) {
|
||||||
|
currIndex += 1;
|
||||||
|
}
|
||||||
|
const newEmptyMsgName = `message${currIndex}`;
|
||||||
|
// Add the new empty message name to the broadcast message
|
||||||
|
// name map, and assign it the old id.
|
||||||
|
// Then, delete the old entry in map.
|
||||||
|
allBroadcastMsgs[newEmptyMsgName] = allBroadcastMsgs[oldEmptyMsgName];
|
||||||
|
delete allBroadcastMsgs[oldEmptyMsgName];
|
||||||
|
// Now update all the broadcast message fields with
|
||||||
|
// the new empty message name.
|
||||||
|
for (let i = 0; i < allBroadcastMsgFields.length; i++) {
|
||||||
|
if (allBroadcastMsgFields[i].value === '') {
|
||||||
|
allBroadcastMsgFields[i].value = newEmptyMsgName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Traverse the broadcast message name map and create
|
||||||
|
// broadcast messages as variables on the stage (which is this
|
||||||
|
// target).
|
||||||
for (const msgName in allBroadcastMsgs) {
|
for (const msgName in allBroadcastMsgs) {
|
||||||
const msgId = allBroadcastMsgs[msgName];
|
const msgId = allBroadcastMsgs[msgName];
|
||||||
const newMsg = new Variable(
|
const newMsg = new Variable(
|
||||||
|
@ -494,6 +528,11 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension
|
||||||
if (shadowObscured) {
|
if (shadowObscured) {
|
||||||
fieldValue = '#990000';
|
fieldValue = '#990000';
|
||||||
}
|
}
|
||||||
|
} else if (expectedArg.inputOp === 'event_broadcast_menu') {
|
||||||
|
fieldName = 'BROADCAST_OPTION';
|
||||||
|
if (shadowObscured) {
|
||||||
|
fieldValue = '';
|
||||||
|
}
|
||||||
} else if (shadowObscured) {
|
} else if (shadowObscured) {
|
||||||
// Filled drop-down menu.
|
// Filled drop-down menu.
|
||||||
fieldValue = '';
|
fieldValue = '';
|
||||||
|
@ -503,6 +542,23 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension
|
||||||
name: fieldName,
|
name: fieldName,
|
||||||
value: fieldValue
|
value: fieldValue
|
||||||
};
|
};
|
||||||
|
// event_broadcast_menus have some extra properties to add to the
|
||||||
|
// field and a different value than the rest
|
||||||
|
if (expectedArg.inputOp === 'event_broadcast_menu') {
|
||||||
|
if (!shadowObscured) {
|
||||||
|
// Need to update the broadcast message name map with
|
||||||
|
// the value of this field.
|
||||||
|
// Also need to provide the fields[fieldName] object,
|
||||||
|
// so that we can later update its value property, e.g.
|
||||||
|
// if sb2 message name is empty string, we will later
|
||||||
|
// replace this field's value with messageN
|
||||||
|
// once we can traverse through all the existing message names
|
||||||
|
// and come up with a fresh messageN.
|
||||||
|
const broadcastId = addBroadcastMsg(fieldValue, fields[fieldName]);
|
||||||
|
fields[fieldName].id = broadcastId;
|
||||||
|
}
|
||||||
|
fields[fieldName].variableType = expectedArg.variableType;
|
||||||
|
}
|
||||||
activeBlock.children.push({
|
activeBlock.children.push({
|
||||||
id: inputUid,
|
id: inputUid,
|
||||||
opcode: expectedArg.inputOp,
|
opcode: expectedArg.inputOp,
|
||||||
|
@ -529,8 +585,14 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension
|
||||||
// Add `id` property to variable fields
|
// Add `id` property to variable fields
|
||||||
activeBlock.fields[expectedArg.fieldName].id = getVariableId(providedArg);
|
activeBlock.fields[expectedArg.fieldName].id = getVariableId(providedArg);
|
||||||
} else if (expectedArg.fieldName === 'BROADCAST_OPTION') {
|
} else if (expectedArg.fieldName === 'BROADCAST_OPTION') {
|
||||||
// add the name in this field to the broadcast msg name map
|
// Add the name in this field to the broadcast msg name map.
|
||||||
const broadcastId = addBroadcastMsg(providedArg);
|
// Also need to provide the fields[fieldName] object,
|
||||||
|
// so that we can later update its value property, e.g.
|
||||||
|
// if sb2 message name is empty string, we will later
|
||||||
|
// replace this field's value with messageN
|
||||||
|
// once we can traverse through all the existing message names
|
||||||
|
// and come up with a fresh messageN.
|
||||||
|
const broadcastId = addBroadcastMsg(providedArg, activeBlock.fields[expectedArg.fieldName]);
|
||||||
activeBlock.fields[expectedArg.fieldName].id = broadcastId;
|
activeBlock.fields[expectedArg.fieldName].id = broadcastId;
|
||||||
}
|
}
|
||||||
const varType = expectedArg.variableType;
|
const varType = expectedArg.variableType;
|
||||||
|
@ -539,6 +601,41 @@ const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extension
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates for blocks that have new menus (e.g. in Looks)
|
||||||
|
switch (oldOpcode) {
|
||||||
|
case 'comeToFront':
|
||||||
|
activeBlock.fields.FRONT_BACK = {
|
||||||
|
name: 'FRONT_BACK',
|
||||||
|
value: 'front'
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'goBackByLayers:':
|
||||||
|
activeBlock.fields.FORWARD_BACKWARD = {
|
||||||
|
name: 'FORWARD_BACKWARD',
|
||||||
|
value: 'backward'
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'backgroundIndex':
|
||||||
|
activeBlock.fields.NUMBER_NAME = {
|
||||||
|
name: 'NUMBER_NAME',
|
||||||
|
value: 'number'
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'sceneName':
|
||||||
|
activeBlock.fields.NUMBER_NAME = {
|
||||||
|
name: 'NUMBER_NAME',
|
||||||
|
value: 'name'
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'costumeIndex':
|
||||||
|
activeBlock.fields.NUMBER_NAME = {
|
||||||
|
name: 'NUMBER_NAME',
|
||||||
|
value: 'number'
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Special cases to generate mutations.
|
// Special cases to generate mutations.
|
||||||
if (oldOpcode === 'stopScripts') {
|
if (oldOpcode === 'stopScripts') {
|
||||||
// Mutation for stop block: if the argument is 'other scripts',
|
// Mutation for stop block: if the argument is 'other scripts',
|
||||||
|
|
|
@ -345,12 +345,12 @@ const specMap = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'comeToFront': {
|
'comeToFront': {
|
||||||
opcode: 'looks_gotofront',
|
opcode: 'looks_gotofrontback',
|
||||||
argMap: [
|
argMap: [
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'goBackByLayers:': {
|
'goBackByLayers:': {
|
||||||
opcode: 'looks_gobacklayers',
|
opcode: 'looks_goforwardbackwardlayers',
|
||||||
argMap: [
|
argMap: [
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
|
@ -360,12 +360,12 @@ const specMap = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'costumeIndex': {
|
'costumeIndex': {
|
||||||
opcode: 'looks_costumeorder',
|
opcode: 'looks_costumenumbername',
|
||||||
argMap: [
|
argMap: [
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'sceneName': {
|
'sceneName': {
|
||||||
opcode: 'looks_backdropname',
|
opcode: 'looks_backdropnumbername',
|
||||||
argMap: [
|
argMap: [
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -390,7 +390,7 @@ const specMap = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'backgroundIndex': {
|
'backgroundIndex': {
|
||||||
opcode: 'looks_backdroporder',
|
opcode: 'looks_backdropnumbername',
|
||||||
argMap: [
|
argMap: [
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -665,8 +665,9 @@ const specMap = {
|
||||||
opcode: 'event_broadcast',
|
opcode: 'event_broadcast',
|
||||||
argMap: [
|
argMap: [
|
||||||
{
|
{
|
||||||
type: 'field',
|
type: 'input',
|
||||||
fieldName: 'BROADCAST_OPTION',
|
inputOp: 'event_broadcast_menu',
|
||||||
|
inputName: 'BROADCAST_INPUT',
|
||||||
variableType: Variable.BROADCAST_MESSAGE_TYPE
|
variableType: Variable.BROADCAST_MESSAGE_TYPE
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -675,8 +676,9 @@ const specMap = {
|
||||||
opcode: 'event_broadcastandwait',
|
opcode: 'event_broadcastandwait',
|
||||||
argMap: [
|
argMap: [
|
||||||
{
|
{
|
||||||
type: 'field',
|
type: 'input',
|
||||||
fieldName: 'BROADCAST_OPTION',
|
inputOp: 'event_broadcast_menu',
|
||||||
|
inputName: 'BROADCAST_INPUT',
|
||||||
variableType: Variable.BROADCAST_MESSAGE_TYPE
|
variableType: Variable.BROADCAST_MESSAGE_TYPE
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -859,9 +861,8 @@ const specMap = {
|
||||||
opcode: 'sensing_keypressed',
|
opcode: 'sensing_keypressed',
|
||||||
argMap: [
|
argMap: [
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'field',
|
||||||
inputOp: 'sensing_keyoptions',
|
fieldName: 'KEY_OPTION'
|
||||||
inputName: 'KEY_OPTION'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -934,9 +935,8 @@ const specMap = {
|
||||||
opcode: 'sensing_of',
|
opcode: 'sensing_of',
|
||||||
argMap: [
|
argMap: [
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'field',
|
||||||
inputOp: 'sensing_of_property_menu',
|
fieldName: 'PROPERTY'
|
||||||
inputName: 'PROPERTY'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
|
|
|
@ -203,7 +203,7 @@ class RenderedTarget extends Target {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
this.emit(RenderedTarget.EVENT_TARGET_MOVED, this, oldX, oldY);
|
this.emit(RenderedTarget.EVENT_TARGET_MOVED, this, oldX, oldY, force);
|
||||||
this.runtime.requestTargetsUpdate(this);
|
this.runtime.requestTargetsUpdate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,8 +616,7 @@ class RenderedTarget extends Target {
|
||||||
// Limits test to this Drawable, so this will return true
|
// Limits test to this Drawable, so this will return true
|
||||||
// even if the clone is obscured by another Drawable.
|
// even if the clone is obscured by another Drawable.
|
||||||
const pickResult = this.runtime.renderer.pick(
|
const pickResult = this.runtime.renderer.pick(
|
||||||
x + (this.runtime.constructor.STAGE_WIDTH / 2),
|
x, y,
|
||||||
-y + (this.runtime.constructor.STAGE_HEIGHT / 2),
|
|
||||||
null, null,
|
null, null,
|
||||||
[this.drawableID]
|
[this.drawableID]
|
||||||
);
|
);
|
||||||
|
@ -699,10 +698,29 @@ class RenderedTarget extends Target {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move back a number of layers.
|
* Move to the back layer.
|
||||||
* @param {number} nLayers How many layers to go back.
|
|
||||||
*/
|
*/
|
||||||
goBackLayers (nLayers) {
|
goToBack () {
|
||||||
|
if (this.renderer) {
|
||||||
|
this.renderer.setDrawableOrder(this.drawableID, -Infinity, false, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move forward a number of layers.
|
||||||
|
* @param {number} nLayers How many layers to go forward.
|
||||||
|
*/
|
||||||
|
goForwardLayers (nLayers) {
|
||||||
|
if (this.renderer) {
|
||||||
|
this.renderer.setDrawableOrder(this.drawableID, nLayers, true, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move backward a number of layers.
|
||||||
|
* @param {number} nLayers How many layers to go backward.
|
||||||
|
*/
|
||||||
|
goBackwardLayers (nLayers) {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
this.renderer.setDrawableOrder(this.drawableID, -nLayers, true, 1);
|
this.renderer.setDrawableOrder(this.drawableID, -nLayers, true, 1);
|
||||||
}
|
}
|
||||||
|
@ -812,7 +830,6 @@ class RenderedTarget extends Target {
|
||||||
newTarget.effects = JSON.parse(JSON.stringify(this.effects));
|
newTarget.effects = JSON.parse(JSON.stringify(this.effects));
|
||||||
newTarget.variables = JSON.parse(JSON.stringify(this.variables));
|
newTarget.variables = JSON.parse(JSON.stringify(this.variables));
|
||||||
newTarget.lists = JSON.parse(JSON.stringify(this.lists));
|
newTarget.lists = JSON.parse(JSON.stringify(this.lists));
|
||||||
newTarget.initDrawable();
|
|
||||||
newTarget.updateAllDrawableProperties();
|
newTarget.updateAllDrawableProperties();
|
||||||
newTarget.goBehindOther(this);
|
newTarget.goBehindOther(this);
|
||||||
return newTarget;
|
return newTarget;
|
||||||
|
@ -845,11 +862,10 @@ class RenderedTarget extends Target {
|
||||||
*/
|
*/
|
||||||
postSpriteInfo (data) {
|
postSpriteInfo (data) {
|
||||||
const force = data.hasOwnProperty('force') ? data.force : null;
|
const force = data.hasOwnProperty('force') ? data.force : null;
|
||||||
if (data.hasOwnProperty('x')) {
|
const isXChanged = data.hasOwnProperty('x');
|
||||||
this.setXY(data.x, this.y, force);
|
const isYChanged = data.hasOwnProperty('y');
|
||||||
}
|
if (isXChanged || isYChanged) {
|
||||||
if (data.hasOwnProperty('y')) {
|
this.setXY(isXChanged ? data.x : this.x, isYChanged ? data.y : this.y, force);
|
||||||
this.setXY(this.x, data.y, force);
|
|
||||||
}
|
}
|
||||||
if (data.hasOwnProperty('direction')) {
|
if (data.hasOwnProperty('direction')) {
|
||||||
this.setDirection(data.direction);
|
this.setDirection(data.direction);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const TextEncoder = require('text-encoding').TextEncoder;
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
|
|
||||||
const centralDispatch = require('./dispatch/central-dispatch');
|
const centralDispatch = require('./dispatch/central-dispatch');
|
||||||
|
@ -8,6 +9,7 @@ const sb2 = require('./serialization/sb2');
|
||||||
const sb3 = require('./serialization/sb3');
|
const sb3 = require('./serialization/sb3');
|
||||||
const StringUtil = require('./util/string-util');
|
const StringUtil = require('./util/string-util');
|
||||||
const formatMessage = require('format-message');
|
const formatMessage = require('format-message');
|
||||||
|
const Variable = require('./engine/variable');
|
||||||
|
|
||||||
const {loadCostume} = require('./import/load-costume.js');
|
const {loadCostume} = require('./import/load-costume.js');
|
||||||
const {loadSound} = require('./import/load-sound.js');
|
const {loadSound} = require('./import/load-sound.js');
|
||||||
|
@ -267,6 +269,8 @@ class VirtualMachine extends EventEmitter {
|
||||||
targets.forEach(target => {
|
targets.forEach(target => {
|
||||||
this.runtime.targets.push(target);
|
this.runtime.targets.push(target);
|
||||||
(/** @type RenderedTarget */ target).updateAllDrawableProperties();
|
(/** @type RenderedTarget */ target).updateAllDrawableProperties();
|
||||||
|
// Ensure unique sprite name
|
||||||
|
if (target.isSprite()) this.renameSprite(target.id, target.getName());
|
||||||
});
|
});
|
||||||
// Select the first target for editing, e.g., the first sprite.
|
// Select the first target for editing, e.g., the first sprite.
|
||||||
if (wholeProject && (targets.length > 1)) {
|
if (wholeProject && (targets.length > 1)) {
|
||||||
|
@ -447,7 +451,7 @@ class VirtualMachine extends EventEmitter {
|
||||||
addBackdrop (md5ext, backdropObject) {
|
addBackdrop (md5ext, backdropObject) {
|
||||||
return loadCostume(md5ext, backdropObject, this.runtime).then(() => {
|
return loadCostume(md5ext, backdropObject, this.runtime).then(() => {
|
||||||
const stage = this.runtime.getTargetForStage();
|
const stage = this.runtime.getTargetForStage();
|
||||||
stage.sprite.costumes.push(backdropObject);
|
stage.addCostume(backdropObject);
|
||||||
stage.setCostume(stage.sprite.costumes.length - 1);
|
stage.setCostume(stage.sprite.costumes.length - 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -628,7 +632,7 @@ class VirtualMachine extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
setEditingTarget (targetId) {
|
setEditingTarget (targetId) {
|
||||||
// Has the target id changed? If not, exit.
|
// Has the target id changed? If not, exit.
|
||||||
if (targetId === this.editingTarget.id) {
|
if (this.editingTarget && targetId === this.editingTarget.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target = this.runtime.getTargetById(targetId);
|
const target = this.runtime.getTargetById(targetId);
|
||||||
|
@ -677,6 +681,35 @@ class VirtualMachine extends EventEmitter {
|
||||||
* of the current editing target's blocks.
|
* of the current editing target's blocks.
|
||||||
*/
|
*/
|
||||||
emitWorkspaceUpdate () {
|
emitWorkspaceUpdate () {
|
||||||
|
// Create a list of broadcast message Ids according to the stage variables
|
||||||
|
const stageVariables = this.runtime.getTargetForStage().variables;
|
||||||
|
let messageIds = [];
|
||||||
|
for (const varId in stageVariables) {
|
||||||
|
if (stageVariables[varId].type === Variable.BROADCAST_MESSAGE_TYPE) {
|
||||||
|
messageIds.push(varId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Go through all blocks on all targets, removing referenced
|
||||||
|
// broadcast ids from the list.
|
||||||
|
for (let i = 0; i < this.runtime.targets.length; i++) {
|
||||||
|
const currTarget = this.runtime.targets[i];
|
||||||
|
const currBlocks = currTarget.blocks._blocks;
|
||||||
|
for (const blockId in currBlocks) {
|
||||||
|
if (currBlocks[blockId].fields.BROADCAST_OPTION) {
|
||||||
|
const id = currBlocks[blockId].fields.BROADCAST_OPTION.id;
|
||||||
|
const index = messageIds.indexOf(id);
|
||||||
|
if (index !== -1) {
|
||||||
|
messageIds = messageIds.slice(0, index)
|
||||||
|
.concat(messageIds.slice(index + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Anything left in messageIds is not referenced by a block, so delete it.
|
||||||
|
for (let i = 0; i < messageIds.length; i++) {
|
||||||
|
const id = messageIds[i];
|
||||||
|
delete this.runtime.getTargetForStage().variables[id];
|
||||||
|
}
|
||||||
const variableMap = Object.assign({},
|
const variableMap = Object.assign({},
|
||||||
this.runtime.getTargetForStage().variables,
|
this.runtime.getTargetForStage().variables,
|
||||||
this.editingTarget.variables
|
this.editingTarget.variables
|
||||||
|
|
52
test/unit/blocks_looks.js
Normal file
52
test/unit/blocks_looks.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
const test = require('tap').test;
|
||||||
|
const Looks = require('../../src/blocks/scratch3_looks');
|
||||||
|
const util = {
|
||||||
|
target: {
|
||||||
|
currentCostume: 0, // Internally, current costume is 0 indexed
|
||||||
|
sprite: {
|
||||||
|
costumes: [
|
||||||
|
{name: 'first name'},
|
||||||
|
{name: 'second name'},
|
||||||
|
{name: 'third name'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fakeRuntime = {
|
||||||
|
getTargetForStage: () => util.target, // Just return the dummy target above.
|
||||||
|
on: () => {} // Stub out listener methods used in constructor.
|
||||||
|
};
|
||||||
|
const blocks = new Looks(fakeRuntime);
|
||||||
|
|
||||||
|
test('getCostumeNumberName returns 1-indexed costume number', t => {
|
||||||
|
util.target.currentCostume = 0; // This is 0-indexed.
|
||||||
|
const args = {NUMBER_NAME: 'number'};
|
||||||
|
const number = blocks.getCostumeNumberName(args, util);
|
||||||
|
t.strictEqual(number, 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getCostumeNumberName can return costume name', t => {
|
||||||
|
util.target.currentCostume = 0; // This is 0-indexed.
|
||||||
|
const args = {NUMBER_NAME: 'name'};
|
||||||
|
const number = blocks.getCostumeNumberName(args, util);
|
||||||
|
t.strictEqual(number, 'first name');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getBackdropNumberName returns 1-indexed costume number', t => {
|
||||||
|
util.target.currentCostume = 2; // This is 0-indexed.
|
||||||
|
const args = {NUMBER_NAME: 'number'};
|
||||||
|
const number = blocks.getBackdropNumberName(args, util);
|
||||||
|
t.strictEqual(number, 3);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getBackdropNumberName can return costume name', t => {
|
||||||
|
util.target.currentCostume = 2; // This is 0-indexed.
|
||||||
|
const args = {NUMBER_NAME: 'name'};
|
||||||
|
const number = blocks.getBackdropNumberName(args, util);
|
||||||
|
t.strictEqual(number, 'third name');
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -1,6 +1,8 @@
|
||||||
const test = require('tap').test;
|
const test = require('tap').test;
|
||||||
const Sensing = require('../../src/blocks/scratch3_sensing');
|
const Sensing = require('../../src/blocks/scratch3_sensing');
|
||||||
const Runtime = require('../../src/engine/runtime');
|
const Runtime = require('../../src/engine/runtime');
|
||||||
|
const Sprite = require('../../src/sprites/sprite');
|
||||||
|
const RenderedTarget = require('../../src/sprites/rendered-target');
|
||||||
|
|
||||||
test('getPrimitives', t => {
|
test('getPrimitives', t => {
|
||||||
const rt = new Runtime();
|
const rt = new Runtime();
|
||||||
|
@ -65,3 +67,19 @@ test('ask and answer with a visible target', t => {
|
||||||
|
|
||||||
s.askAndWait({QUESTION: expectedQuestion}, util);
|
s.askAndWait({QUESTION: expectedQuestion}, util);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('set drag mode', t => {
|
||||||
|
const runtime = new Runtime();
|
||||||
|
runtime.requestTargetsUpdate = () => {}; // noop for testing
|
||||||
|
const sensing = new Sensing(runtime);
|
||||||
|
const s = new Sprite();
|
||||||
|
const rt = new RenderedTarget(s, runtime);
|
||||||
|
|
||||||
|
sensing.setDragMode({DRAG_MODE: 'not draggable'}, {target: rt});
|
||||||
|
t.strictEqual(rt.draggable, false);
|
||||||
|
|
||||||
|
sensing.setDragMode({DRAG_MODE: 'draggable'}, {target: rt});
|
||||||
|
t.strictEqual(rt.draggable, true);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
|
@ -8,8 +8,10 @@ test('spec', t => {
|
||||||
|
|
||||||
t.type(m, 'object');
|
t.type(m, 'object');
|
||||||
t.type(m.postData, 'function');
|
t.type(m.postData, 'function');
|
||||||
t.type(m.getX, 'function');
|
t.type(m.getClientX, 'function');
|
||||||
t.type(m.getY, 'function');
|
t.type(m.getClientY, 'function');
|
||||||
|
t.type(m.getScratchX, 'function');
|
||||||
|
t.type(m.getScratchY, 'function');
|
||||||
t.type(m.getIsDown, 'function');
|
t.type(m.getIsDown, 'function');
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
@ -19,14 +21,16 @@ test('mouseUp', t => {
|
||||||
const m = new Mouse(rt);
|
const m = new Mouse(rt);
|
||||||
|
|
||||||
m.postData({
|
m.postData({
|
||||||
x: 1,
|
x: -20,
|
||||||
y: 10,
|
y: 10,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
canvasWidth: 480,
|
canvasWidth: 480,
|
||||||
canvasHeight: 360
|
canvasHeight: 360
|
||||||
});
|
});
|
||||||
t.strictEquals(m.getX(), -239);
|
t.strictEquals(m.getClientX(), -20);
|
||||||
t.strictEquals(m.getY(), 170);
|
t.strictEquals(m.getClientY(), 10);
|
||||||
|
t.strictEquals(m.getScratchX(), -240);
|
||||||
|
t.strictEquals(m.getScratchY(), 170);
|
||||||
t.strictEquals(m.getIsDown(), false);
|
t.strictEquals(m.getIsDown(), false);
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
@ -37,13 +41,32 @@ test('mouseDown', t => {
|
||||||
|
|
||||||
m.postData({
|
m.postData({
|
||||||
x: 10,
|
x: 10,
|
||||||
y: 100,
|
y: 400,
|
||||||
isDown: true,
|
isDown: true,
|
||||||
canvasWidth: 480,
|
canvasWidth: 480,
|
||||||
canvasHeight: 360
|
canvasHeight: 360
|
||||||
});
|
});
|
||||||
t.strictEquals(m.getX(), -230);
|
t.strictEquals(m.getClientX(), 10);
|
||||||
t.strictEquals(m.getY(), 80);
|
t.strictEquals(m.getClientY(), 400);
|
||||||
|
t.strictEquals(m.getScratchX(), -230);
|
||||||
|
t.strictEquals(m.getScratchY(), -180);
|
||||||
t.strictEquals(m.getIsDown(), true);
|
t.strictEquals(m.getIsDown(), true);
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('at zoomed scale', t => {
|
||||||
|
const rt = new Runtime();
|
||||||
|
const m = new Mouse(rt);
|
||||||
|
|
||||||
|
m.postData({
|
||||||
|
x: 240,
|
||||||
|
y: 540,
|
||||||
|
canvasWidth: 960,
|
||||||
|
canvasHeight: 720
|
||||||
|
});
|
||||||
|
t.strictEquals(m.getClientX(), 240);
|
||||||
|
t.strictEquals(m.getClientY(), 540);
|
||||||
|
t.strictEquals(m.getScratchX(), -120);
|
||||||
|
t.strictEquals(m.getScratchY(), -90);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
|
@ -293,8 +293,12 @@ test('layers', t => {
|
||||||
a.renderer = renderer;
|
a.renderer = renderer;
|
||||||
a.goToFront();
|
a.goToFront();
|
||||||
t.equals(a.renderer.order, 5);
|
t.equals(a.renderer.order, 5);
|
||||||
a.goBackLayers(2);
|
a.goBackwardLayers(2);
|
||||||
t.equals(a.renderer.order, 3);
|
t.equals(a.renderer.order, 3);
|
||||||
|
a.goToBack();
|
||||||
|
t.equals(a.renderer.order, 1);
|
||||||
|
a.goForwardLayers(1);
|
||||||
|
t.equals(a.renderer.order, 2);
|
||||||
o.drawableID = 999;
|
o.drawableID = 999;
|
||||||
a.goBehindOther(o);
|
a.goBehindOther(o);
|
||||||
t.equals(a.renderer.order, 1);
|
t.equals(a.renderer.order, 1);
|
||||||
|
|
|
@ -287,12 +287,18 @@ test('emitWorkspaceUpdate', t => {
|
||||||
global: {
|
global: {
|
||||||
toXML: () => 'global'
|
toXML: () => 'global'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
blocks: {
|
||||||
|
toXML: () => 'blocks'
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
variables: {
|
variables: {
|
||||||
unused: {
|
unused: {
|
||||||
toXML: () => 'unused'
|
toXML: () => 'unused'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
blocks: {
|
||||||
|
toXML: () => 'blocks'
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
variables: {
|
variables: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue