unify Scratch-style clones and Snap-specimens

implement clones as specimens
upd4.1
Jens Mönig 2017-07-04 13:51:22 +02:00
rodzic d89f29957e
commit de10e558e6
6 zmienionych plików z 156 dodań i 123 usunięć

Wyświetl plik

@ -150,7 +150,7 @@ CustomCommandBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.blocks = '2017-June-26';
modules.blocks = '2017-July-04';
var SyntaxElementMorph;
var BlockMorph;
@ -8117,7 +8117,7 @@ InputSlotMorph.prototype.collidablesMenu = function () {
allNames = [];
stage.children.forEach(function (morph) {
if (morph instanceof SpriteMorph && !morph.isClone) {
if (morph instanceof SpriteMorph && !morph.isTemporary) {
if (morph.name !== rcvr.name) {
allNames = allNames.concat(morph.name);
}
@ -8166,7 +8166,7 @@ InputSlotMorph.prototype.clonablesMenu = function () {
dict.myself = ['myself'];
}
stage.children.forEach(function (morph) {
if (morph instanceof SpriteMorph && !morph.isClone) {
if (morph instanceof SpriteMorph && !morph.isTemporary) {
allNames = allNames.concat(morph.name);
}
});

4
gui.js
Wyświetl plik

@ -74,7 +74,7 @@ isRetinaSupported, SliderMorph, Animation*/
// Global stuff ////////////////////////////////////////////////////////
modules.gui = '2017-June-26';
modules.gui = '2017-July-04';
// Declarations
@ -1514,7 +1514,7 @@ IDE_Morph.prototype.createCorral = function () {
frame.alpha = 0;
this.sprites.asArray().forEach(function (morph) {
if (!morph.isClone) {
if (!morph.isTemporary) {
template = new SpriteIconMorph(morph, template);
frame.contents.add(template);
}

Wyświetl plik

@ -3492,6 +3492,15 @@ Fixes:
* Objects: reflect attribute inheritance status by ghosting / un-ghosting stage monitors
* Objects: migrate experimental “jukebox” reporters to the new “my sounds” reporter
170703
------
* Objects, Threads, GUI, Blocks, YPR: renamed Sprite::isClone to Sprite:isTemporary
170704
------
* Morphic: Simplify contains()
* Unify Scratch-style clones and Snap-specimens, part 1: implement clones as specimens
Features:
* polymorphic sprite-local custom blocks

Wyświetl plik

@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph,
BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize,
TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph*/
modules.objects = '2017-June-30';
modules.objects = '2017-July-04';
var SpriteMorph;
var StageMorph;
@ -1363,7 +1363,7 @@ SpriteMorph.prototype.init = function (globals) {
this.scale = 1;
this.rotationStyle = 1; // 1 = full, 2 = left/right, 0 = off
this.version = Date.now(); // for observer optimization
this.isClone = false; // indicate a "temporary" Scratch-style clone
this.isTemporary = false; // indicate a temporary Scratch-style clone
this.isCorpse = false; // indicate whether a sprite/clone has been deleted
this.cloneOriginName = '';
@ -1399,7 +1399,8 @@ SpriteMorph.prototype.init = function (globals) {
// sprite inheritance
this.exemplar = null;
this.cachedSpecimens = null; // not to be persisted
this.instances = [];
this.cachedPropagation = false; // not to be persisted
this.inheritedAttributes = []; // 'x position', 'direction', 'size' etc...
SpriteMorph.uber.init.call(this);
@ -1420,13 +1421,23 @@ SpriteMorph.prototype.fullCopy = function (forClone) {
arr = [],
cb, effect;
c.instances = [];
c.stopTalking();
c.color = this.color.copy();
c.blocksCache = {};
c.paletteCache = {};
c.variables = this.variables.copy();
c.variables.owner = c;
if (!forClone) {
if (forClone) {
c.exemplar = this;
this.addSpecimen(c);
this.cachedPropagation = false;
['scripts', 'costumes', 'sounds'].forEach(function (att) {
if (!contains(c.inheritedAttributes, att)) {
c.inheritedAttributes.push(att);
}
});
} else {
c.scripts = this.scripts.fullCopy();
c.customBlocks = [];
this.customBlocks.forEach(function (def) {
@ -1473,7 +1484,7 @@ SpriteMorph.prototype.fullCopy = function (forClone) {
SpriteMorph.prototype.appearIn = function (ide) {
// private - used in IDE_Morph.duplicateSprite()
if (!this.isClone) {
if (!this.isTemporary) {
this.name = ide.newSpriteName(this.name);
ide.corral.addSprite(this);
ide.sprites.add(this);
@ -2929,13 +2940,15 @@ SpriteMorph.prototype.wearCostume = function (costume, noShadow) {
this.shadowAttribute('costume #');
}
this.specimens().forEach(function (instance) {
if (instance.inheritsAttribute('costume #')) {
if (idx === null) {
instance.wearCostume(null, true);
} else if (idx === -1) {
instance.wearCostume(costume, true);
} else {
instance.doSwitchToCostume(idx + 1, true);
if (instance.cachedPropagation) {
if (instance.inheritsAttribute('costume #')) {
if (idx === null) {
instance.wearCostume(null, true);
} else if (idx === -1) {
instance.wearCostume(costume, true);
} else {
instance.doSwitchToCostume(idx + 1, true);
}
}
}
});
@ -3059,7 +3072,7 @@ SpriteMorph.prototype.userMenu = function () {
// menu.addItem('help', 'nop');
return menu;
}
if (!this.isClone) {
if (!this.isTemporary) {
menu.addItem("duplicate", 'duplicate');
}
menu.addItem("delete", 'remove');
@ -3071,7 +3084,7 @@ SpriteMorph.prototype.userMenu = function () {
'edit the costume\'s\nrotation center'
);
}
if (!this.isClone) {
if (!this.isTemporary) {
menu.addItem("edit", 'edit');
}
menu.addLine();
@ -3089,7 +3102,7 @@ SpriteMorph.prototype.userMenu = function () {
};
SpriteMorph.prototype.exportSprite = function () {
if (this.isClone) {return; }
if (this.isTemporary) {return; }
var ide = this.parentThatIsA(IDE_Morph);
if (ide) {
ide.exportSprite(this);
@ -3160,9 +3173,9 @@ SpriteMorph.prototype.clonify = function (stage, immediately) {
part.clonify(stage);
});
stage.cloneCount += 1;
this.cloneOriginName = this.isClone ?
this.cloneOriginName = this.isTemporary ?
this.cloneOriginName : this.name;
this.isClone = true;
this.isTemporary = true;
this.name = '';
stage.add(this);
hats = this.allHatBlocksFor('__clone__init__');
@ -3181,7 +3194,7 @@ SpriteMorph.prototype.clonify = function (stage, immediately) {
};
SpriteMorph.prototype.removeClone = function () {
if (this.isClone) {
if (this.isTemporary) {
// this.stopTalking();
this.parent.threads.stopAllForReceiver(this);
this.corpsify();
@ -3418,9 +3431,11 @@ SpriteMorph.prototype.setScale = function (percentage, noShadow) {
if (!noShadow) {
this.shadowAttribute('size');
}
this.specimens().forEach(function (instance) {
if (instance.inheritsAttribute('size')) {
instance.setScale(percentage, true);
this.instances.forEach(function (instance) {
if (instance.cachedPropagation) {
if (instance.inheritsAttribute('size')) {
instance.setScale(percentage, true);
}
}
});
};
@ -3896,7 +3911,7 @@ SpriteMorph.prototype.prepareToBeGrabbed = function (hand) {
this.recordLayers();
this.shadowAttribute('x position');
this.shadowAttribute('y position');
this.specimens(); // make sure specimens are cached
this.instances; // make sure specimens are cached
if (!this.bounds.containsPoint(hand.position()) &&
this.isCorrectingOutsideDrag()) {
this.setCenter(hand.position());
@ -4033,15 +4048,17 @@ SpriteMorph.prototype.moveBy = function (delta, justMe) {
this.parts.forEach(function (part) {
part.moveBy(delta);
});
this.specimens().forEach(function (instance) {
var inheritsX = instance.inheritsAttribute('x position'),
inheritsY = instance.inheritsAttribute('y position');
if (inheritsX && inheritsY) {
instance.moveBy(delta);
} else if (inheritsX) {
instance.moveBy(new Point(delta.x, 0));
} else if (inheritsY) {
instance.moveBy(new Point(0, delta.y));
this.instances.forEach(function (instance) {
if (instance.cachedPropagation) {
var inheritsX = instance.inheritsAttribute('x position'),
inheritsY = instance.inheritsAttribute('y position');
if (inheritsX && inheritsY) {
instance.moveBy(delta);
} else if (inheritsX) {
instance.moveBy(new Point(delta.x, 0));
} else if (inheritsY) {
instance.moveBy(new Point(0, delta.y));
}
}
});
}
@ -4053,15 +4070,17 @@ SpriteMorph.prototype.silentMoveBy = function (delta, justMe) {
this.parts.forEach(function (part) {
part.moveBy(delta);
});
this.specimens().forEach(function (instance) {
var inheritsX = instance.inheritsAttribute('x position'),
inheritsY = instance.inheritsAttribute('y position');
if (inheritsX && inheritsY) {
instance.moveBy(delta);
} else if (inheritsX) {
instance.moveBy(new Point(delta.x, 0));
} else if (inheritsY) {
instance.moveBy(new Point(0, delta.y));
this.instances.forEach(function (instance) {
if (instance.cachedPropagation) {
var inheritsX = instance.inheritsAttribute('x position'),
inheritsY = instance.inheritsAttribute('y position');
if (inheritsX && inheritsY) {
instance.moveBy(delta);
} else if (inheritsX) {
instance.moveBy(new Point(delta.x, 0));
} else if (inheritsY) {
instance.moveBy(new Point(0, delta.y));
}
}
});
}
@ -4096,35 +4115,6 @@ SpriteMorph.prototype.nestingBounds = function () {
return result;
};
SpriteMorph.prototype.slideBackTo = function (
situation,
msecs,
onBeforeDrop,
onComplete
) {
// override the inherited method to make sure my specimens can follow
// by caching them while sliding
var myself = this;
// caching specimens
if (isNil(this.cachedSpecimens)) {
this.cachedSpecimens = situation.origin.children.filter(function (m) {
return m instanceof SpriteMorph && (m.exemplar === myself);
});
}
SpriteMorph.uber.slideBackTo.call(
this,
situation,
msecs,
function () {
if (onBeforeDrop) {onBeforeDrop(); }
},
onBeforeDrop,
onComplete
);
};
// SpriteMorph motion primitives
SpriteMorph.prototype.setPosition = function (aPoint, justMe) {
@ -4186,9 +4176,11 @@ SpriteMorph.prototype.setHeading = function (degrees, noShadow) {
if (!noShadow) {
this.shadowAttribute('direction');
}
this.specimens().forEach(function (instance) {
if (instance.inheritsAttribute('direction')) {
instance.setHeading(degrees, true);
this.instances.forEach(function (instance) {
if (instance.cachedPropagation) {
if (instance.inheritsAttribute('direction')) {
instance.setHeading(degrees, true);
}
}
});
};
@ -4559,7 +4551,7 @@ SpriteMorph.prototype.receiveUserInteraction = function (interaction) {
};
SpriteMorph.prototype.mouseDoubleClick = function () {
if (this.isClone) {return; }
if (this.isTemporary) {return; }
this.edit();
};
@ -4817,7 +4809,7 @@ SpriteMorph.prototype.deleteAllBlockInstances = function (definition) {
});
}
} else {
this.allSpecimens().concat(this).forEach(function (sprite) {
this.allinstances.concat(this).forEach(function (sprite) {
sprite.customBlocks.forEach(function (def) {
def.purgeCorpses();
});
@ -4865,7 +4857,7 @@ SpriteMorph.prototype.allIndependentInvocationsOf = function (aSpec) {
return [];
}
blocks = this.allInvocationsOf(aSpec);
this.specimens().forEach(function (sprite) {
this.instances.forEach(function (sprite) {
sprite.addAllInvocationsOf(aSpec, blocks);
});
return blocks;
@ -4874,7 +4866,7 @@ SpriteMorph.prototype.allIndependentInvocationsOf = function (aSpec) {
SpriteMorph.prototype.allDependentInvocationsOf = function (aSpec) {
var blocks;
blocks = this.allInvocationsOf(aSpec);
this.specimens().forEach(function (sprite) {
this.instances.forEach(function (sprite) {
sprite.addAllInvocationsOf(aSpec, blocks);
});
return blocks;
@ -4920,7 +4912,7 @@ SpriteMorph.prototype.addAllInvocationsOf = function (aSpec, anArray) {
this.allInvocationsOf(aSpec).forEach(function (block) {
anArray.push(block);
});
this.specimens().forEach(function (sprite) {
this.instances.forEach(function (sprite) {
sprite.addAllInvocationsOf(aSpec, anArray);
});
}
@ -5122,40 +5114,39 @@ SpriteMorph.prototype.setExemplar = function (another) {
var ide = this.parentThatIsA(IDE_Morph);
this.emancipate();
this.exemplar = another;
if (isNil(another)) {
this.variables.parentFrame = (this.globalVariables());
} else {
if (another) {
this.variables.parentFrame = (another.variables);
another.addSpecimen(this);
} else {
this.variables.parentFrame = (this.globalVariables());
}
if (ide) {
ide.flushBlocksCache('variables');
ide.refreshPalette();
}
if (another) {
another.cachedSpecimens = null;
another.specimens(); // update the inheritance cache
}
};
SpriteMorph.prototype.prune = function () {
// sever ties with all my specimen, if any,
this.specimens().forEach(function (child) {
this.instances.forEach(function (child) {
child.shadowAllAttributes();
child.shadowAllMethods();
child.shadowAllVars();
child.exemplar = null;
});
this.cachedSpecimens = null;
this.instances = [];
};
SpriteMorph.prototype.emancipate = function () {
// sever all relations with my exemplar, if any,
// and make sure I am the root of my specimen
if (this.exemplar) {
this.shadowAllAttributes();
this.shadowAllMethods();
this.shadowAllVars();
this.exemplar.cachedSpecimens = null;
if (!this.isTemporary) {
this.shadowAllAttributes();
this.shadowAllMethods();
this.shadowAllVars();
}
this.exemplar.removeSpecimen(this); // optimization
this.exemplar = null;
}
};
@ -5173,21 +5164,29 @@ SpriteMorph.prototype.allExemplars = function () {
SpriteMorph.prototype.specimens = function () {
// without myself
var myself = this;
if (isNil(this.cachedSpecimens)) {
this.cachedSpecimens = this.siblings().filter(function (m) {
return m instanceof SpriteMorph && (m.exemplar === myself);
});
}
return this.cachedSpecimens;
return this.instances;
};
SpriteMorph.prototype.allSpecimens = function () {
// without myself
var myself = this;
return this.siblings().filter(function (m) {
return m instanceof SpriteMorph && contains(m.allExemplars(), myself);
var all = this.instances.slice();
this.instances.forEach(function (child) {
all.push.appl(all, child.allSpecimens());
});
return all;
};
SpriteMorph.prototype.addSpecimen = function (another) {
// private - use setExemplar() to establish an inheritance relationship
this.instances.push(another);
};
SpriteMorph.prototype.removeSpecimen = function(another) {
// private - use setExemplar(null) to cancel an inheritance relationship
var idx = this.instances.indexOf(another);
if (idx !== -1) {
this.instances.splice(idx, 1);
}
};
// SpriteMorph inheritance - attributes
@ -5196,6 +5195,19 @@ SpriteMorph.prototype.inheritsAttribute = function (aName) {
return !isNil(this.exemplar) && contains(this.inheritedAttributes, aName);
};
SpriteMorph.prototype.updatePropagationCache = function () {
// private - indicate whether one of my inherited attributes is technically
// propagated down from my exemplar, instead of truly shared.
// (only) needed for internal optimization caching
var myself = this;
this.cachedPropagation = !isNil(this.exemplar) && detect(
['x position', 'y position', 'direction', 'size', 'costume #'],
function (att) {
return contains(myself.inheritedAttributes, att);
}
);
};
SpriteMorph.prototype.shadowedAttributes = function () {
// answer an array of attribute names that can be deleted/shared
var inherited = this.inheritedAttributes;
@ -5232,7 +5244,7 @@ SpriteMorph.prototype.shadowAttribute = function (aName) {
}
});
this.costumes = wardrobe;
this.specimens().forEach(function (obj) {
this.instances.forEach(function (obj) {
if (obj.inheritsAttribute('costumes')) {
obj.refreshInheritedAttribute('costumes');
}
@ -5243,7 +5255,7 @@ SpriteMorph.prototype.shadowAttribute = function (aName) {
jukebox.add(sound.copy());
});
this.sounds = jukebox;
this.specimens().forEach(function (obj) {
this.instances.forEach(function (obj) {
if (obj.inheritsAttribute('sounds')) {
obj.refreshInheritedAttribute('sounds');
}
@ -5259,14 +5271,17 @@ SpriteMorph.prototype.shadowAttribute = function (aName) {
this.scripts.setPosition(pos);
ide.spriteEditor.adjustScrollBars();
}
this.specimens().forEach(function (obj) {
this.instances.forEach(function (obj) {
if (obj.inheritsAttribute('scripts')) {
obj.refreshInheritedAttribute('scripts');
}
});
} else if (ide) {
ide.flushBlocksCache(); // optimization: specify category if known
ide.refreshPalette();
} else {
this.updatePropagationCache();
if (ide) {
ide.flushBlocksCache(); // optimization: specify category if known
ide.refreshPalette();
}
}
};
@ -5290,22 +5305,26 @@ SpriteMorph.prototype.refreshInheritedAttribute = function (aName) {
switch (aName) {
case 'x position':
case 'y position':
this.cachedPropagation = true;
this.gotoXY(this.xPosition(), this.yPosition(), false, true);
break;
case 'direction':
this.cachedPropagation = true;
this.setHeading(this.direction(), true);
break;
case 'size':
this.cachedPropagation = true;
this.setScale(this.getScale(), true);
break;
case 'costume #':
this.cachedPropagation = true;
this.doSwitchToCostume(this.getCostumeIdx(), true);
break;
case 'costumes':
idx = this.getCostumeIdx();
this.costumes = this.exemplar.costumes;
this.doSwitchToCostume(idx, true);
this.specimens().forEach(function (sprite) {
this.instances.forEach(function (sprite) {
if (sprite.inheritsAttribute('costumes')) {
sprite.refreshInheritedAttribute('costumes');
}
@ -5313,7 +5332,7 @@ SpriteMorph.prototype.refreshInheritedAttribute = function (aName) {
break;
case 'sounds':
this.sounds = this.exemplar.sounds;
this.specimens().forEach(function (sprite) {
this.instances.forEach(function (sprite) {
if (sprite.inheritsAttribute('sounds')) {
sprite.refreshInheritedAttribute('sounds');
}
@ -5329,7 +5348,7 @@ SpriteMorph.prototype.refreshInheritedAttribute = function (aName) {
ide.fixLayout('selectSprite');
}
}
this.specimens().forEach(function (sprite) {
this.instances.forEach(function (sprite) {
if (sprite.inheritsAttribute('scripts')) {
sprite.refreshInheritedAttribute('scripts');
}
@ -5663,7 +5682,9 @@ SpriteMorph.prototype.restoreLayers = function () {
SpriteMorph.prototype.destroy = function () {
// make sure to sever all inheritance ties to other sprites
this.emancipate();
this.prune();
if (!this.isTemporary) {
this.prune();
}
SpriteMorph.uber.destroy.call(this);
};
@ -6508,7 +6529,9 @@ StageMorph.prototype.fireStopAllEvent = function () {
StageMorph.prototype.removeAllClones = function () {
var myself = this,
clones = this.children.filter(
function (morph) {return morph.isClone; }
function (morph) {
return morph instanceof SpriteMorph && morph.isTemporary;
}
);
clones.forEach(function (clone) {
myself.threads.stopAllForReceiver(clone);

Wyświetl plik

@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy,
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph,
TableFrameMorph, ColorSlotMorph, isSnapObject*/
modules.threads = '2017-June-26';
modules.threads = '2017-July-04';
var ThreadManager;
var Process;
@ -246,7 +246,7 @@ ThreadManager.prototype.stopAllForReceiver = function (rcvr, excpt) {
this.processes.forEach(function (proc) {
if (proc.homeContext.receiver === rcvr && proc !== excpt) {
proc.stop();
if (rcvr.isClone) {
if (rcvr.isTemporary) {
proc.isDead = true;
}
}
@ -2724,7 +2724,7 @@ Process.prototype.getObjectsNamed = function (name, thisObj, stageObj) {
those = [];
function check(obj) {
return obj instanceof SpriteMorph && obj.isClone ?
return obj instanceof SpriteMorph && obj.isTemporary ?
obj.cloneOriginName === name : obj.name === name;
}
@ -3006,13 +3006,14 @@ Process.prototype.reportGet = function (query) {
objName = thisObj.name || thisObj.cloneOriginName;
return new List(
stage.children.filter(function (each) {
return each.isClone &&
return each.isTemporary &&
(each !== thisObj) &&
(each.cloneOriginName === objName);
})
);
case 'other clones':
return thisObj.isClone ? this.reportGet(['clones']) : new List();
return thisObj.isTemporary ?
this.reportGet(['clones']) : new List();
case 'neighbors':
stage = thisObj.parentThatIsA(StageMorph);
neighborhood = thisObj.bounds.expandBy(new Point(

4
ypr.js
Wyświetl plik

@ -8,7 +8,7 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Last changed 2013-04-03 by Jens Moenig (disabled text area overlay)
Last changed 2017-07-04 by Jens Moenig (disabled text area overlay, introduced Sprite::isTemporary)
*/
@ -392,7 +392,7 @@ var sb = (function (sb) {
sb.addFields(-1, 'Slider', 'BorderedMorph', 'slider,value,setValueSelector,sliderShadow,sliderColor,descending,model');
sb.addFields(-2, 'AbstractSound', '', '');
sb.addFields(-3, 'ScriptableScratchMorph', 'Morph', 'objName,vars,blocksBin,customBlocks,isClone,media,costume');
sb.addFields(-3, 'ScriptableScratchMorph', 'Morph', 'objName,vars,blocksBin,customBlocks,isTemporary,media,costume');
sb.addFields(-4, 'ArgMorph', 'BorderedMorph', 'labelMorph');
sb.addFields(-5, 'PasteUpMorph', 'BorderedMorph', '');
sb.addFields(-6, 'ScratchMedia', '', 'mediaName');