kopia lustrzana https://github.com/backface/turtlestitch
towards v4.0.4 - under construction -
* Show result bubble when the user clicks on a command script that uses REPORT (You can now click on REPORT and it actually does something) * New generic “When” hat block, enhances red stop button behavior * New block (instance) variables feature (experimental) * evaluator performance optimizations * Morphic grab-threshold fix for scroll frames * fixed several block rendering glitches * List category LENGTH reporter now also works on text * Changed “any” to “random” (in English only) * new FILL primitive in the Pen category * switched to animation frame scheduling, please use TURBO for music * Updated German translationdev
rodzic
b4af8f52e3
commit
f24b65f673
120
blocks.js
120
blocks.js
|
@ -156,7 +156,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.blocks = '2015-November-17';
|
||||
modules.blocks = '2015-December-15';
|
||||
|
||||
var SyntaxElementMorph;
|
||||
var BlockMorph;
|
||||
|
@ -485,6 +485,10 @@ SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) {
|
|||
if ((idx === -1) || (scripts === null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (oldArg.cachedSlotSpec) {oldArg.cachedSlotSpec = null; }
|
||||
if (newArg.cachedSlotSpec) {newArg.cachedSlotSpec = null; }
|
||||
|
||||
this.startLayout();
|
||||
if (newArg.parent) {
|
||||
newArg.parent.removeChild(newArg);
|
||||
|
@ -532,6 +536,9 @@ SyntaxElementMorph.prototype.silentReplaceInput = function (oldArg, newArg) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (oldArg.cachedSlotSpec) {oldArg.cachedSlotSpec = null; }
|
||||
if (newArg.cachedSlotSpec) {newArg.cachedSlotSpec = null; }
|
||||
|
||||
if (newArg.parent) {
|
||||
newArg.parent.removeChild(newArg);
|
||||
}
|
||||
|
@ -628,6 +635,10 @@ SyntaxElementMorph.prototype.getVarNamesDict = function () {
|
|||
rcvr = block.receiver();
|
||||
block.allParents().forEach(function (morph) {
|
||||
if (morph instanceof PrototypeHatBlockMorph) {
|
||||
tempVars.push.apply(
|
||||
tempVars,
|
||||
morph.variableNames()
|
||||
);
|
||||
tempVars.push.apply(
|
||||
tempVars,
|
||||
morph.inputs()[0].inputFragmentNames()
|
||||
|
@ -780,6 +791,10 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
|
|||
part = new MultiArgMorph('%t', null, 1, spec);
|
||||
part.canBeEmpty = false;
|
||||
break;
|
||||
case '%blockVars':
|
||||
part = new MultiArgMorph('%t', 'block variables', 0, spec);
|
||||
part.canBeEmpty = false;
|
||||
break;
|
||||
case '%parms':
|
||||
part = new MultiArgMorph('%t', 'Input Names:', 0, spec);
|
||||
part.canBeEmpty = false;
|
||||
|
@ -2125,11 +2140,9 @@ BlockMorph.prototype.setSpec = function (spec, silently) {
|
|||
if (part instanceof RingMorph) {
|
||||
part.fixBlockColor();
|
||||
}
|
||||
if (part instanceof MultiArgMorph
|
||||
|| contains(
|
||||
[CommandSlotMorph, RingCommandSlotMorph],
|
||||
part.constructor
|
||||
)) {
|
||||
if (part instanceof MultiArgMorph ||
|
||||
part.constructor === CommandSlotMorph ||
|
||||
part.constructor === RingCommandSlotMorph) {
|
||||
part.fixLayout();
|
||||
}
|
||||
if (myself.isPrototype) {
|
||||
|
@ -2374,7 +2387,7 @@ BlockMorph.prototype.developersMenu = function () {
|
|||
|
||||
new DialogBoxMorph(
|
||||
this,
|
||||
this.setSpec,
|
||||
this.userSetSpec,
|
||||
this
|
||||
).prompt(
|
||||
menu.title + '\nspec',
|
||||
|
@ -2861,7 +2874,7 @@ BlockMorph.prototype.codeMappingHeader = function () {
|
|||
|
||||
BlockMorph.prototype.eraseHoles = function (context) {
|
||||
var myself = this,
|
||||
isReporter = this instanceof ReporterBlockMorph,
|
||||
isRing = this instanceof RingMorph,
|
||||
shift = this.edge * 0.5,
|
||||
gradient,
|
||||
rightX,
|
||||
|
@ -2902,7 +2915,7 @@ BlockMorph.prototype.eraseHoles = function (context) {
|
|||
context.clearRect(
|
||||
hole.bounds.origin.x - myself.bounds.origin.x + 1,
|
||||
hole.bounds.origin.y - myself.bounds.origin.y + 1,
|
||||
isReporter ? w - 2 : w + 1,
|
||||
isRing ? w - 2 : w + 1,
|
||||
h
|
||||
);
|
||||
});
|
||||
|
@ -3181,6 +3194,8 @@ BlockMorph.prototype.fullCopy = function () {
|
|||
block.cachedInputs = null;
|
||||
if (block instanceof InputSlotMorph) {
|
||||
block.contents().clearSelection();
|
||||
} else if (block.definition) {
|
||||
block.initializeVariables();
|
||||
}
|
||||
} else if (block instanceof CursorMorph) {
|
||||
block.destroy();
|
||||
|
@ -3420,7 +3435,10 @@ BlockMorph.prototype.stackWidth = function () {
|
|||
};
|
||||
|
||||
BlockMorph.prototype.snap = function () {
|
||||
var top = this.topBlock();
|
||||
var top = this.topBlock(),
|
||||
receiver,
|
||||
stage,
|
||||
ide;
|
||||
top.allComments().forEach(function (comment) {
|
||||
comment.align(top);
|
||||
});
|
||||
|
@ -3431,6 +3449,21 @@ BlockMorph.prototype.snap = function () {
|
|||
if (top.getHighlight()) {
|
||||
top.addHighlight(top.removeHighlight());
|
||||
}
|
||||
// register generic hat blocks
|
||||
if (this.selector === 'receiveCondition') {
|
||||
receiver = top.receiver();
|
||||
if (receiver) {
|
||||
stage = receiver.parentThatIsA(StageMorph);
|
||||
if (stage) {
|
||||
stage.enableCustomHatBlocks = true;
|
||||
stage.threads.pauseCustomHatBlocks = false;
|
||||
ide = stage.parentThatIsA(IDE_Morph);
|
||||
if (ide) {
|
||||
ide.controlBar.stopButton.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// CommandBlockMorph ///////////////////////////////////////////////////
|
||||
|
@ -3468,6 +3501,7 @@ CommandBlockMorph.prototype.init = function (silently) {
|
|||
this.setExtent(new Point(200, 100), silently);
|
||||
this.partOfCustomCommand = false;
|
||||
this.exitTag = null;
|
||||
// this.cachedNextBlock = null; // don't serialize
|
||||
};
|
||||
|
||||
// CommandBlockMorph enumerating:
|
||||
|
@ -3495,6 +3529,7 @@ CommandBlockMorph.prototype.nextBlock = function (block) {
|
|||
var nb = this.nextBlock(),
|
||||
affected = this.parentThatIsA(CommandSlotMorph);
|
||||
this.add(block);
|
||||
// this.cachedNextBlock = block;
|
||||
if (nb) {
|
||||
block.bottomBlock().nextBlock(nb);
|
||||
}
|
||||
|
@ -3508,6 +3543,18 @@ CommandBlockMorph.prototype.nextBlock = function (block) {
|
|||
affected.fixLayout();
|
||||
}
|
||||
} else {
|
||||
/* cachedNextBlock - has issues, disabled for now
|
||||
if (!this.cachedNextBlock) {
|
||||
this.cachedNextBlock = detect(
|
||||
this.children,
|
||||
function (child) {
|
||||
return child instanceof CommandBlockMorph
|
||||
&& !child.isPrototype;
|
||||
}
|
||||
);
|
||||
}
|
||||
return this.cachedNextBlock;
|
||||
*/
|
||||
return detect(
|
||||
this.children,
|
||||
function (child) {
|
||||
|
@ -4330,6 +4377,7 @@ ReporterBlockMorph.prototype.init = function (isPredicate, silently) {
|
|||
ReporterBlockMorph.uber.init.call(this, silently);
|
||||
this.isPredicate = isPredicate || false;
|
||||
this.setExtent(new Point(200, 80), silently);
|
||||
this.cachedSlotSpec = null; // don't serialize
|
||||
};
|
||||
|
||||
// ReporterBlockMorph drag & drop:
|
||||
|
@ -4340,6 +4388,7 @@ ReporterBlockMorph.prototype.snap = function (hand) {
|
|||
nb,
|
||||
target;
|
||||
|
||||
this.cachedSlotSpec = null;
|
||||
if (!scripts instanceof ScriptsMorph) {
|
||||
return null;
|
||||
}
|
||||
|
@ -4388,6 +4437,7 @@ ReporterBlockMorph.prototype.prepareToBeGrabbed = function (handMorph) {
|
|||
}
|
||||
ReporterBlockMorph.uber.prepareToBeGrabbed.call(this, handMorph);
|
||||
this.alpha = 0.85;
|
||||
this.cachedSlotSpec = null;
|
||||
};
|
||||
|
||||
// ReporterBlockMorph enumerating
|
||||
|
@ -4400,11 +4450,12 @@ ReporterBlockMorph.prototype.blockSequence = function () {
|
|||
// ReporterBlockMorph evaluating
|
||||
|
||||
ReporterBlockMorph.prototype.isUnevaluated = function () {
|
||||
/*
|
||||
answer whether my parent block's slot is designated to be of an
|
||||
'unevaluated' kind, denoting a spedial form
|
||||
*/
|
||||
return contains(['%anyUE', '%boolUE', '%f'], this.getSlotSpec());
|
||||
// answer whether my parent block's slot is designated to be of an
|
||||
// 'unevaluated' kind, denoting a spedial form
|
||||
var spec = this.getSlotSpec();
|
||||
return spec === '%anyUE' ||
|
||||
spec === '%boolUE' ||
|
||||
spec === '%f';
|
||||
};
|
||||
|
||||
ReporterBlockMorph.prototype.isLocked = function () {
|
||||
|
@ -4414,6 +4465,28 @@ ReporterBlockMorph.prototype.isLocked = function () {
|
|||
|
||||
ReporterBlockMorph.prototype.getSlotSpec = function () {
|
||||
// answer the spec of the slot I'm in, if any
|
||||
// cached for performance
|
||||
if (!this.cachedSlotSpec) {
|
||||
this.cachedSlotSpec = this.determineSlotSpec();
|
||||
/*
|
||||
} else {
|
||||
// debug slot spec caching
|
||||
var real = this.determineSlotSpec();
|
||||
if (real !== this.cachedSlotSpec) {
|
||||
throw new Error(
|
||||
'cached slot spec ' +
|
||||
this.cachedSlotSpec +
|
||||
' does not match: ' +
|
||||
real
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
return this.cachedSlotSpec;
|
||||
};
|
||||
|
||||
ReporterBlockMorph.prototype.determineSlotSpec = function () {
|
||||
// private - answer the spec of the slot I'm in, if any
|
||||
var parts, idx;
|
||||
if (this.parent instanceof BlockMorph) {
|
||||
parts = this.parent.parts().filter(
|
||||
|
@ -4440,19 +4513,25 @@ ReporterBlockMorph.prototype.getSlotSpec = function () {
|
|||
// ReporterBlockMorph events
|
||||
|
||||
ReporterBlockMorph.prototype.mouseClickLeft = function (pos) {
|
||||
var isRing;
|
||||
var label;
|
||||
if (this.parent instanceof BlockInputFragmentMorph) {
|
||||
return this.parent.mouseClickLeft();
|
||||
}
|
||||
if (this.parent instanceof TemplateSlotMorph) {
|
||||
isRing = this.parent.parent && this.parent.parent.parent &&
|
||||
this.parent.parent.parent instanceof RingMorph;
|
||||
if (this.parent.parent && this.parent.parent.parent &&
|
||||
this.parent.parent.parent instanceof RingMorph) {
|
||||
label = "Input name";
|
||||
} else if (this.parent.parent.elementSpec === '%blockVars') {
|
||||
label = "Block variable name";
|
||||
} else {
|
||||
label = "Script variable name";
|
||||
}
|
||||
new DialogBoxMorph(
|
||||
this,
|
||||
this.userSetSpec,
|
||||
this
|
||||
).prompt(
|
||||
isRing ? "Input name" : "Script variable name",
|
||||
label,
|
||||
this.blockSpec,
|
||||
this.world()
|
||||
);
|
||||
|
@ -9713,7 +9792,8 @@ MultiArgMorph.prototype.addInput = function (contents) {
|
|||
// newPart.alpha = this.alpha ? 1 : (1 - this.alpha) / 2;
|
||||
if (contents) {
|
||||
newPart.setContents(contents);
|
||||
} else if (this.elementSpec === '%scriptVars') {
|
||||
} else if (this.elementSpec === '%scriptVars' ||
|
||||
this.elementSpec === '%blockVars') {
|
||||
name = '';
|
||||
i = idx;
|
||||
while (i > 0) {
|
||||
|
|
166
byob.js
166
byob.js
|
@ -104,11 +104,11 @@ ArrowMorph, PushButtonMorph, contains, InputSlotMorph, ShadowMorph,
|
|||
ToggleButtonMorph, IDE_Morph, MenuMorph, copy, ToggleElementMorph,
|
||||
Morph, fontHeight, StageMorph, SyntaxElementMorph, SnapSerializer,
|
||||
CommentMorph, localize, CSlotMorph, SpeechBubbleMorph, MorphicPreferences,
|
||||
SymbolMorph, isNil, CursorMorph*/
|
||||
SymbolMorph, isNil, CursorMorph, VariableFrame*/
|
||||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.byob = '2015-November-16';
|
||||
modules.byob = '2015-December-15';
|
||||
|
||||
// Declarations
|
||||
|
||||
|
@ -142,6 +142,7 @@ function CustomBlockDefinition(spec, receiver) {
|
|||
this.spec = spec || '';
|
||||
// format: {'inputName' : [type, default, options, readonly]}
|
||||
this.declarations = {};
|
||||
this.variableNames = [];
|
||||
this.comment = null;
|
||||
this.codeMapping = null; // experimental, generate text code
|
||||
this.codeHeader = null; // experimental, generate text code
|
||||
|
@ -384,7 +385,7 @@ CustomBlockDefinition.prototype.scriptsModel = function () {
|
|||
proto.allComments().forEach(function (comment) {
|
||||
comment.align(proto);
|
||||
});
|
||||
proto.children[0].fixLayout();
|
||||
proto.parts()[0].fixLayout();
|
||||
scripts.fixMultiArgs();
|
||||
return scripts;
|
||||
};
|
||||
|
@ -409,11 +410,21 @@ CustomCommandBlockMorph.prototype.init = function (definition, isProto) {
|
|||
CustomCommandBlockMorph.uber.init.call(this, true); // silently
|
||||
this.category = definition.category;
|
||||
this.selector = 'evaluateCustomBlock';
|
||||
this.variables = null;
|
||||
this.initializeVariables();
|
||||
if (definition) { // needed for de-serializing
|
||||
this.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
CustomCommandBlockMorph.prototype.initializeVariables = function () {
|
||||
var myself = this;
|
||||
this.variables = new VariableFrame();
|
||||
this.definition.variableNames.forEach(function (name) {
|
||||
myself.variables.addVar(name);
|
||||
});
|
||||
};
|
||||
|
||||
CustomCommandBlockMorph.prototype.refresh = function (silently) {
|
||||
var def = this.definition,
|
||||
newSpec = this.isPrototype ?
|
||||
|
@ -447,6 +458,10 @@ CustomCommandBlockMorph.prototype.refresh = function (silently) {
|
|||
inp.setContents(def.inputNames()[idx]);
|
||||
}
|
||||
});
|
||||
|
||||
// initialize block vars
|
||||
// to do: preserve values of unchanged variable names
|
||||
this.initializeVariables();
|
||||
};
|
||||
|
||||
CustomCommandBlockMorph.prototype.restoreInputs = function (oldInputs) {
|
||||
|
@ -759,7 +774,47 @@ CustomCommandBlockMorph.prototype.isInUse = function () {
|
|||
// CustomCommandBlockMorph menu:
|
||||
|
||||
CustomCommandBlockMorph.prototype.userMenu = function () {
|
||||
var menu;
|
||||
var hat = this.parentThatIsA(PrototypeHatBlockMorph),
|
||||
rcvr = this.receiver(),
|
||||
myself = this,
|
||||
menu;
|
||||
|
||||
function monitor(vName) {
|
||||
var stage = rcvr.parentThatIsA(StageMorph),
|
||||
varFrame = myself.variables;
|
||||
menu.addItem(
|
||||
vName + '...',
|
||||
function () {
|
||||
var watcher = detect(
|
||||
stage.children,
|
||||
function (morph) {
|
||||
return morph instanceof WatcherMorph
|
||||
&& morph.target === varFrame
|
||||
&& morph.getter === vName;
|
||||
}
|
||||
),
|
||||
others;
|
||||
if (watcher !== null) {
|
||||
watcher.show();
|
||||
watcher.fixLayout(); // re-hide hidden parts
|
||||
return;
|
||||
}
|
||||
watcher = new WatcherMorph(
|
||||
vName + ' ' + localize('(temporary)'),
|
||||
SpriteMorph.prototype.blockColor.variables,
|
||||
varFrame,
|
||||
vName
|
||||
);
|
||||
watcher.setPosition(stage.position().add(10));
|
||||
others = stage.watchers(watcher.left());
|
||||
if (others.length > 0) {
|
||||
watcher.setTop(others[others.length - 1].bottom());
|
||||
}
|
||||
stage.add(watcher);
|
||||
watcher.fixLayout();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (this.isPrototype) {
|
||||
menu = new MenuMorph(this);
|
||||
|
@ -770,6 +825,23 @@ CustomCommandBlockMorph.prototype.userMenu = function () {
|
|||
},
|
||||
'open a new window\nwith a picture of this script'
|
||||
);
|
||||
if (hat.inputs().length < 2) {
|
||||
menu.addItem(
|
||||
"block variables...",
|
||||
function () {
|
||||
hat.enableBlockVars();
|
||||
},
|
||||
'experimental -\nunder construction'
|
||||
);
|
||||
} else {
|
||||
menu.addItem(
|
||||
"remove block variables...",
|
||||
function () {
|
||||
hat.enableBlockVars(false);
|
||||
},
|
||||
'experimental -\nunder construction'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
menu = this.constructor.uber.userMenu.call(this);
|
||||
if (!menu) {
|
||||
|
@ -780,6 +852,10 @@ CustomCommandBlockMorph.prototype.userMenu = function () {
|
|||
|
||||
// menu.addItem("export definition...", 'exportBlockDefinition');
|
||||
menu.addItem("delete block definition...", 'deleteBlockDefinition');
|
||||
|
||||
this.variables.names().forEach(function (vName) {
|
||||
monitor(vName);
|
||||
});
|
||||
}
|
||||
menu.addItem("edit...", 'edit'); // works also for prototypes
|
||||
return menu;
|
||||
|
@ -938,16 +1014,19 @@ CustomReporterBlockMorph.prototype.init = function (
|
|||
) {
|
||||
this.definition = definition; // mandatory
|
||||
this.isPrototype = isProto || false; // optional
|
||||
|
||||
CustomReporterBlockMorph.uber.init.call(this, isPredicate, true); // sil.
|
||||
|
||||
this.category = definition.category;
|
||||
this.variables = new VariableFrame();
|
||||
this.initializeVariables();
|
||||
this.selector = 'evaluateCustomBlock';
|
||||
if (definition) { // needed for de-serializing
|
||||
this.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
CustomReporterBlockMorph.prototype.initializeVariables =
|
||||
CustomCommandBlockMorph.prototype.initializeVariables;
|
||||
|
||||
CustomReporterBlockMorph.prototype.refresh = function () {
|
||||
CustomCommandBlockMorph.prototype.refresh.call(this, true);
|
||||
if (!this.isPrototype) {
|
||||
|
@ -1732,7 +1811,7 @@ BlockEditorMorph.prototype.init = function (definition, target) {
|
|||
|
||||
this.setExtent(new Point(375, 300)); // normal initial extent
|
||||
this.fixLayout();
|
||||
proto.children[0].fixLayout();
|
||||
proto.parts()[0].fixLayout();
|
||||
scripts.fixMultiArgs();
|
||||
};
|
||||
|
||||
|
@ -1864,6 +1943,7 @@ BlockEditorMorph.prototype.updateDefinition = function () {
|
|||
this.definition.receiver = this.target; // only for serialization
|
||||
this.definition.spec = this.prototypeSpec();
|
||||
this.definition.declarations = this.prototypeSlots();
|
||||
this.definition.variableNames = this.variableNames();
|
||||
this.definition.scripts = [];
|
||||
this.definition.editorDimensions = this.bounds.copy();
|
||||
|
||||
|
@ -1940,6 +2020,14 @@ BlockEditorMorph.prototype.prototypeSlots = function () {
|
|||
).parts()[0].declarationsFromFragments();
|
||||
};
|
||||
|
||||
BlockEditorMorph.prototype.variableNames = function () {
|
||||
// answer the variable declarations from my prototype hat
|
||||
return detect(
|
||||
this.body.contents.children,
|
||||
function (c) {return c instanceof PrototypeHatBlockMorph; }
|
||||
).variableNames();
|
||||
};
|
||||
|
||||
// BlockEditorMorph layout
|
||||
|
||||
BlockEditorMorph.prototype.setInitialDimensions = function () {
|
||||
|
@ -2006,7 +2094,8 @@ function PrototypeHatBlockMorph(definition) {
|
|||
}
|
||||
|
||||
PrototypeHatBlockMorph.prototype.init = function (definition) {
|
||||
var proto = definition.prototypeInstance();
|
||||
var proto = definition.prototypeInstance(),
|
||||
vars;
|
||||
|
||||
this.definition = definition;
|
||||
|
||||
|
@ -2019,6 +2108,14 @@ PrototypeHatBlockMorph.prototype.init = function (definition) {
|
|||
this.color = SpriteMorph.prototype.blockColor.control;
|
||||
this.category = 'control';
|
||||
this.add(proto);
|
||||
if (definition.variableNames.length) {
|
||||
vars = this.labelPart('%blockVars');
|
||||
this.add(this.labelPart('%br'));
|
||||
this.add(vars);
|
||||
definition.variableNames.forEach(function (name) {
|
||||
vars.addInput(name);
|
||||
});
|
||||
}
|
||||
proto.refreshPrototypeSlotTypes(); // show slot type indicators
|
||||
this.fixLayout();
|
||||
};
|
||||
|
@ -2032,11 +2129,11 @@ PrototypeHatBlockMorph.prototype.mouseClickLeft = function () {
|
|||
if (this.world().currentKey === 16) { // shift-clicked
|
||||
return this.focus();
|
||||
}
|
||||
this.children[0].mouseClickLeft();
|
||||
this.parts()[0].mouseClickLeft();
|
||||
};
|
||||
|
||||
PrototypeHatBlockMorph.prototype.userMenu = function () {
|
||||
return this.children[0].userMenu();
|
||||
return this.parts()[0].userMenu();
|
||||
};
|
||||
|
||||
// PrototypeHatBlockMorph zebra coloring
|
||||
|
@ -2045,7 +2142,7 @@ PrototypeHatBlockMorph.prototype.fixBlockColor = function (
|
|||
nearestBlock,
|
||||
isForced
|
||||
) {
|
||||
var nearest = this.children[0] || nearestBlock;
|
||||
var nearest = this.parts()[0] || nearestBlock;
|
||||
|
||||
if (!this.zebraContrast && !isForced) {
|
||||
return;
|
||||
|
@ -2068,6 +2165,25 @@ PrototypeHatBlockMorph.prototype.fixBlockColor = function (
|
|||
}
|
||||
};
|
||||
|
||||
// PrototypeHatBlockMorph block instance variables
|
||||
|
||||
PrototypeHatBlockMorph.prototype.variableNames = function (choice) {
|
||||
var parts = this.parts();
|
||||
if (parts.length < 3) {return []; }
|
||||
return parts[2].evaluate();
|
||||
};
|
||||
|
||||
PrototypeHatBlockMorph.prototype.enableBlockVars = function (choice) {
|
||||
var prot = this.parts()[0];
|
||||
if (choice === false) {
|
||||
this.setSpec('%s', true);
|
||||
} else {
|
||||
this.setSpec('%s %br %blockVars', true);
|
||||
}
|
||||
this.replaceInput(this.parts()[0], prot);
|
||||
this.spec = null;
|
||||
};
|
||||
|
||||
// BlockLabelFragment //////////////////////////////////////////////////
|
||||
|
||||
// BlockLabelFragment instance creation:
|
||||
|
@ -2306,8 +2422,11 @@ BlockLabelFragmentMorph.prototype.userMenu = function () {
|
|||
tuple[0] = '$' + string;
|
||||
myself.text = tuple.join('-');
|
||||
myself.fragment.labelString = myself.text;
|
||||
myself.parent.parent.changed();
|
||||
myself.drawNew();
|
||||
myself.changed();
|
||||
myself.parent.parent.fixLayout();
|
||||
myself.parent.parent.changed();
|
||||
},
|
||||
null,
|
||||
this,
|
||||
|
@ -2428,21 +2547,38 @@ BlockLabelPlaceHolderMorph.prototype.drawNew = function () {
|
|||
if (this.parent.fixLayout) {
|
||||
this.parent.fixLayout();
|
||||
}
|
||||
if (this.parent.parent instanceof PrototypeHatBlockMorph) {
|
||||
this.parent.parent.fixLayout();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// BlockLabelPlaceHolderMorph events:
|
||||
|
||||
BlockLabelPlaceHolderMorph.prototype.mouseEnter = function () {
|
||||
var hat = this.parentThatIsA(PrototypeHatBlockMorph);
|
||||
this.isHighlighted = true;
|
||||
this.drawNew();
|
||||
this.changed();
|
||||
if (this.plainLabel && hat) {
|
||||
hat.changed();
|
||||
this.drawNew();
|
||||
hat.changed();
|
||||
} else {
|
||||
this.drawNew();
|
||||
this.changed();
|
||||
}
|
||||
};
|
||||
|
||||
BlockLabelPlaceHolderMorph.prototype.mouseLeave = function () {
|
||||
var hat = this.parentThatIsA(PrototypeHatBlockMorph);
|
||||
this.isHighlighted = false;
|
||||
this.drawNew();
|
||||
this.changed();
|
||||
if (this.plainLabel && hat) {
|
||||
hat.changed();
|
||||
this.drawNew();
|
||||
hat.changed();
|
||||
} else {
|
||||
this.drawNew();
|
||||
this.changed();
|
||||
}
|
||||
};
|
||||
|
||||
BlockLabelPlaceHolderMorph.prototype.mouseClickLeft
|
||||
|
|
21
cloud.js
21
cloud.js
|
@ -30,7 +30,7 @@
|
|||
/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop,
|
||||
localize*/
|
||||
|
||||
modules.cloud = '2015-December-04';
|
||||
modules.cloud = '2015-December-15';
|
||||
|
||||
// Global stuff
|
||||
|
||||
|
@ -326,7 +326,8 @@ Cloud.prototype.reconnect = function (
|
|||
Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
|
||||
var myself = this,
|
||||
pdata,
|
||||
media;
|
||||
media,
|
||||
size;
|
||||
|
||||
ide.serializer.isCollectingMedia = true;
|
||||
pdata = ide.serializer.serialize(ide.stage);
|
||||
|
@ -335,6 +336,19 @@ Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
|
|||
ide.serializer.isCollectingMedia = false;
|
||||
ide.serializer.flushMedia();
|
||||
|
||||
size = pdata.length + (media ? media.length : 0);
|
||||
/*
|
||||
if (size > 5242880) {
|
||||
new DialogBoxMorph().inform(
|
||||
'Snap!Cloud',
|
||||
'Project exceeds 5 MB size limit',
|
||||
ide.world(),
|
||||
ide.cloudIcon(null, new Color(180, 0, 0))
|
||||
);
|
||||
throw new Error('Project exceeds 5 MB size limit');
|
||||
}
|
||||
*/
|
||||
|
||||
// check if serialized data can be parsed back again
|
||||
try {
|
||||
ide.serializer.parse(pdata);
|
||||
|
@ -353,6 +367,7 @@ Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
|
|||
ide.serializer.isCollectingMedia = false;
|
||||
ide.serializer.flushMedia();
|
||||
|
||||
ide.showMessage('Uploading ' + Math.round(size / 1024) + ' KB...');
|
||||
myself.reconnect(
|
||||
function () {
|
||||
myself.callService(
|
||||
|
@ -545,7 +560,7 @@ Cloud.prototype.callService = function (
|
|||
} else {
|
||||
responseList = myself.parseResponse(
|
||||
request.responseText
|
||||
)
|
||||
);
|
||||
}
|
||||
callBack.call(null, responseList, service.url);
|
||||
}
|
||||
|
|
90
gui.js
90
gui.js
|
@ -71,7 +71,7 @@ BlockRemovalDialogMorph*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.gui = '2015-December-04';
|
||||
modules.gui = '2015-December-15';
|
||||
|
||||
// Declarations
|
||||
|
||||
|
@ -624,11 +624,22 @@ IDE_Morph.prototype.createControlBar = function () {
|
|||
this.controlBar.appModeButton = appModeButton; // for refreshing
|
||||
|
||||
// stopButton
|
||||
button = new PushButtonMorph(
|
||||
this,
|
||||
button = new ToggleButtonMorph(
|
||||
null, // colors
|
||||
this, // the IDE is the target
|
||||
'stopAllScripts',
|
||||
new SymbolMorph('octagon', 14)
|
||||
[
|
||||
new SymbolMorph('octagon', 14),
|
||||
new SymbolMorph('square', 14)
|
||||
],
|
||||
function () { // query
|
||||
return myself.stage ?
|
||||
myself.stage.enableCustomHatBlocks &&
|
||||
myself.stage.threads.pauseCustomHatBlocks
|
||||
: true;
|
||||
}
|
||||
);
|
||||
|
||||
button.corner = 12;
|
||||
button.color = colors[0];
|
||||
button.highlightColor = colors[1];
|
||||
|
@ -642,13 +653,15 @@ IDE_Morph.prototype.createControlBar = function () {
|
|||
button.drawNew();
|
||||
// button.hint = 'stop\nevery-\nthing';
|
||||
button.fixLayout();
|
||||
button.refresh();
|
||||
stopButton = button;
|
||||
this.controlBar.add(stopButton);
|
||||
this.controlBar.stopButton = stopButton; // for refreshing
|
||||
|
||||
//pauseButton
|
||||
button = new ToggleButtonMorph(
|
||||
null, //colors,
|
||||
myself, // the IDE is the target
|
||||
this, // the IDE is the target
|
||||
'togglePauseResume',
|
||||
[
|
||||
new SymbolMorph('pause', 12),
|
||||
|
@ -1706,6 +1719,8 @@ IDE_Morph.prototype.pressStart = function () {
|
|||
if (this.world().currentKey === 16) { // shiftClicked
|
||||
this.toggleFastTracking();
|
||||
} else {
|
||||
this.stage.threads.pauseCustomHatBlocks = false;
|
||||
this.controlBar.stopButton.refresh();
|
||||
this.runScripts();
|
||||
}
|
||||
};
|
||||
|
@ -1763,6 +1778,13 @@ IDE_Morph.prototype.isPaused = function () {
|
|||
};
|
||||
|
||||
IDE_Morph.prototype.stopAllScripts = function () {
|
||||
if (this.stage.enableCustomHatBlocks) {
|
||||
this.stage.threads.pauseCustomHatBlocks =
|
||||
!this.stage.threads.pauseCustomHatBlocks;
|
||||
} else {
|
||||
this.stage.threads.pauseCustomHatBlocks = false;
|
||||
}
|
||||
this.controlBar.stopButton.refresh();
|
||||
this.stage.fireStopAllEvent();
|
||||
};
|
||||
|
||||
|
@ -2719,7 +2741,7 @@ IDE_Morph.prototype.aboutSnap = function () {
|
|||
module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn,
|
||||
world = this.world();
|
||||
|
||||
aboutTxt = 'Snap! 4.0.3\nBuild Your Own Blocks\n\n'
|
||||
aboutTxt = 'Snap! 4.0.4\nBuild Your Own Blocks\n\n'
|
||||
+ 'Copyright \u24B8 2015 Jens M\u00F6nig and '
|
||||
+ 'Brian Harvey\n'
|
||||
+ 'jens@moenig.org, bh@cs.berkeley.edu\n\n'
|
||||
|
@ -3022,6 +3044,51 @@ IDE_Morph.prototype.saveProjectToDisk = function () {
|
|||
}
|
||||
};
|
||||
|
||||
/* alternative download mechanism, commented out in favor of blob-uris
|
||||
IDE_Morph.prototype.saveProjectToDisk = function () {
|
||||
var data;
|
||||
if (Process.prototype.isCatchingErrors) {
|
||||
try {
|
||||
data = this.serializer.serialize(this.stage);
|
||||
} catch (err) {
|
||||
this.showMessage('Serialization failed: ' + err);
|
||||
}
|
||||
} else {
|
||||
data = this.serializer.serialize(this.stage);
|
||||
}
|
||||
this.download(data, this.projectName);
|
||||
};
|
||||
|
||||
IDE_Morph.prototype.download = function (data, fileName, mime, suffix) {
|
||||
var link = document.createElement('a'),
|
||||
myself = this;
|
||||
|
||||
mime = mime || 'data:text/xml';
|
||||
fileName = fileName || 'download';
|
||||
suffix = suffix || 'xml';
|
||||
|
||||
function saveToDisk() {
|
||||
var msg = myself.showMessage('Downloading to disk...');
|
||||
link.setAttribute('href', mime + ',' + data);
|
||||
link.setAttribute('download', fileName + '.' + suffix);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
msg.destroy();
|
||||
}
|
||||
|
||||
if (Process.prototype.isCatchingErrors) {
|
||||
try {
|
||||
saveToDisk();
|
||||
} catch (err) {
|
||||
this.showMessage('Saving failed: ' + err);
|
||||
}
|
||||
} else {
|
||||
saveToDisk();
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
IDE_Morph.prototype.exportProject = function (name, plain) {
|
||||
var menu, str;
|
||||
if (name) {
|
||||
|
@ -3494,10 +3561,11 @@ IDE_Morph.prototype.rawOpenProjectString = function (str) {
|
|||
|
||||
IDE_Morph.prototype.openCloudDataString = function (str) {
|
||||
var msg,
|
||||
myself = this;
|
||||
myself = this,
|
||||
size = Math.round(str.length / 1024);
|
||||
this.nextSteps([
|
||||
function () {
|
||||
msg = myself.showMessage('Opening project...');
|
||||
msg = myself.showMessage('Opening project\n' + size + ' KB...');
|
||||
},
|
||||
function () {nop(); }, // yield (bug in Chrome)
|
||||
function () {
|
||||
|
@ -5275,6 +5343,12 @@ ProjectDialogMorph.prototype.rawOpenCloudProject = function (proj) {
|
|||
'getRawProject',
|
||||
function (response) {
|
||||
SnapCloud.disconnect();
|
||||
/*
|
||||
if (myself.world().currentKey === 16) {
|
||||
myself.ide.download(response);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
myself.ide.source = 'cloud';
|
||||
myself.ide.droppedText(response);
|
||||
if (proj.Public === 'true') {
|
||||
|
|
102
history.txt
102
history.txt
|
@ -2695,3 +2695,105 @@ end - bulk of 151116
|
|||
* Cloud: doubled the number of supported backend slices
|
||||
* Cloud, GUI: support new “raw” cloud project services
|
||||
|
||||
++++++++++++++++++++++++++
|
||||
new stuff - bulk of 151215
|
||||
++++++++++++++++++++++++++
|
||||
|
||||
151121
|
||||
------
|
||||
* Threads: Show result bubble when the user clicks on a command script that uses REPORT (You can now click on REPORT and it actually does something)
|
||||
|
||||
151124
|
||||
------
|
||||
* Blocks: fix a re-rendering glitch when changing block specs in dev mode
|
||||
* Threads: add optional receiver (environment) to invoke() function
|
||||
|
||||
151125
|
||||
------
|
||||
* Threads, Objects, GUI, Store: Generic “When” hat block
|
||||
* BYOB: fixed a rendering bug when using plain prototype labels
|
||||
|
||||
151126
|
||||
------
|
||||
* Threads, Blocks: Performance optimizations (replace “contains” with chained tests)
|
||||
* German translation update (for custom hat blocks)
|
||||
|
||||
151127
|
||||
------
|
||||
* Blocks, BYOB, Store: new experimental block variables feature
|
||||
* BYOB: more prototype label rendering fixes
|
||||
|
||||
151128
|
||||
------
|
||||
* BYOB, Store: Fix some bugs related to block vars (zebra coloring etc.)
|
||||
|
||||
151201
|
||||
------
|
||||
* BYOB, Blocks: Fix BlockMorph.fullCopy() for block vars
|
||||
|
||||
151202
|
||||
------
|
||||
* Threads: Only support block vars for blocks that actually define any, to avoid race conditions among parallel global blocks with the same definition that also access sprite-local variables
|
||||
|
||||
151207
|
||||
------
|
||||
* Threads, GUI: Stop button stops / restarts custom hat blocks, green flag starts custom hat blocks
|
||||
|
||||
151208
|
||||
------
|
||||
* Objects, Blocks, Threads, GUI, Store, Locale: Automatically enable/disable custom hat blocks when they’re used in a project
|
||||
* BYOB: initialize custom block vars on every definition-refresh
|
||||
|
||||
151209
|
||||
------
|
||||
* Threads: allow invoke() to operate on both blocks and rings with arguments
|
||||
* Blocks: cache reporter slot specs for evaluation performance (30% speedup)
|
||||
|
||||
151210
|
||||
------
|
||||
* Store: persist block (instance) vars
|
||||
* Threads: only show result bubble on user-clicked scripts if “Report” is in the lexical script (not inside a reporter block definition)
|
||||
* Morphic: obey grab threshold when dragging inside scroll frames
|
||||
|
||||
151211
|
||||
------
|
||||
* Threads: extend red LENGTH reporter to also work on Text
|
||||
* GUI, Objects, Blocks: extend the red stop button to reflect whether custom hat blocks are paused (indicated by a red square instead of the stop sign)
|
||||
* Blocks: Tweak C-Slots to better fit inside reporters
|
||||
|
||||
151212
|
||||
------
|
||||
* Locale: change English ‘any’ (in “item of”) to ‘random’ because teachers
|
||||
|
||||
151214
|
||||
------
|
||||
* Objects: added “fill” primitive to the Pen category
|
||||
* Updated German translation
|
||||
* GUI: Directly download projects from cloud by holding shift while opening - commented out
|
||||
* GUI, Cloud: show size of uploaded / downloaded projects
|
||||
* GUI, Cloud: upload size limit of 5 MB - commented out
|
||||
|
||||
151215
|
||||
------
|
||||
* snap.html: switch to animation frame scheduling because Chrome sucks sooooo much!!!!
|
||||
* GUI: pushed version to 4.0.4
|
||||
|
||||
++++++++++++++++++++++++++
|
||||
|
||||
v4.0.4 draft features:
|
||||
* Show result bubble when the user clicks on a command script that uses REPORT (You can now click on REPORT and it actually does something)
|
||||
* New generic “When” hat block, enhances red stop button behavior
|
||||
* New block (instance) variables feature (experimental)
|
||||
* evaluator performance optimizations
|
||||
* Morphic grab-threshold fix for scroll frames
|
||||
* fixed several block rendering glitches
|
||||
* List category LENGTH reporter now also works on text
|
||||
* Changed “any” to “random” (in English only)
|
||||
* new FILL primitive in the Pen category
|
||||
* switched to animation frame scheduling, please use TURBO for music
|
||||
* Updated German translation
|
||||
|
||||
++++++++++++++++++++++++++
|
||||
end - bulk of 151215
|
||||
++++++++++++++++++++++++++
|
||||
|
||||
|
|
|
@ -185,7 +185,7 @@ SnapTranslator.dict.de = {
|
|||
'translator_e-mail':
|
||||
'jens@moenig.org', // optional
|
||||
'last_changed':
|
||||
'2015-10-07', // this, too, will appear in the Translators tab
|
||||
'2015-12-14', // this, too, will appear in the Translators tab
|
||||
|
||||
// GUI
|
||||
// control bar:
|
||||
|
@ -411,6 +411,8 @@ SnapTranslator.dict.de = {
|
|||
'setze Stiftdicke auf %n',
|
||||
'stamp':
|
||||
'stemple',
|
||||
'fill':
|
||||
'male aus',
|
||||
|
||||
// control:
|
||||
'when %greenflag clicked':
|
||||
|
@ -429,6 +431,8 @@ SnapTranslator.dict.de = {
|
|||
'vom Mauszeiger betreten',
|
||||
'mouse-departed':
|
||||
'vom Mauszeiger verlassen',
|
||||
'when %b':
|
||||
'Wenn %b',
|
||||
'when I receive %msgHat':
|
||||
'Wenn ich %msgHat empfange',
|
||||
'broadcast %msg':
|
||||
|
|
10
locale.js
10
locale.js
|
@ -42,7 +42,7 @@
|
|||
|
||||
/*global modules, contains*/
|
||||
|
||||
modules.locale = '2015-November-16';
|
||||
modules.locale = '2015-December-15';
|
||||
|
||||
// Global stuff
|
||||
|
||||
|
@ -123,7 +123,11 @@ SnapTranslator.dict.en = {
|
|||
'translator_e-mail':
|
||||
'jens@moenig.org',
|
||||
'last_changed':
|
||||
'2012-10-16',
|
||||
'2015-12-12',
|
||||
|
||||
// rewordings in English avoiding having to adjust all other translations
|
||||
'any':
|
||||
'random',
|
||||
|
||||
// long strings look-up only
|
||||
'file menu import hint':
|
||||
|
@ -149,7 +153,7 @@ SnapTranslator.dict.de = {
|
|||
'translator_e-mail':
|
||||
'jens@moenig.org',
|
||||
'last_changed':
|
||||
'2015-10-07'
|
||||
'2015-12-14'
|
||||
};
|
||||
|
||||
SnapTranslator.dict.it = {
|
||||
|
|
19
morphic.js
19
morphic.js
|
@ -1052,7 +1052,7 @@
|
|||
/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio,
|
||||
FileList, getBlurredShadowSupport*/
|
||||
|
||||
var morphicVersion = '2015-November-16';
|
||||
var morphicVersion = '2015-December-15';
|
||||
var modules = {}; // keep track of additional loaded modules
|
||||
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
|
||||
|
||||
|
@ -8896,6 +8896,7 @@ ScrollFrameMorph.prototype.mouseDownLeft = function (pos) {
|
|||
return null;
|
||||
}
|
||||
var world = this.root(),
|
||||
hand = world.hand,
|
||||
oldPos = pos,
|
||||
myself = this,
|
||||
deltaX = 0,
|
||||
|
@ -8904,10 +8905,18 @@ ScrollFrameMorph.prototype.mouseDownLeft = function (pos) {
|
|||
|
||||
this.step = function () {
|
||||
var newPos;
|
||||
if (world.hand.mouseButton &&
|
||||
(world.hand.children.length === 0) &&
|
||||
(myself.bounds.containsPoint(world.hand.position()))) {
|
||||
newPos = world.hand.bounds.origin;
|
||||
if (hand.mouseButton &&
|
||||
(hand.children.length === 0) &&
|
||||
(myself.bounds.containsPoint(hand.bounds.origin))) {
|
||||
|
||||
if (hand.grabPosition &&
|
||||
(hand.grabPosition.distanceTo(hand.position()) <=
|
||||
MorphicPreferences.grabThreshold)) {
|
||||
// still within the grab threshold
|
||||
return null;
|
||||
}
|
||||
|
||||
newPos = hand.bounds.origin;
|
||||
deltaX = newPos.x - oldPos.x;
|
||||
if (deltaX !== 0) {
|
||||
myself.scrollX(deltaX);
|
||||
|
|
109
objects.js
109
objects.js
|
@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.objects = '2015-November-16';
|
||||
modules.objects = '2015-December-15';
|
||||
|
||||
var SpriteMorph;
|
||||
var StageMorph;
|
||||
|
@ -569,6 +569,12 @@ SpriteMorph.prototype.initBlocks = function () {
|
|||
category: 'pen',
|
||||
spec: 'stamp'
|
||||
},
|
||||
floodFill: {
|
||||
only: SpriteMorph,
|
||||
type: 'command',
|
||||
category: 'pen',
|
||||
spec: 'fill'
|
||||
},
|
||||
|
||||
// Control
|
||||
receiveGo: {
|
||||
|
@ -602,6 +608,11 @@ SpriteMorph.prototype.initBlocks = function () {
|
|||
category: 'control',
|
||||
spec: 'when I receive %msgHat'
|
||||
},
|
||||
receiveCondition: {
|
||||
type: 'hat',
|
||||
category: 'control',
|
||||
spec: 'when %b'
|
||||
},
|
||||
doBroadcast: {
|
||||
type: 'command',
|
||||
category: 'control',
|
||||
|
@ -1861,12 +1872,14 @@ SpriteMorph.prototype.blockTemplates = function (category) {
|
|||
blocks.push(block('setSize'));
|
||||
blocks.push('-');
|
||||
blocks.push(block('doStamp'));
|
||||
blocks.push(block('floodFill'));
|
||||
|
||||
} else if (cat === 'control') {
|
||||
|
||||
blocks.push(block('receiveGo'));
|
||||
blocks.push(block('receiveKey'));
|
||||
blocks.push(block('receiveInteraction'));
|
||||
blocks.push(block('receiveCondition'));
|
||||
blocks.push(block('receiveMessage'));
|
||||
blocks.push('-');
|
||||
blocks.push(block('doBroadcast'));
|
||||
|
@ -3350,6 +3363,60 @@ SpriteMorph.prototype.drawLine = function (start, dest) {
|
|||
}
|
||||
};
|
||||
|
||||
SpriteMorph.prototype.floodFill = function () {
|
||||
var layer = this.parent.penTrails(),
|
||||
width = layer.width,
|
||||
height = layer.height,
|
||||
ctx = layer.getContext('2d'),
|
||||
img = ctx.getImageData(0, 0, width, height),
|
||||
dta = img.data,
|
||||
stack = [
|
||||
((height / 2) - Math.round(this.yPosition())) * width +
|
||||
Math.round(this.xPosition() + (width / 2))
|
||||
],
|
||||
current,
|
||||
src;
|
||||
|
||||
function read(p) {
|
||||
var d = p * 4;
|
||||
return [dta[d], dta[d + 1], dta[d + 2], dta[d + 3]];
|
||||
}
|
||||
|
||||
function check(p) {
|
||||
return p[0] === src[0] &&
|
||||
p[1] === src[1] &&
|
||||
p[2] === src[2] &&
|
||||
p[3] === src[3];
|
||||
}
|
||||
|
||||
src = read(stack[0]);
|
||||
if (src[0] === Math.round(this.color.r) &&
|
||||
src[1] === Math.round(this.color.g) &&
|
||||
src[2] === Math.round(this.color.b) &&
|
||||
src[3] === Math.round(this.color.a * 255)) {
|
||||
return;
|
||||
}
|
||||
while (stack.length > 0) {
|
||||
current = stack.pop();
|
||||
if (check(read(current))) {
|
||||
if (current % width > 1) {
|
||||
stack.push(current + 1);
|
||||
stack.push(current - 1);
|
||||
}
|
||||
if (current > 0 && current < height * width) {
|
||||
stack.push(current + width);
|
||||
stack.push(current - width);
|
||||
}
|
||||
}
|
||||
dta[current * 4] = Math.round(this.color.r);
|
||||
dta[current * 4 + 1] = Math.round(this.color.g);
|
||||
dta[current * 4 + 2] = Math.round(this.color.b);
|
||||
dta[current * 4 + 3] = Math.round(this.color.a * 255);
|
||||
}
|
||||
ctx.putImageData(img, 0, 0);
|
||||
this.parent.changed();
|
||||
};
|
||||
|
||||
// SpriteMorph motion - adjustments due to nesting
|
||||
|
||||
SpriteMorph.prototype.moveBy = function (delta, justMe) {
|
||||
|
@ -3693,6 +3760,15 @@ SpriteMorph.prototype.allHatBlocksForInteraction = function (interaction) {
|
|||
});
|
||||
};
|
||||
|
||||
SpriteMorph.prototype.allGenericHatBlocks = function () {
|
||||
return this.scripts.children.filter(function (morph) {
|
||||
if (morph.selector) {
|
||||
return morph.selector === 'receiveCondition';
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
// SpriteMorph events
|
||||
|
||||
SpriteMorph.prototype.mouseClickLeft = function () {
|
||||
|
@ -4674,7 +4750,6 @@ StageMorph.prototype.hiddenPrimitives = {};
|
|||
StageMorph.prototype.codeMappings = {};
|
||||
StageMorph.prototype.codeHeaders = {};
|
||||
StageMorph.prototype.enableCodeMapping = false;
|
||||
|
||||
StageMorph.prototype.enableInheritance = false;
|
||||
|
||||
// StageMorph instance creation
|
||||
|
@ -4695,6 +4770,7 @@ StageMorph.prototype.init = function (globals) {
|
|||
this.sounds = new List();
|
||||
this.version = Date.now(); // for observers
|
||||
this.isFastTracked = false;
|
||||
this.enableCustomHatBlocks = true;
|
||||
this.cloneCount = 0;
|
||||
|
||||
this.timerStart = Date.now();
|
||||
|
@ -5036,6 +5112,9 @@ StageMorph.prototype.step = function () {
|
|||
}
|
||||
|
||||
// manage threads
|
||||
if (this.enableCustomHatBlocks) {
|
||||
this.stepGenericConditions();
|
||||
}
|
||||
if (this.isFastTracked && this.threads.processes.length) {
|
||||
this.children.forEach(function (morph) {
|
||||
if (morph instanceof SpriteMorph) {
|
||||
|
@ -5072,6 +5151,28 @@ StageMorph.prototype.step = function () {
|
|||
}
|
||||
};
|
||||
|
||||
StageMorph.prototype.stepGenericConditions = function (stopAll) {
|
||||
var hats = [],
|
||||
myself = this,
|
||||
ide;
|
||||
this.children.concat(this).forEach(function (morph) {
|
||||
if (morph instanceof SpriteMorph || morph instanceof StageMorph) {
|
||||
hats = hats.concat(morph.allGenericHatBlocks());
|
||||
}
|
||||
});
|
||||
if (!hats.length) {
|
||||
this.enableCustomHatBlocks = false;
|
||||
ide = this.parentThatIsA(IDE_Morph);
|
||||
if (ide) {
|
||||
ide.controlBar.stopButton.refresh();
|
||||
}
|
||||
return;
|
||||
}
|
||||
hats.forEach(function (block) {
|
||||
myself.threads.doWhen(block, stopAll);
|
||||
});
|
||||
};
|
||||
|
||||
StageMorph.prototype.developersMenu = function () {
|
||||
var myself = this,
|
||||
menu = StageMorph.uber.developersMenu.call(this);
|
||||
|
@ -5430,6 +5531,7 @@ StageMorph.prototype.blockTemplates = function (category) {
|
|||
blocks.push(block('receiveGo'));
|
||||
blocks.push(block('receiveKey'));
|
||||
blocks.push(block('receiveInteraction'));
|
||||
blocks.push(block('receiveCondition'));
|
||||
blocks.push(block('receiveMessage'));
|
||||
blocks.push('-');
|
||||
blocks.push(block('doBroadcast'));
|
||||
|
@ -5984,6 +6086,9 @@ StageMorph.prototype.allHatBlocksForKey
|
|||
StageMorph.prototype.allHatBlocksForInteraction
|
||||
= SpriteMorph.prototype.allHatBlocksForInteraction;
|
||||
|
||||
StageMorph.prototype.allGenericHatBlocks
|
||||
= SpriteMorph.prototype.allGenericHatBlocks;
|
||||
|
||||
// StageMorph events
|
||||
|
||||
StageMorph.prototype.mouseClickLeft
|
||||
|
|
|
@ -24,9 +24,10 @@
|
|||
world = new WorldMorph(document.getElementById('world'));
|
||||
world.worldCanvas.focus();
|
||||
new IDE_Morph().openIn(world);
|
||||
setInterval(loop, 1);
|
||||
loop();
|
||||
};
|
||||
function loop() {
|
||||
requestAnimationFrame(loop);
|
||||
world.doOneCycle();
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Snap! Build Your Own Blocks</title>
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<script type="text/javascript" src="morphic.js"></script>
|
||||
<script type="text/javascript" src="widgets.js"></script>
|
||||
<script type="text/javascript" src="blocks.js"></script>
|
||||
<script type="text/javascript" src="threads.js"></script>
|
||||
<script type="text/javascript" src="objects.js"></script>
|
||||
<script type="text/javascript" src="gui.js"></script>
|
||||
<script type="text/javascript" src="paint.js"></script>
|
||||
<script type="text/javascript" src="lists.js"></script>
|
||||
<script type="text/javascript" src="byob.js"></script>
|
||||
<script type="text/javascript" src="xml.js"></script>
|
||||
<script type="text/javascript" src="store.js"></script>
|
||||
<script type="text/javascript" src="locale.js"></script>
|
||||
<script type="text/javascript" src="cloud.js"></script>
|
||||
<script type="text/javascript" src="sha512.js"></script>
|
||||
<script type="text/javascript">
|
||||
var world;
|
||||
window.onload = function () {
|
||||
world = new WorldMorph(document.getElementById('world'));
|
||||
world.worldCanvas.focus();
|
||||
new IDE_Morph().openIn(world);
|
||||
setInterval(loop, 1);
|
||||
};
|
||||
function loop() {
|
||||
world.doOneCycle();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body style="margin: 0;">
|
||||
<canvas id="world" tabindex="1" style="position: absolute;" />
|
||||
</body>
|
||||
</html>
|
26
store.js
26
store.js
|
@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.store = '2015-October-07';
|
||||
modules.store = '2015-December-15';
|
||||
|
||||
|
||||
// XML_Serializer ///////////////////////////////////////////////////////
|
||||
|
@ -792,7 +792,7 @@ SnapSerializer.prototype.loadCustomBlocks = function (
|
|||
// private
|
||||
var myself = this;
|
||||
element.children.forEach(function (child) {
|
||||
var definition, names, inputs, header, code, comment, i;
|
||||
var definition, names, inputs, vars, header, code, comment, i;
|
||||
if (child.tag !== 'block-definition') {
|
||||
return;
|
||||
}
|
||||
|
@ -836,6 +836,13 @@ SnapSerializer.prototype.loadCustomBlocks = function (
|
|||
});
|
||||
}
|
||||
|
||||
vars = child.childNamed('variables');
|
||||
if (vars) {
|
||||
definition.variableNames = myself.loadValue(
|
||||
vars.require('list')
|
||||
).asArray();
|
||||
}
|
||||
|
||||
header = child.childNamed('header');
|
||||
if (header) {
|
||||
definition.codeHeader = header.contents;
|
||||
|
@ -1047,7 +1054,9 @@ SnapSerializer.prototype.loadBlock = function (model, isReporter) {
|
|||
block.isDraggable = true;
|
||||
inputs = block.inputs();
|
||||
model.children.forEach(function (child, i) {
|
||||
if (child.tag === 'comment') {
|
||||
if (child.tag === 'variables') {
|
||||
this.loadVariables(block.variables, child);
|
||||
} else if (child.tag === 'comment') {
|
||||
block.comment = this.loadComment(child);
|
||||
block.comment.block = block;
|
||||
} else if (child.tag === 'receiver') {
|
||||
|
@ -1728,11 +1737,16 @@ CustomCommandBlockMorph.prototype.toBlockXML = function (serializer) {
|
|||
var scope = this.definition.isGlobal ? undefined
|
||||
: this.definition.receiver.name;
|
||||
return serializer.format(
|
||||
'<custom-block s="@"%>%%%</custom-block>',
|
||||
'<custom-block s="@"%>%%%%</custom-block>',
|
||||
this.blockSpec,
|
||||
this.definition.isGlobal ?
|
||||
'' : serializer.format(' scope="@"', scope),
|
||||
serializer.store(this.inputs()),
|
||||
this.definition.variableNames.length ?
|
||||
'<variables>' +
|
||||
this.variables.toXML(serializer) +
|
||||
'</variables>'
|
||||
: '',
|
||||
this.comment ? this.comment.toXML(serializer) : '',
|
||||
scope && !this.definition.receiver[serializer.idProperty] ?
|
||||
'<receiver>' +
|
||||
|
@ -1748,6 +1762,7 @@ CustomReporterBlockMorph.prototype.toBlockXML
|
|||
CustomBlockDefinition.prototype.toXML = function (serializer) {
|
||||
var myself = this;
|
||||
|
||||
|
||||
function encodeScripts(array) {
|
||||
return array.reduce(function (xml, element) {
|
||||
if (element instanceof BlockMorph) {
|
||||
|
@ -1763,6 +1778,7 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
|
|||
return serializer.format(
|
||||
'<block-definition s="@" type="@" category="@">' +
|
||||
'%' +
|
||||
(this.variableNames.length ? '<variables>%</variables>' : '@') +
|
||||
'<header>@</header>' +
|
||||
'<code>@</code>' +
|
||||
'<inputs>%</inputs>%%' +
|
||||
|
@ -1771,6 +1787,8 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
|
|||
this.type,
|
||||
this.category || 'other',
|
||||
this.comment ? this.comment.toXML(serializer) : '',
|
||||
(this.variableNames.length ?
|
||||
serializer.store(new List(this.variableNames)) : ''),
|
||||
this.codeHeader || '',
|
||||
this.codeMapping || '',
|
||||
Object.keys(this.declarations).reduce(function (xml, decl) {
|
||||
|
|
221
threads.js
221
threads.js
|
@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.threads = '2015-November-16';
|
||||
modules.threads = '2015-December-15';
|
||||
|
||||
var ThreadManager;
|
||||
var Process;
|
||||
|
@ -128,24 +128,67 @@ function snapEquals(a, b) {
|
|||
return x === y;
|
||||
}
|
||||
|
||||
function invoke(block, timeout) {
|
||||
// exectue the given block synchronously, i.e. without yielding.
|
||||
// if a timeout (in milliseconds) is specified, abort execution
|
||||
function invoke(
|
||||
action, // a BlockMorph or a Context, a reified ("ringified") block
|
||||
contextArgs, // optional List of arguments for the context, or null
|
||||
receiver, // optional sprite or environment
|
||||
timeout, // msecs
|
||||
timeoutErrorMsg, // string
|
||||
suppressErrors // bool
|
||||
) {
|
||||
// execute the given block or context synchronously without yielding.
|
||||
// Apply context (not a block) to a list of optional arguments.
|
||||
// Receiver (sprite, stage or environment), timeout etc. are optional.
|
||||
// If a timeout (in milliseconds) is specified, abort execution
|
||||
// after the timeout has been reached and throw an error.
|
||||
// For debugging purposes only.
|
||||
// SuppressErrors (bool) if non-timeout errors occurring in the
|
||||
// block are handled elsewhere.
|
||||
// This is highly experimental.
|
||||
// Caution: Kids, do not try this at home!
|
||||
// use ThreadManager::startProcess instead
|
||||
var proc = new Process(block),
|
||||
startTime = Date.now();
|
||||
while (proc.isRunning()) {
|
||||
if (timeout && ((Date.now() - startTime) > timeout)) {
|
||||
throw (new Error("a synchronous Snap! script has timed out"));
|
||||
// Use ThreadManager::startProcess with a callback instead
|
||||
|
||||
var proc = new Process(),
|
||||
deadline = (timeout ? Date.now() + timeout : null),
|
||||
rcvr;
|
||||
|
||||
if (action instanceof Context) {
|
||||
if (receiver) {
|
||||
action = proc.reportContextFor(receiver);
|
||||
}
|
||||
proc.runStep();
|
||||
proc.initializeFor(action, contextArgs || new List());
|
||||
} else if (action instanceof BlockMorph) {
|
||||
proc.topBlock = action;
|
||||
rcvr = receiver || action.receiver();
|
||||
if (rcvr) {
|
||||
proc.homeContext = new Context();
|
||||
proc.homeContext.receiver = rcvr;
|
||||
if (rcvr.variables) {
|
||||
proc.homeContext.variables.parentFrame = rcvr.variables;
|
||||
}
|
||||
}
|
||||
proc.context = new Context(
|
||||
null,
|
||||
action.blockSequence(),
|
||||
proc.homeContext
|
||||
);
|
||||
} else {
|
||||
throw new Error('expecting a block or ring but getting ' + action);
|
||||
}
|
||||
if (block instanceof ReporterBlockMorph) {
|
||||
return proc.homeContext.inputs[0];
|
||||
if (suppressErrors) {
|
||||
proc.isCatchingErrors = false;
|
||||
}
|
||||
while (proc.isRunning()) {
|
||||
if (deadline && (Date.now() > deadline)) {
|
||||
throw (new Error(
|
||||
localize(
|
||||
timeoutErrorMsg ||
|
||||
"a synchronous Snap! script has timed out")
|
||||
)
|
||||
);
|
||||
}
|
||||
proc.runStep(deadline);
|
||||
}
|
||||
return proc.homeContext.inputs[0];
|
||||
}
|
||||
|
||||
// ThreadManager ///////////////////////////////////////////////////////
|
||||
|
@ -154,12 +197,14 @@ function ThreadManager() {
|
|||
this.processes = [];
|
||||
}
|
||||
|
||||
ThreadManager.prototype.pauseCustomHatBlocks = false;
|
||||
|
||||
ThreadManager.prototype.toggleProcess = function (block) {
|
||||
var active = this.findProcess(block);
|
||||
if (active) {
|
||||
active.stop();
|
||||
} else {
|
||||
return this.startProcess(block);
|
||||
return this.startProcess(block, null, null, null, true);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -167,7 +212,8 @@ ThreadManager.prototype.startProcess = function (
|
|||
block,
|
||||
isThreadSafe,
|
||||
exportResult,
|
||||
callback
|
||||
callback,
|
||||
isClicked
|
||||
) {
|
||||
var active = this.findProcess(block),
|
||||
top = block.topBlock(),
|
||||
|
@ -181,6 +227,7 @@ ThreadManager.prototype.startProcess = function (
|
|||
}
|
||||
newProc = new Process(block.topBlock(), callback);
|
||||
newProc.exportResult = exportResult;
|
||||
newProc.isClicked = isClicked || false;
|
||||
if (!newProc.homeContext.receiver.isClone) {
|
||||
top.addHighlight();
|
||||
}
|
||||
|
@ -267,7 +314,7 @@ ThreadManager.prototype.removeTerminatedProcesses = function () {
|
|||
}
|
||||
}
|
||||
|
||||
if (proc.topBlock instanceof ReporterBlockMorph) {
|
||||
if (proc.topBlock instanceof ReporterBlockMorph || proc.isShowingResult) {
|
||||
if (proc.onComplete instanceof Function) {
|
||||
proc.onComplete(proc.homeContext.inputs[0]);
|
||||
} else {
|
||||
|
@ -303,6 +350,38 @@ ThreadManager.prototype.findProcess = function (block) {
|
|||
);
|
||||
};
|
||||
|
||||
ThreadManager.prototype.doWhen = function (block, stopIt) {
|
||||
if (this.pauseCustomHatBlocks) {return; }
|
||||
var pred = block.inputs()[0];
|
||||
if (block.removeHighlight()) {
|
||||
block.world().hand.destroyTemporaries();
|
||||
}
|
||||
if (stopIt) {return; }
|
||||
if ((!block) ||
|
||||
!(pred instanceof ReporterBlockMorph) ||
|
||||
this.findProcess(block)
|
||||
) {return; }
|
||||
try {
|
||||
if (invoke(
|
||||
pred,
|
||||
null,
|
||||
null,
|
||||
20,
|
||||
'the predicate takes\ntoo long for a\ncustom hat block',
|
||||
true // suppress errors => handle them right here instead
|
||||
) === true) {
|
||||
this.startProcess(block);
|
||||
}
|
||||
} catch (error) {
|
||||
block.addErrorHighlight();
|
||||
block.showBubble(
|
||||
error.name
|
||||
+ '\n'
|
||||
+ error.message
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Process /////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
|
@ -349,6 +428,10 @@ ThreadManager.prototype.findProcess = function (block) {
|
|||
httpRequest active instance of an HttpRequest or null
|
||||
pauseOffset msecs between the start of an interpolated operation
|
||||
and when the process was paused
|
||||
isClicked boolean flag indicating whether the process was
|
||||
initiated by a user-click on a block
|
||||
isShowingResult boolean flag indicating whether a "report" command
|
||||
has been executed in a user-clicked process
|
||||
exportResult boolean flag indicating whether a picture of the top
|
||||
block along with the result bubble shoud be exported
|
||||
onComplete an optional callback function to be executed when
|
||||
|
@ -369,6 +452,8 @@ function Process(topBlock, onComplete) {
|
|||
this.readyToYield = false;
|
||||
this.readyToTerminate = false;
|
||||
this.isDead = false;
|
||||
this.isClicked = false;
|
||||
this.isShowingResult = false;
|
||||
this.errorFlag = false;
|
||||
this.context = null;
|
||||
this.homeContext = new Context();
|
||||
|
@ -404,7 +489,7 @@ Process.prototype.isRunning = function () {
|
|||
|
||||
// Process entry points
|
||||
|
||||
Process.prototype.runStep = function () {
|
||||
Process.prototype.runStep = function (deadline) {
|
||||
// a step is an an uninterruptable 'atom', it can consist
|
||||
// of several contexts, even of several blocks
|
||||
|
||||
|
@ -422,6 +507,14 @@ Process.prototype.runStep = function () {
|
|||
if (this.isPaused) {
|
||||
return this.pauseStep();
|
||||
}
|
||||
if (deadline && (Date.now() > deadline)) {
|
||||
if (this.isAtomic &&
|
||||
this.homeContext.receiver &&
|
||||
this.homeContext.receiver.endWarp) {
|
||||
this.homeContext.receiver.endWarp();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.evaluateContext();
|
||||
}
|
||||
this.lastYield = Date.now();
|
||||
|
@ -505,9 +598,12 @@ Process.prototype.evaluateContext = function () {
|
|||
};
|
||||
|
||||
Process.prototype.evaluateBlock = function (block, argCount) {
|
||||
var selector = block.selector;
|
||||
// check for special forms
|
||||
if (contains(['reportOr', 'reportAnd', 'doReport'], block.selector)) {
|
||||
return this[block.selector](block);
|
||||
if (selector === 'reportOr' ||
|
||||
selector === 'reportAnd' ||
|
||||
selector === 'doReport') {
|
||||
return this[selector](block);
|
||||
}
|
||||
|
||||
// first evaluate all inputs, then apply the primitive
|
||||
|
@ -517,13 +613,13 @@ Process.prototype.evaluateBlock = function (block, argCount) {
|
|||
if (argCount > inputs.length) {
|
||||
this.evaluateNextInput(block);
|
||||
} else {
|
||||
if (this[block.selector]) {
|
||||
if (this[selector]) {
|
||||
rcvr = this;
|
||||
}
|
||||
if (this.isCatchingErrors) {
|
||||
try {
|
||||
this.returnValueToParentContext(
|
||||
rcvr[block.selector].apply(rcvr, inputs)
|
||||
rcvr[selector].apply(rcvr, inputs)
|
||||
);
|
||||
this.popContext();
|
||||
} catch (error) {
|
||||
|
@ -531,7 +627,7 @@ Process.prototype.evaluateBlock = function (block, argCount) {
|
|||
}
|
||||
} else {
|
||||
this.returnValueToParentContext(
|
||||
rcvr[block.selector].apply(rcvr, inputs)
|
||||
rcvr[selector].apply(rcvr, inputs)
|
||||
);
|
||||
this.popContext();
|
||||
}
|
||||
|
@ -574,6 +670,9 @@ Process.prototype.reportAnd = function (block) {
|
|||
|
||||
Process.prototype.doReport = function (block) {
|
||||
var outer = this.context.outerContext;
|
||||
if (this.isClicked && (block.topBlock() === this.topBlock)) {
|
||||
this.isShowingResult = true;
|
||||
}
|
||||
if (this.context.expression.partOfCustomCommand) {
|
||||
this.doStopCustomBlock();
|
||||
this.popContext();
|
||||
|
@ -655,10 +754,9 @@ Process.prototype.evaluateInput = function (input) {
|
|||
} else {
|
||||
ans = input.evaluate();
|
||||
if (ans) {
|
||||
if (contains(
|
||||
[CommandSlotMorph, ReporterSlotMorph],
|
||||
input.constructor
|
||||
) || (input instanceof CSlotMorph &&
|
||||
if (input.constructor === CommandSlotMorph ||
|
||||
input.constructor === ReporterSlotMorph ||
|
||||
(input instanceof CSlotMorph &&
|
||||
(!input.isStatic || input.isLambda))) {
|
||||
// I know, this still needs yet to be done right....
|
||||
ans = this.reify(ans, new List());
|
||||
|
@ -718,6 +816,7 @@ Process.prototype.evaluateNextInput = function (element) {
|
|||
var nxt = this.context.inputs.length,
|
||||
args = element.inputs(),
|
||||
exp = args[nxt],
|
||||
sel = this.context.expression.selector,
|
||||
outer = this.context.outerContext; // for tail call elimination
|
||||
|
||||
if (exp.isUnevaluated) {
|
||||
|
@ -729,8 +828,7 @@ Process.prototype.evaluateNextInput = function (element) {
|
|||
THE SCRIPT), because those allow for additional
|
||||
explicit parameter bindings.
|
||||
*/
|
||||
if (contains(['reify', 'reportScript'],
|
||||
this.context.expression.selector)) {
|
||||
if (sel === 'reify' || sel === 'reportScript') {
|
||||
this.context.addInput(exp);
|
||||
} else {
|
||||
this.context.addInput(this.reify(exp, new List()));
|
||||
|
@ -944,6 +1042,15 @@ Process.prototype.evaluate = function (
|
|||
};
|
||||
|
||||
Process.prototype.fork = function (context, args) {
|
||||
var proc = new Process(),
|
||||
stage = this.homeContext.receiver.parentThatIsA(StageMorph);
|
||||
proc.initializeFor(context, args);
|
||||
proc.pushContext('doYield');
|
||||
stage.threads.processes.push(proc);
|
||||
};
|
||||
|
||||
Process.prototype.initializeFor = function (context, args) {
|
||||
// used by Process.fork() and global invoke()
|
||||
if (context.isContinuation) {
|
||||
throw new Error(
|
||||
'continuations cannot be forked'
|
||||
|
@ -961,8 +1068,7 @@ Process.prototype.fork = function (context, args) {
|
|||
parms = args.asArray(),
|
||||
i,
|
||||
value,
|
||||
stage = this.homeContext.receiver.parentThatIsA(StageMorph),
|
||||
proc = new Process();
|
||||
exit;
|
||||
|
||||
// assign parameters if any were passed
|
||||
if (parms.length > 0) {
|
||||
|
@ -1008,13 +1114,24 @@ Process.prototype.fork = function (context, args) {
|
|||
|
||||
if (runnable.expression instanceof CommandBlockMorph) {
|
||||
runnable.expression = runnable.expression.blockSequence();
|
||||
|
||||
// insert a tagged exit context
|
||||
// which "report" can catch later
|
||||
// needed for invoke() situations
|
||||
exit = new Context(
|
||||
runnable.parentContext,
|
||||
'expectReport',
|
||||
outer,
|
||||
outer.receiver
|
||||
);
|
||||
exit.tag = 'exit';
|
||||
runnable.parentContext = exit;
|
||||
}
|
||||
|
||||
proc.homeContext = context.outerContext;
|
||||
proc.topBlock = context.expression;
|
||||
proc.context = runnable;
|
||||
proc.pushContext('doYield');
|
||||
stage.threads.processes.push(proc);
|
||||
this.homeContext = new Context(); // context.outerContext;
|
||||
this.homeContext.receiver = context.outerContext.receiver;
|
||||
this.topBlock = context.expression;
|
||||
this.context = runnable;
|
||||
};
|
||||
|
||||
// Process stopping blocks primitives
|
||||
|
@ -1091,8 +1208,21 @@ Process.prototype.evaluateCustomBlock = function () {
|
|||
this.procedureCount += 1;
|
||||
outer = new Context();
|
||||
outer.receiver = this.context.receiver;
|
||||
outer.variables.parentFrame = outer.receiver ?
|
||||
outer.receiver.variables : null;
|
||||
|
||||
outer.variables.parentFrame = this.context.expression.variables;
|
||||
|
||||
// block (instance) var support, experimental:
|
||||
// only splice in block vars if any are defined, because block vars
|
||||
// can cause race conditions in global block definitions that
|
||||
// access sprite-local variables at the same time.
|
||||
if (this.context.expression.definition.variableNames.length) {
|
||||
this.context.expression.variables.parentFrame = outer.receiver ?
|
||||
outer.receiver.variables : null;
|
||||
} else {
|
||||
// original code without block variables:
|
||||
outer.variables.parentFrame = outer.receiver ?
|
||||
outer.receiver.variables : null;
|
||||
}
|
||||
|
||||
runnable = new Context(
|
||||
this.context.parentContext,
|
||||
|
@ -1420,7 +1550,10 @@ Process.prototype.reportListItem = function (index, list) {
|
|||
};
|
||||
|
||||
Process.prototype.reportListLength = function (list) {
|
||||
return list.length();
|
||||
if (list instanceof List) {
|
||||
return list.length();
|
||||
}
|
||||
return list.length; // catch a common student error
|
||||
};
|
||||
|
||||
Process.prototype.reportListContainsItem = function (list, element) {
|
||||
|
@ -2110,10 +2243,12 @@ Process.prototype.reportIsIdentical = function (a, b) {
|
|||
|
||||
Process.prototype.isImmutable = function (obj) {
|
||||
// private
|
||||
return contains(
|
||||
['nothing', 'Boolean', 'text', 'number', 'undefined'],
|
||||
this.reportTypeOf(obj)
|
||||
);
|
||||
var type = this.reportTypeOf(obj);
|
||||
return type === 'nothing' ||
|
||||
type === 'Boolean' ||
|
||||
type === 'text' ||
|
||||
type === 'number' ||
|
||||
type === 'undefined';
|
||||
};
|
||||
|
||||
Process.prototype.reportTrue = function () {
|
||||
|
|
Ładowanie…
Reference in New Issue