summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJens Mönig <jens@moenig.org>2015-03-21 11:12:43 +0100
committerJens Mönig <jens@moenig.org>2015-03-21 11:12:43 +0100
commit4acf1896bd9fa57d3dbe3f4721b6790c6f3b0d56 (patch)
tree8d44be9ed6bb5458d4354d59200783669dbc5cb2
parentfdd2ecf7d91d8026d3ca42c4bd52774877c72645 (diff)
downloadsnap-4acf1896bd9fa57d3dbe3f4721b6790c6f3b0d56.tar.gz
snap-4acf1896bd9fa57d3dbe3f4721b6790c6f3b0d56.zip
Prototypal inheritance of sprite-local variables
(experimental) slotwise inheritance à la Henry Lieberman for sprite-local variables. see http://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.htm l Let a sprite inherit another sprite’s local variables by making it the “parent” in the sprite-icon’s context menu (the button icon in the sprite corral underneath the stage). The child not only inherits the variable slot but also - dynamically - the parent variable’s value. Changing the parent’s variable value also changes it for every child. If a child uses SET or CHANGE on an inherited variable it automatically “shadows” it with its own value, thereby stopping dynamic participation in the parent slot’s value (in effect dis-inheriting that slot). Deleting a shadowed variable slot once again reinstates its inheritance status. inherited variables are shown as “ghosted” both in the child’s variables palette and in such stage watchers. “Shadowing” them un-ghosts both the variable blob template in the palette and the watcher onstage (if any). Deleting a shadowed variable once again ghosts the watcher and the palette block template. Delete a (shadowed) variable either via the “Delete a variable” button in the IDE or using the new “Delete” block in the variables category
-rw-r--r--blocks.js12
-rw-r--r--gui.js18
-rwxr-xr-xhistory.txt4
-rw-r--r--morphic.js27
-rw-r--r--objects.js206
-rwxr-xr-xsnap.html2
-rw-r--r--store.js45
-rw-r--r--threads.js73
-rw-r--r--widgets.js11
9 files changed, 335 insertions, 63 deletions
diff --git a/blocks.js b/blocks.js
index aef09da..711976f 100644
--- a/blocks.js
+++ b/blocks.js
@@ -155,7 +155,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
// Global stuff ////////////////////////////////////////////////////////
-modules.blocks = '2015-March-09';
+modules.blocks = '2015-March-21';
var SyntaxElementMorph;
@@ -3016,6 +3016,12 @@ BlockMorph.prototype.alternateBlockColor = function () {
this.fixChildrensBlockColor(true); // has issues if not forced
};
+BlockMorph.prototype.ghost = function () {
+ this.setColor(
+ SpriteMorph.prototype.blockColor[this.category].lighter(35)
+ );
+};
+
BlockMorph.prototype.fixLabelColor = function () {
if (this.zebraContrast > 0 && this.category) {
var clr = SpriteMorph.prototype.blockColor[this.category];
@@ -3096,6 +3102,10 @@ BlockMorph.prototype.mouseClickLeft = function () {
}
};
+BlockMorph.prototype.reactToTemplateCopy = function () {
+ this.forceNormalColoring();
+};
+
// BlockMorph thumbnail
BlockMorph.prototype.thumbnail = function (scale, clipWidth, noShadow) {
diff --git a/gui.js b/gui.js
index 50f3cb2..4cd2a9b 100644
--- a/gui.js
+++ b/gui.js
@@ -69,7 +69,7 @@ SpeechBubbleMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.gui = '2015-March-15';
+modules.gui = '2015-March-21';
// Declarations
@@ -2598,7 +2598,7 @@ IDE_Morph.prototype.aboutSnap = function () {
module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn,
world = this.world();
- aboutTxt = 'Snap! 4.0\nBuild Your Own Blocks\n\n--- rc ---\n\n'
+ aboutTxt = 'Snap! 4.1 (OOP)\nBuild Your Own Blocks\n\n--- dev ---\n\n'
+ 'Copyright \u24B8 2015 Jens M\u00F6nig and '
+ 'Brian Harvey\n'
+ 'jens@moenig.org, bh@cs.berkeley.edu\n\n'
@@ -5182,7 +5182,7 @@ function SpriteIconMorph(aSprite, aTemplate) {
}
SpriteIconMorph.prototype.init = function (aSprite, aTemplate) {
- var colors, action, query, myself = this;
+ var colors, action, query, hover, myself = this;
if (!aTemplate) {
colors = [
@@ -5212,6 +5212,11 @@ SpriteIconMorph.prototype.init = function (aSprite, aTemplate) {
return false;
};
+ hover = function () {
+ if (!aSprite.exemplar) {return null; }
+ return (localize('parent' + ':\n' + aSprite.exemplar.name));
+ };
+
// additional properties:
this.object = aSprite || new SpriteMorph(); // mandatory, actually
this.version = this.object.version;
@@ -5227,7 +5232,7 @@ SpriteIconMorph.prototype.init = function (aSprite, aTemplate) {
this.object.name, // label string
query, // predicate/selector
null, // environment
- null, // hint
+ hover, // hint
aTemplate // optional, for cached background images
);
@@ -5398,6 +5403,7 @@ SpriteIconMorph.prototype.userMenu = function () {
menu.addItem("duplicate", 'duplicateSprite');
menu.addItem("delete", 'removeSprite');
menu.addLine();
+ menu.addItem("parent...", 'chooseExemplar');
if (this.object.anchor) {
menu.addItem(
localize('detach from') + ' ' + this.object.anchor.name,
@@ -5432,6 +5438,10 @@ SpriteIconMorph.prototype.exportSprite = function () {
this.object.exportSprite();
};
+SpriteIconMorph.prototype.chooseExemplar = function () {
+ this.object.chooseExemplar();
+};
+
SpriteIconMorph.prototype.showSpriteOnStage = function () {
this.object.showOnStage();
};
diff --git a/history.txt b/history.txt
index b53e59c..e02d368 100755
--- a/history.txt
+++ b/history.txt
@@ -2476,3 +2476,7 @@ ______
------
* Store: fixed #743
* GUI, html: switch from beta to release candidate
+
+150321
+------
+* OOP: Prototypal inheritance of sprite-local variables
diff --git a/morphic.js b/morphic.js
index 9616897..359e552 100644
--- a/morphic.js
+++ b/morphic.js
@@ -8,7 +8,7 @@
written by Jens Mönig
jens@moenig.org
- Copyright (C) 2014 by Jens Mönig
+ Copyright (C) 2015 by Jens Mönig
This file is part of Snap!.
@@ -527,6 +527,12 @@
a duplicate of the template whose "isDraggable" flag is true and
whose "isTemplate" flag is false, in other words: a non-template.
+ When creating a copy from a template, the copy's
+
+ reactToTemplateCopy
+
+ is invoked, if it is present.
+
Dragging is indicated by adding a drop shadow to the morph in hand.
If a morph follows the hand without displaying a drop shadow it is
merely being moved about without changing its parent (owner morph),
@@ -1020,9 +1026,10 @@
programming hero.
I have originally written morphic.js in Florian Balmer's Notepad2
- editor for Windows and later switched to Apple's Dashcode. I've also
- come to depend on both Douglas Crockford's JSLint, Mozilla's Firebug
- and Google's Chrome to get it right.
+ editor for Windows, later switched to Apple's Dashcode and later
+ still to Apple's Xcode. I've also come to depend on both Douglas
+ Crockford's JSLint, Mozilla's Firebug and Google's Chrome to get
+ it right.
IX. contributors
@@ -1041,7 +1048,7 @@
/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio,
FileList, getBlurredShadowSupport*/
-var morphicVersion = '2014-December-05';
+var morphicVersion = '2015-March-21';
var modules = {}; // keep track of additional loaded modules
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
@@ -8060,7 +8067,7 @@ TriggerMorph.prototype.init = function (
this.environment = environment || null;
this.labelString = labelString || null;
this.label = null;
- this.hint = hint || null;
+ this.hint = hint || null; // null, String, or Function
this.fontSize = fontSize || MorphicPreferences.menuFontSize;
this.fontStyle = fontStyle || 'sans-serif';
this.highlightColor = new Color(192, 192, 192);
@@ -8209,10 +8216,11 @@ TriggerMorph.prototype.triggerDoubleClick = function () {
// TriggerMorph events:
TriggerMorph.prototype.mouseEnter = function () {
+ var contents = this.hint instanceof Function ? this.hint() : this.hint;
this.image = this.highlightImage;
this.changed();
- if (this.hint) {
- this.bubbleHelp(this.hint);
+ if (contents) {
+ this.bubbleHelp(contents);
}
};
@@ -9683,6 +9691,9 @@ HandMorph.prototype.processMouseMove = function (event) {
morph = this.morphToGrab.fullCopy();
morph.isTemplate = false;
morph.isDraggable = true;
+ if (morph.reactToTemplateCopy) {
+ morph.reactToTemplateCopy();
+ }
this.grab(morph);
this.grabOrigin = this.morphToGrab.situation();
}
diff --git a/objects.js b/objects.js
index 1fe1309..56e5806 100644
--- a/objects.js
+++ b/objects.js
@@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.objects = '2015-February-28';
+modules.objects = '2015-March-21';
var SpriteMorph;
var StageMorph;
@@ -1103,6 +1103,13 @@ SpriteMorph.prototype.initBlocks = function () {
spec: 'script variables %scriptVars'
},
+ // inheritance - experimental
+ doDeleteAttr: {
+ type: 'command',
+ category: 'variables',
+ spec: 'delete %var'
+ },
+
// Lists
reportNewList: {
type: 'reporter',
@@ -1348,11 +1355,13 @@ SpriteMorph.prototype.init = function (globals) {
'confetti': 0
};
+ // sprite inheritance
+ this.exemplar = null;
+
SpriteMorph.uber.init.call(this);
this.isDraggable = true;
this.isDown = false;
-
this.heading = 90;
this.changed();
this.drawNew();
@@ -1642,7 +1651,8 @@ SpriteMorph.prototype.variableBlock = function (varName) {
SpriteMorph.prototype.blockTemplates = function (category) {
var blocks = [], myself = this, varNames, button,
- cat = category || 'motion', txt;
+ cat = category || 'motion', txt,
+ inheritedVars = this.inheritedVariableNames();
function block(selector) {
if (StageMorph.prototype.hiddenPrimitives[selector]) {
@@ -1657,6 +1667,9 @@ SpriteMorph.prototype.blockTemplates = function (category) {
var newBlock = SpriteMorph.prototype.variableBlock(varName);
newBlock.isDraggable = false;
newBlock.isTemplate = true;
+ if (contains(inheritedVars, varName)) {
+ newBlock.ghost();
+ }
return newBlock;
}
@@ -1705,15 +1718,16 @@ SpriteMorph.prototype.blockTemplates = function (category) {
}
function addVar(pair) {
+ var ide;
if (pair) {
- if (myself.variables.silentFind(pair[0])) {
+ if (myself.isVariableNameInUse(pair[0])) {
myself.inform('that name is already in use');
} else {
+ ide = myself.parentThatIsA(IDE_Morph);
myself.addVariable(pair[0], pair[1]);
myself.toggleVariableWatcher(pair[0], pair[1]);
- myself.blocksCache[cat] = null;
- myself.paletteCache[cat] = null;
- myself.parentThatIsA(IDE_Morph).refreshPalette();
+ ide.flushBlocksCache('variables'); // b/c of inheritance
+ ide.refreshPalette();
}
}
}
@@ -2028,7 +2042,7 @@ SpriteMorph.prototype.blockTemplates = function (category) {
button.showHelp = BlockMorph.prototype.showHelp;
blocks.push(button);
- if (this.variables.allNames().length > 0) {
+ if (this.deletableVariableNames().length > 0) {
button = new PushButtonMorph(
null,
function () {
@@ -2037,7 +2051,7 @@ SpriteMorph.prototype.blockTemplates = function (category) {
null,
myself
);
- myself.variables.allNames().forEach(function (name) {
+ myself.deletableVariableNames().forEach(function (name) {
menu.addItem(name, name);
});
menu.popUpAtHand(myself.world());
@@ -2067,6 +2081,13 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push(block('doHideVar'));
blocks.push(block('doDeclareVariables'));
+ // inheritance:
+
+ blocks.push('-');
+ blocks.push(block('doDeleteAttr'));
+
+ ///////////////////////////////
+
blocks.push('=');
blocks.push(block('reportNewList'));
@@ -2491,7 +2512,7 @@ SpriteMorph.prototype.searchBlocks = function () {
SpriteMorph.prototype.addVariable = function (name, isGlobal) {
var ide = this.parentThatIsA(IDE_Morph);
if (isGlobal) {
- this.variables.parentFrame.addVar(name);
+ this.globalVariables().addVar(name);
if (ide) {
ide.flushBlocksCache('variables');
}
@@ -2503,7 +2524,10 @@ SpriteMorph.prototype.addVariable = function (name, isGlobal) {
SpriteMorph.prototype.deleteVariable = function (varName) {
var ide = this.parentThatIsA(IDE_Morph);
- this.deleteVariableWatcher(varName);
+ if (!contains(this.inheritedVariableNames(true), varName)) {
+ // check only shadowed variables
+ this.deleteVariableWatcher(varName);
+ }
this.variables.deleteVar(varName);
if (ide) {
ide.flushBlocksCache('variables'); // b/c the var could be global
@@ -3643,6 +3667,7 @@ SpriteMorph.prototype.reportThreadCount = function () {
SpriteMorph.prototype.findVariableWatcher = function (varName) {
var stage = this.parentThatIsA(StageMorph),
+ globals = this.globalVariables(),
myself = this;
if (stage === null) {
return null;
@@ -3652,7 +3677,7 @@ SpriteMorph.prototype.findVariableWatcher = function (varName) {
function (morph) {
return morph instanceof WatcherMorph
&& (morph.target === myself.variables
- || morph.target === myself.variables.parentFrame)
+ || morph.target === globals)
&& morph.getter === varName;
}
);
@@ -3660,6 +3685,7 @@ SpriteMorph.prototype.findVariableWatcher = function (varName) {
SpriteMorph.prototype.toggleVariableWatcher = function (varName, isGlobal) {
var stage = this.parentThatIsA(StageMorph),
+ globals = this.globalVariables(),
watcher,
others;
if (stage === null) {
@@ -3679,12 +3705,12 @@ SpriteMorph.prototype.toggleVariableWatcher = function (varName, isGlobal) {
// if no watcher exists, create a new one
if (isNil(isGlobal)) {
- isGlobal = contains(this.variables.parentFrame.names(), varName);
+ isGlobal = contains(globals.names(), varName);
}
watcher = new WatcherMorph(
varName,
this.blockColor.variables,
- isGlobal ? this.variables.parentFrame : this.variables,
+ isGlobal ? globals : this.variables,
varName
);
watcher.setPosition(stage.position().add(10));
@@ -4110,6 +4136,118 @@ SpriteMorph.prototype.restoreLayers = function () {
this.layers = null;
};
+// SpriteMorph inheritance - general
+
+SpriteMorph.prototype.chooseExemplar = function () {
+ var stage = this.parentThatIsA(StageMorph),
+ myself = this,
+ other = stage.children.filter(function (m) {
+ return m instanceof SpriteMorph &&
+ (!contains(m.allExemplars(), myself));
+ }),
+ menu;
+ menu = new MenuMorph(
+ function (aSprite) {myself.setExemplar(aSprite); },
+ localize('current parent') +
+ ':\n' +
+ (this.exemplar ? this.exemplar.name : localize('none'))
+ );
+ other.forEach(function (eachSprite) {
+ menu.addItem(eachSprite.name, eachSprite);
+ });
+ menu.addLine();
+ menu.addItem(localize('none'), null);
+ menu.popUpAtHand(this.world());
+};
+
+SpriteMorph.prototype.setExemplar = function (another) {
+ var ide = this.parentThatIsA(IDE_Morph);
+ this.exemplar = another;
+ if (isNil(another)) {
+ this.variables.parentFrame = (this.globalVariables());
+ } else {
+ this.variables.parentFrame = (another.variables);
+ }
+ if (ide) {
+ ide.flushBlocksCache('variables');
+ ide.refreshPalette();
+ }
+};
+
+SpriteMorph.prototype.allExemplars = function () {
+ // including myself
+ var all = [],
+ current = this;
+ while (!isNil(current)) {
+ all.push(current);
+ current = current.exemplar;
+ }
+ return all;
+};
+
+SpriteMorph.prototype.specimens = function () {
+ // without myself
+ var myself = this;
+ return this.siblings().filter(function (m) {
+ return m instanceof SpriteMorph && (m.exemplar === myself);
+ });
+};
+
+SpriteMorph.prototype.allSpecimens = function () {
+ // without myself
+ var myself = this;
+ return this.siblings().filter(function (m) {
+ return m instanceof SpriteMorph && contains(m.allExemplars(), myself);
+ });
+};
+
+// SpriteMorph inheritance - variables
+
+SpriteMorph.prototype.isVariableNameInUse = function (vName) {
+ if (contains(this.variables.names(), vName)) {return true; }
+ return contains(this.globalVariables().names(), vName);
+};
+
+SpriteMorph.prototype.globalVariables = function () {
+ var current = this.variables.parentFrame;
+ while (current.owner) {
+ current = current.parentFrame;
+ }
+ return current;
+};
+
+SpriteMorph.prototype.shadowVar = function (name, value) {
+ var ide = this.parentThatIsA(IDE_Morph);
+ this.variables.addVar(name, value);
+ ide.flushBlocksCache('variables');
+ ide.refreshPalette();
+};
+
+SpriteMorph.prototype.inheritedVariableNames = function (shadowedOnly) {
+ var names = [],
+ own = this.variables.names(),
+ current = this.variables.parentFrame;
+
+ function test(each) {
+ return shadowedOnly ? contains(own, each) : !contains(own, each);
+ }
+
+ while (current.owner instanceof SpriteMorph) {
+ names.push.apply(
+ names,
+ current.names().filter(test)
+ );
+ current = current.parentFrame;
+ }
+ return names;
+};
+
+SpriteMorph.prototype.deletableVariableNames = function () {
+ return this.variables.names().concat(
+ this.globalVariables().names()
+ );
+};
+
// SpriteMorph highlighting
SpriteMorph.prototype.addHighlight = function (oldHighlight) {
@@ -4966,7 +5104,7 @@ StageMorph.prototype.blockTemplates = function (category) {
function addVar(pair) {
if (pair) {
- if (myself.variables.silentFind(pair[0])) {
+ if (myself.isVariableNameInUse(pair[0])) {
myself.inform('that name is already in use');
} else {
myself.addVariable(pair[0], pair[1]);
@@ -5269,6 +5407,13 @@ StageMorph.prototype.blockTemplates = function (category) {
blocks.push(block('doHideVar'));
blocks.push(block('doDeclareVariables'));
+ // inheritance:
+
+ blocks.push('-');
+ blocks.push(block('doDeleteAttr'));
+
+ ///////////////////////////////
+
blocks.push('=');
blocks.push(block('reportNewList'));
@@ -5660,6 +5805,14 @@ StageMorph.prototype.doubleDefinitionsFor
StageMorph.prototype.replaceDoubleDefinitionsFor
= SpriteMorph.prototype.replaceDoubleDefinitionsFor;
+// StageMorph inheritance support - variables
+
+StageMorph.prototype.isVariableNameInUse
+ = SpriteMorph.prototype.isVariableNameInUse;
+
+StageMorph.prototype.globalVariables
+ = SpriteMorph.prototype.globalVariables;
+
// SpriteBubbleMorph ////////////////////////////////////////////////////////
/*
@@ -6892,13 +7045,32 @@ WatcherMorph.prototype.setSliderMax = function (num) {
// WatcherMorph updating:
WatcherMorph.prototype.update = function () {
- var newValue,
- num;
+ var newValue, sprite, num;
+
if (this.target && this.getter) {
this.updateLabel();
if (this.target instanceof VariableFrame) {
newValue = this.target.vars[this.getter] ?
this.target.vars[this.getter].value : undefined;
+ if (newValue === undefined && this.target.owner) {
+ sprite = this.target.owner;
+ if (contains(sprite.inheritedVariableNames(), this.getter)) {
+ newValue = this.target.getVar(this.getter);
+ // ghost cell color
+ this.cellMorph.setColor(
+ SpriteMorph.prototype.blockColor.variables
+ .lighter(35)
+ );
+ } else {
+ this.destroy();
+ return;
+ }
+ } else {
+ // un-ghost the cell color
+ this.cellMorph.setColor(
+ SpriteMorph.prototype.blockColor.variables
+ );
+ }
} else {
newValue = this.target[this.getter]();
}
diff --git a/snap.html b/snap.html
index 976bcd6..234ed1f 100755
--- a/snap.html
+++ b/snap.html
@@ -2,7 +2,7 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Snap! Build Your Own Blocks</title>
+ <title>Snap! Build Your Own Blocks. Development (OOP)</title>
<link rel="shortcut icon" href="favicon.ico">
<script type="text/javascript" src="morphic.js"></script>
<script type="text/javascript" src="widgets.js"></script>
diff --git a/store.js b/store.js
index d4a2b64..b2bb0a3 100644
--- a/store.js
+++ b/store.js
@@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/
// Global stuff ////////////////////////////////////////////////////////
-modules.store = '2015-March-15';
+modules.store = '2015-March-21';
// XML_Serializer ///////////////////////////////////////////////////////
@@ -459,9 +459,17 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) {
myself.loadValue(model);
});
- // restore nesting associations
+ // restore inheritance and nesting associations
myself.project.stage.children.forEach(function (sprite) {
- var anchor;
+ var exemplar, anchor;
+ if (sprite.inheritanceInfo) { // only sprites can inherit
+ exemplar = myself.project.sprites[
+ sprite.inheritanceInfo.exemplar
+ ];
+ if (exemplar) {
+ sprite.setExemplar(exemplar);
+ }
+ }
if (sprite.nestingInfo) { // only sprites may have nesting info
anchor = myself.project.sprites[sprite.nestingInfo.anchor];
if (anchor) {
@@ -471,6 +479,7 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) {
}
});
myself.project.stage.children.forEach(function (sprite) {
+ delete sprite.inheritanceInfo;
if (sprite.nestingInfo) { // only sprites may have nesting info
sprite.nestingScale = +(sprite.nestingInfo.scale || sprite.scale);
delete sprite.nestingInfo;
@@ -639,9 +648,17 @@ SnapSerializer.prototype.loadSprites = function (xmlString, ide) {
myself.loadObject(sprite, model);
});
- // restore nesting associations
+ // restore inheritance and nesting associations
project.stage.children.forEach(function (sprite) {
- var anchor;
+ var exemplar, anchor;
+ if (sprite.inheritanceInfo) { // only sprites can inherit
+ exemplar = project.sprites[
+ sprite.inheritanceInfo.exemplar
+ ];
+ if (exemplar) {
+ sprite.setExemplar(exemplar);
+ }
+ }
if (sprite.nestingInfo) { // only sprites may have nesting info
anchor = project.sprites[sprite.nestingInfo.anchor];
if (anchor) {
@@ -651,6 +668,7 @@ SnapSerializer.prototype.loadSprites = function (xmlString, ide) {
}
});
project.stage.children.forEach(function (sprite) {
+ delete sprite.inheritanceInfo;
if (sprite.nestingInfo) { // only sprites may have nesting info
sprite.nestingScale = +(sprite.nestingInfo.scale || sprite.scale);
delete sprite.nestingInfo;
@@ -690,6 +708,7 @@ SnapSerializer.prototype.loadMediaModel = function (xmlNode) {
SnapSerializer.prototype.loadObject = function (object, model) {
// private
var blocks = model.require('blocks');
+ this.loadInheritanceInfo(object, model);
this.loadNestingInfo(object, model);
this.loadCostumes(object, model);
this.loadSounds(object, model);
@@ -699,6 +718,14 @@ SnapSerializer.prototype.loadObject = function (object, model) {
this.loadScripts(object.scripts, model.require('scripts'));
};
+SnapSerializer.prototype.loadInheritanceInfo = function (object, model) {
+ // private
+ var info = model.childNamed('inherit');
+ if (info) {
+ object.inheritanceInfo = info.attributes;
+ }
+};
+
SnapSerializer.prototype.loadNestingInfo = function (object, model) {
// private
var info = model.childNamed('nest');
@@ -1467,6 +1494,7 @@ SpriteMorph.prototype.toXML = function (serializer) {
' draggable="@"' +
'%' +
' costume="@" color="@,@,@" pen="@" ~>' +
+ '%' + // inheritance info
'%' + // nesting info
'<costumes>%</costumes>' +
'<sounds>%</sounds>' +
@@ -1489,6 +1517,13 @@ SpriteMorph.prototype.toXML = function (serializer) {
this.color.b,
this.penPoint,
+ // inheritance info
+ this.exemplar
+ ? '<inherit exemplar="' +
+ this.exemplar.name
+ + '"/>'
+ : '',
+
// nesting info
this.anchor
? '<nest anchor="' +
diff --git a/threads.js b/threads.js
index 50af8dd..d3dcfc9 100644
--- a/threads.js
+++ b/threads.js
@@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
// Global stuff ////////////////////////////////////////////////////////
-modules.threads = '2015-February-28';
+modules.threads = '2015-March-21';
var ThreadManager;
var Process;
@@ -1159,7 +1159,7 @@ Process.prototype.doSetVar = function (varName, value) {
name = name.expression.blockSpec;
}
}
- varFrame.setVar(name, value);
+ varFrame.setVar(name, value, this.blockReceiver());
};
Process.prototype.doChangeVar = function (varName, value) {
@@ -1171,7 +1171,7 @@ Process.prototype.doChangeVar = function (varName, value) {
name = name.expression.blockSpec;
}
}
- varFrame.changeVar(name, value);
+ varFrame.changeVar(name, value, this.blockReceiver());
};
Process.prototype.reportGetVar = function () {
@@ -1295,6 +1295,20 @@ Process.prototype.doRemoveTemporaries = function () {
}
};
+// Process sprite inheritance primitives
+
+Process.prototype.doDeleteAttr = function (attrName) {
+ // currently only variables are deletable
+ var name = attrName;
+
+ if (name instanceof Context) {
+ if (name.expression.selector === 'reportGetVar') {
+ name = name.expression.blockSpec;
+ }
+ }
+ this.blockReceiver().deleteVariable(name);
+};
+
// Process lists primitives
Process.prototype.reportNewList = function (elements) {
@@ -3103,35 +3117,50 @@ VariableFrame.prototype.silentFind = function (name) {
return null;
};
-VariableFrame.prototype.setVar = function (name, value) {
-/*
- change the specified variable if it exists
- else throw an error, because variables need to be
- declared explicitly (e.g. through a "script variables" block),
- before they can be accessed.
-*/
+VariableFrame.prototype.setVar = function (name, value, sender) {
+ // change the specified variable if it exists
+ // else throw an error, because variables need to be
+ // declared explicitly (e.g. through a "script variables" block),
+ // before they can be accessed.
+ // if the found frame is inherited by the sender sprite
+ // shadow it (create an explicit one for the sender)
+ // before setting the value ("create-on-write")
+
var frame = this.find(name);
if (frame) {
- frame.vars[name].value = value;
+ if (sender instanceof SpriteMorph &&
+ (frame.owner instanceof SpriteMorph) &&
+ (sender !== frame.owner)) {
+ sender.shadowVar(name, value);
+ } else {
+ frame.vars[name].value = value;
+ }
}
};
-VariableFrame.prototype.changeVar = function (name, delta) {
-/*
- change the specified variable if it exists
- else throw an error, because variables need to be
- declared explicitly (e.g. through a "script variables" block,
- before they can be accessed.
-*/
+VariableFrame.prototype.changeVar = function (name, delta, sender) {
+ // change the specified variable if it exists
+ // else throw an error, because variables need to be
+ // declared explicitly (e.g. through a "script variables" block,
+ // before they can be accessed.
+ // if the found frame is inherited by the sender sprite
+ // shadow it (create an explicit one for the sender)
+ // before changing the value ("create-on-write")
+
var frame = this.find(name),
- value;
+ value,
+ newValue;
if (frame) {
value = parseFloat(frame.vars[name].value);
- if (isNaN(value)) {
- frame.vars[name].value = delta;
+ newValue = isNaN(value) ? delta : value + parseFloat(delta);
+ if (sender instanceof SpriteMorph &&
+ (frame.owner instanceof SpriteMorph) &&
+ (sender !== frame.owner)) {
+ sender.shadowVar(name, newValue);
} else {
- frame.vars[name].value = value + parseFloat(delta);
+ frame.vars[name].value = newValue;
}
+
}
};
diff --git a/widgets.js b/widgets.js
index bb250a8..b52782b 100644
--- a/widgets.js
+++ b/widgets.js
@@ -7,7 +7,7 @@
written by Jens Mönig
jens@moenig.org
- Copyright (C) 2014 by Jens Mönig
+ Copyright (C) 2015 by Jens Mönig
This file is part of Snap!.
@@ -74,7 +74,7 @@ HTMLCanvasElement, fontHeight, SymbolMorph, localize, SpeechBubbleMorph,
ArrowMorph, MenuMorph, isString, isNil, SliderMorph, MorphicPreferences,
ScrollFrameMorph*/
-modules.widgets = '2014-February-13';
+modules.widgets = '2015-March-21';
var PushButtonMorph;
var ToggleButtonMorph;
@@ -493,7 +493,7 @@ function ToggleButtonMorph(
labelString,
query, // predicate/selector
environment,
- hint,
+ hint, // optional, String or Function
template, // optional, for cached background images
minWidth, // <num> optional, if specified label will left-align
hasPreview, // <bool> show press color on left edge (e.g. category)
@@ -560,12 +560,13 @@ ToggleButtonMorph.prototype.init = function (
// ToggleButtonMorph events
ToggleButtonMorph.prototype.mouseEnter = function () {
+ var contents = this.hint instanceof Function ? this.hint() : this.hint;
if (!this.state) {
this.image = this.highlightImage;
this.changed();
}
- if (this.hint) {
- this.bubbleHelp(this.hint);
+ if (contents) {
+ this.bubbleHelp(contents);
}
};