diff options
| author | Gubolin <gubolin@fantasymail.de> | 2015-03-24 18:29:54 +0100 |
|---|---|---|
| committer | Gubolin <gubolin@fantasymail.de> | 2015-03-24 18:29:54 +0100 |
| commit | bdd780e0030507626c947f85880a9eaa36e24194 (patch) | |
| tree | 070a36fee08b43b5e231b2a1e8e5fe28314a7cb8 | |
| parent | c8de6a00e38e52bfc4b9b3a095cbcad1861687de (diff) | |
| parent | 457224dd620eea4f20f24ebc62391363333cd00f (diff) | |
| download | snap-bdd780e0030507626c947f85880a9eaa36e24194.tar.gz snap-bdd780e0030507626c947f85880a9eaa36e24194.zip | |
merge branch OOP
| -rw-r--r-- | blocks.js | 39 | ||||
| -rw-r--r-- | gui.js | 18 | ||||
| -rwxr-xr-x | history.txt | 12 | ||||
| -rw-r--r-- | morphic.js | 39 | ||||
| -rw-r--r-- | objects.js | 242 | ||||
| -rwxr-xr-x | snap.html | 2 | ||||
| -rw-r--r-- | store.js | 49 | ||||
| -rw-r--r-- | threads.js | 78 | ||||
| -rw-r--r-- | widgets.js | 11 |
9 files changed, 405 insertions, 85 deletions
@@ -155,7 +155,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2015-March-09'; +modules.blocks = '2015-March-23'; var SyntaxElementMorph; @@ -1212,6 +1212,15 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { ); part.isStatic = true; break; + case '%shd': + part = new InputSlotMorph( + null, + false, + 'shadowedVariablesMenu', + true + ); + part.isStatic = true; + break; case '%lst': part = new InputSlotMorph( null, @@ -1910,7 +1919,8 @@ SyntaxElementMorph.prototype.endLayout = function () { %att - chameleon colored rectangular drop-down for attributes %fun - chameleon colored rectangular drop-down for math functions %typ - chameleon colored rectangular drop-down for data types - %var - chameleon colored rectangular drop-down for variable names + %var - chameleon colored rectangular drop-down for variable names + %shd - Chameleon colored rectuangular drop-down for shadowed var names %lst - chameleon colored rectangular drop-down for list names %b - chameleon colored hexagonal slot (for predicates) %l - list icon @@ -3037,6 +3047,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]; @@ -3117,6 +3133,10 @@ BlockMorph.prototype.mouseClickLeft = function () { } }; +BlockMorph.prototype.reactToTemplateCopy = function () { + this.forceNormalColoring(); +}; + // BlockMorph thumbnail BlockMorph.prototype.thumbnail = function (scale, clipWidth, noShadow) { @@ -6913,6 +6933,21 @@ InputSlotMorph.prototype.getVarNamesDict = function () { return {}; }; +InputSlotMorph.prototype.shadowedVariablesMenu = function () { + var block = this.parentThatIsA(BlockMorph), + rcvr, + dict = {}; + + if (!block) {return dict; } + rcvr = block.receiver(); + if (rcvr) { + rcvr.inheritedVariableNames(true).forEach(function (name) { + dict[name] = name; + }); + } + return dict; +}; + InputSlotMorph.prototype.setChoices = function (dict, readonly) { // externally specify choices and read-only status, // used for custom blocks @@ -69,7 +69,7 @@ SpeechBubbleMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2015-March-15'; +modules.gui = '2015-March-21'; // Declarations @@ -2711,7 +2711,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' @@ -5650,7 +5650,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 = [ @@ -5680,6 +5680,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; @@ -5695,7 +5700,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 ); @@ -5866,6 +5871,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, @@ -5900,6 +5906,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..fda02a3 100755 --- a/history.txt +++ b/history.txt @@ -2476,3 +2476,15 @@ ______ ------ * Store: fixed #743 * GUI, html: switch from beta to release candidate + +150321 +------ +* OOP: Prototypal inheritance of sprite-local variables + +150323 +------ +* OOP: Objects, Blocks, Threads - tweaks + +150323 +------ +* OOP: Morphic, Objects, Store - tweak inherited variable watcher slider deserialization @@ -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-24'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug @@ -5926,7 +5933,7 @@ SliderMorph.prototype.userSetStart = function (num) { this.start = Math.max(num, this.stop); }; -SliderMorph.prototype.setStart = function (num) { +SliderMorph.prototype.setStart = function (num, noUpdate) { // for context menu demo purposes var newStart; if (typeof num === 'number') { @@ -5944,12 +5951,12 @@ SliderMorph.prototype.setStart = function (num) { } } this.value = Math.max(this.value, this.start); - this.updateTarget(); + if (!noUpdate) {this.updateTarget(); } this.drawNew(); this.changed(); }; -SliderMorph.prototype.setStop = function (num) { +SliderMorph.prototype.setStop = function (num, noUpdate) { // for context menu demo purposes var newStop; if (typeof num === 'number') { @@ -5961,12 +5968,12 @@ SliderMorph.prototype.setStop = function (num) { } } this.value = Math.min(this.value, this.stop); - this.updateTarget(); + if (!noUpdate) {this.updateTarget(); } this.drawNew(); this.changed(); }; -SliderMorph.prototype.setSize = function (num) { +SliderMorph.prototype.setSize = function (num, noUpdate) { // for context menu demo purposes var newSize; if (typeof num === 'number') { @@ -5984,7 +5991,7 @@ SliderMorph.prototype.setSize = function (num) { } } this.value = Math.min(this.value, this.stop - this.size); - this.updateTarget(); + if (!noUpdate) {this.updateTarget(); } this.drawNew(); this.changed(); }; @@ -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(); } @@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.objects = '2015-February-28'; +modules.objects = '2015-March-24'; var SpriteMorph; var StageMorph; @@ -1192,6 +1192,13 @@ SpriteMorph.prototype.initBlocks = function () { spec: 'script variables %scriptVars' }, + // inheritance - experimental + doDeleteAttr: { + type: 'command', + category: 'variables', + spec: 'delete %shd' + }, + // Lists reportNewList: { type: 'reporter', @@ -1462,11 +1469,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(); @@ -1759,7 +1768,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]) { @@ -1774,6 +1784,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; } @@ -1822,15 +1835,18 @@ SpriteMorph.prototype.blockTemplates = function (category) { } function addVar(pair) { + var ide; if (pair) { - if (myself.variables.silentFind(pair[0])) { + if (myself.isVariableNameInUse(pair[0], pair[1])) { 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(); + if (!myself.showingVariableWatcher(pair[0])) { + myself.toggleVariableWatcher(pair[0], pair[1]); + } + ide.flushBlocksCache('variables'); // b/c of inheritance + ide.refreshPalette(); } } } @@ -2168,7 +2184,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 () { @@ -2177,7 +2193,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()); @@ -2207,6 +2223,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')); @@ -2638,7 +2661,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'); } @@ -2650,7 +2673,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 @@ -3853,6 +3879,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; @@ -3862,7 +3889,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; } ); @@ -3870,6 +3897,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) { @@ -3889,12 +3917,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)); @@ -4320,6 +4348,129 @@ 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, isGlobal) { + if (isGlobal) { + return contains(this.variables.allNames(), 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); + if (ide) { + 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 () { + var locals = this.variables.names(), + inherited = this.inheritedVariableNames(); + return locals.concat( + this.globalVariables().names().filter( + function (each) { + return !contains(locals, each) && !contains(inherited, each); + } + ) + ); +}; + // SpriteMorph highlighting SpriteMorph.prototype.addHighlight = function (oldHighlight) { @@ -5282,7 +5433,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]); @@ -5608,9 +5759,7 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push(block('doShowVar')); blocks.push(block('doHideVar')); blocks.push(block('doDeclareVariables')); - blocks.push('='); - blocks.push(block('reportNewList')); blocks.push('-'); blocks.push(block('reportCONS')); @@ -6033,6 +6182,18 @@ 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; + +StageMorph.prototype.inheritedVariableNames = function () { + return []; +}; + // SpriteBubbleMorph //////////////////////////////////////////////////////// /* @@ -7257,32 +7418,51 @@ WatcherMorph.prototype.isGlobal = function (selector) { // WatcherMorph slider accessing: -WatcherMorph.prototype.setSliderMin = function (num) { +WatcherMorph.prototype.setSliderMin = function (num, noUpdate) { if (this.target instanceof VariableFrame) { - this.sliderMorph.setSize(1); - this.sliderMorph.setStart(num); - this.sliderMorph.setSize(this.sliderMorph.rangeSize() / 5); + this.sliderMorph.setSize(1, noUpdate); + this.sliderMorph.setStart(num, noUpdate); + this.sliderMorph.setSize(this.sliderMorph.rangeSize() / 5, noUpdate); } }; -WatcherMorph.prototype.setSliderMax = function (num) { +WatcherMorph.prototype.setSliderMax = function (num, noUpdate) { if (this.target instanceof VariableFrame) { - this.sliderMorph.setSize(1); - this.sliderMorph.setStop(num); - this.sliderMorph.setSize(this.sliderMorph.rangeSize() / 5); + this.sliderMorph.setSize(1, noUpdate); + this.sliderMorph.setStop(num, noUpdate); + this.sliderMorph.setSize(this.sliderMorph.rangeSize() / 5, noUpdate); } }; // 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](); } @@ -7367,7 +7547,11 @@ WatcherMorph.prototype.fixLayout = function () { this.sliderMorph.button.pressColor.b += 100; this.sliderMorph.setHeight(fontSize); this.sliderMorph.action = function (num) { - myself.target.vars[myself.getter].value = Math.round(num); + myself.target.setVar( + myself.getter, + Math.round(num), + myself.target.owner + ); }; this.add(this.sliderMorph); } @@ -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> @@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/ // Global stuff //////////////////////////////////////////////////////// -modules.store = '2015-March-15'; +modules.store = '2015-March-24'; // 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; @@ -531,8 +540,8 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) { } watcher.setStyle(model.attributes.style || 'normal'); if (watcher.style === 'slider') { - watcher.setSliderMin(model.attributes.min || '1'); - watcher.setSliderMax(model.attributes.max || '100'); + watcher.setSliderMin(model.attributes.min || '1', true); + watcher.setSliderMax(model.attributes.max || '100', true); } watcher.setPosition( project.stage.topLeft().add(new Point( @@ -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="' + @@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/ // Global stuff //////////////////////////////////////////////////////// -modules.threads = '2015-February-28'; +modules.threads = '2015-March-23'; var ThreadManager; var Process; @@ -1184,7 +1184,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) { @@ -1196,7 +1196,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 () { @@ -1241,7 +1241,7 @@ Process.prototype.doShowVar = function (varName) { } // if no watcher exists, create a new one isGlobal = contains( - this.homeContext.receiver.variables.parentFrame.names(), + this.homeContext.receiver.globalVariables().names(), varName ); if (isGlobal || target.owner) { @@ -1370,6 +1370,23 @@ Process.prototype.reportPeerId = function () { return stage.peerId; }; +// Process sprite inheritance primitives + +Process.prototype.doDeleteAttr = function (attrName) { + // currently only variables are deletable + var name = attrName, + rcvr = this.blockReceiver(); + + if (name instanceof Context) { + if (name.expression.selector === 'reportGetVar') { + name = name.expression.blockSpec; + } + } + if (contains(rcvr.inheritedVariableNames(true), name)) { + rcvr.deleteVariable(name); + } +}; + // Process lists primitives Process.prototype.reportNewList = function (elements) { @@ -3624,35 +3641,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; } + } }; @@ -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); } }; |
