inheritance of sprite attributes (x, y, direction, size)

upd4.1
Jens Mönig 2017-05-05 11:17:15 +02:00
rodzic e206f145a7
commit 145c764632
5 zmienionych plików z 185 dodań i 116 usunięć

Wyświetl plik

@ -150,7 +150,7 @@ CustomCommandBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.blocks = '2017-April-10';
modules.blocks = '2017-May-05';
var SyntaxElementMorph;
var BlockMorph;
@ -2363,6 +2363,8 @@ BlockMorph.prototype.userMenu = function () {
vNames = proc && proc.context && proc.context.outerContext ?
proc.context.outerContext.variables.names() : [],
alternatives,
field,
rcvr,
top;
function addOption(label, toggle, test, onHint, offHint) {
@ -2456,6 +2458,36 @@ BlockMorph.prototype.userMenu = function () {
'hidePrimitive'
);
}
// allow toggling inheritable attributes
if (StageMorph.prototype.enableInheritance) {
rcvr = this.receiver();
field = {
xPosition: 'x',
yPosition: 'y',
direction: 'dir',
getScale: 'size'
}[this.selector];
if (field && rcvr && rcvr.exemplar) {
menu.addLine();
if (rcvr.inheritsAttribute(field)) {
menu.addItem(
'disinherit',
function () {
rcvr.shadowAttribute(field);
}
);
} else {
menu.addItem(
localize('inherit from') + ' ' + rcvr.exemplar.name,
function () {
rcvr.inheritAttribute(field);
}
);
}
}
}
if (StageMorph.prototype.enableCodeMapping) {
menu.addLine();
menu.addItem(

Wyświetl plik

@ -3409,9 +3409,18 @@ Fixes:
== v4.1 - development - ===
170411
------
* Objects: export text from variable watchers to new browser tab by default
170505
------
* attribute inheritance support for x, y, dir and size
Features:
* polymorphic sprite-local custom blocks
* inheritance of sprite-local custom blocks
* inheritance of sprite attributes (x, y, direction, size)
* localization support when typing expressions
* support for user-forced line-breaks in custom block labels
* ternary Boolean slot setting: support to limit Boolean input slots to “true/false” outside of rings and in palette

Wyświetl plik

@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph,
BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize,
TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph*/
modules.objects = '2017-April-22';
modules.objects = '2017-May-05';
var SpriteMorph;
var StageMorph;
@ -114,10 +114,12 @@ SpriteMorph.uber = PenMorph.prototype;
// SpriteMorph settings
SpriteMorph.prototype.attributes = // +++
SpriteMorph.prototype.attributes =
[
'x',
'y',
'dir',
'size'
];
SpriteMorph.prototype.categories =
@ -1384,6 +1386,7 @@ SpriteMorph.prototype.init = function (globals) {
// sprite inheritance
this.exemplar = null;
this.cachedSpecimens = null; // temporary for morphic animations
this.inheritedAttributes = []; // 'x', 'y', 'dir', 'size' etc...
SpriteMorph.uber.init.call(this);
@ -1697,12 +1700,13 @@ SpriteMorph.prototype.blockTemplates = function (category) {
cat = category || 'motion', txt,
inheritedVars = this.inheritedVariableNames();
function block(selector) {
function block(selector, isGhosted) {
if (StageMorph.prototype.hiddenPrimitives[selector]) {
return null;
}
var newBlock = SpriteMorph.prototype.blockForSelector(selector, true);
newBlock.isTemplate = true;
if (isGhosted) {newBlock.ghost(); }
return newBlock;
}
@ -1798,11 +1802,11 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push(block('bounceOffEdge'));
blocks.push('-');
blocks.push(watcherToggle('xPosition'));
blocks.push(block('xPosition'));
blocks.push(block('xPosition', this.inheritsAttribute('x')));
blocks.push(watcherToggle('yPosition'));
blocks.push(block('yPosition'));
blocks.push(block('yPosition', this.inheritsAttribute('y')));
blocks.push(watcherToggle('direction'));
blocks.push(block('direction'));
blocks.push(block('direction', this.inheritsAttribute('dir')));
} else if (cat === 'looks') {
@ -1823,7 +1827,7 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push(block('changeScale'));
blocks.push(block('setScale'));
blocks.push(watcherToggle('getScale'));
blocks.push(block('getScale'));
blocks.push(block('getScale', this.inheritsAttribute('size')));
blocks.push('-');
blocks.push(block('show'));
blocks.push(block('hide'));
@ -3346,10 +3350,13 @@ SpriteMorph.prototype.changeSize = function (delta) {
SpriteMorph.prototype.getScale = function () {
// answer my scale in percent
if (this.inheritsAttribute('size')) {
return this.exemplar.getScale();
}
return this.scale * 100;
};
SpriteMorph.prototype.setScale = function (percentage) {
SpriteMorph.prototype.setScale = function (percentage, noShadow) {
// set my (absolute) scale in percent
var x = this.xPosition(),
y = this.yPosition(),
@ -3385,6 +3392,16 @@ SpriteMorph.prototype.setScale = function (percentage) {
y + (yDist * growth)
);
});
// propagate to children that inherit my scale
if (!noShadow) {
this.shadowAttribute('size');
}
this.specimens().forEach(function (instance) {
if (instance.inheritsAttribute('size')) {
instance.setScale(percentage, true);
}
});
};
SpriteMorph.prototype.changeScale = function (delta) {
@ -3856,6 +3873,9 @@ SpriteMorph.prototype.positionTalkBubble = function () {
SpriteMorph.prototype.prepareToBeGrabbed = function (hand) {
this.removeShadow();
this.recordLayers();
this.shadowAttribute('x');
this.shadowAttribute('y');
this.cachedSpecimens = this.specimens();
if (!this.bounds.containsPoint(hand.position()) &&
this.isCorrectingOutsideDrag()) {
this.setCenter(hand.position());
@ -3871,10 +3891,17 @@ SpriteMorph.prototype.isCorrectingOutsideDrag = function () {
};
SpriteMorph.prototype.justDropped = function () {
var stage = this.parentThatIsA(StageMorph);
var stage = this.parentThatIsA(StageMorph),
myself = this;
if (stage) {
stage.enableCustomHatBlocks = true;
}
this.cachedSpecimens = null;
if (this.exemplar) {
this.inheritedAttributes.forEach(function (att) {
myself.refreshInheritedAttribute(att);
});
}
this.restoreLayers();
this.positionTalkBubble();
this.receiveUserInteraction('dropped');
@ -3986,20 +4013,23 @@ SpriteMorph.prototype.moveBy = function (delta, justMe) {
this.parts.forEach(function (part) {
part.moveBy(delta);
});
this.specimens().forEach(function (instance) {
var inheritsX = instance.inheritsAttribute('x'),
inheritsY = instance.inheritsAttribute('y');
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));
}
});
}
};
/* +++
SpriteMorph.prototype.moveBy = function (delta, justMe) {
// override the inherited default to make sure my parts follow
// unless it's justMe (a correction)
var start = this.isDown && !justMe && this.parent ?
this.rotationCenter() : null;
SpriteMorph.uber.moveBy.call(this, delta);
if (start) {
this.drawLine(start, this.rotationCenter());
}
if (!justMe) {
SpriteMorph.prototype.silentMoveBy = function (delta, justMe) {
SpriteMorph.uber.silentMoveBy.call(this, delta);
if (!justMe && this.parent instanceof HandMorph) {
this.parts.forEach(function (part) {
part.moveBy(delta);
});
@ -4009,23 +4039,13 @@ SpriteMorph.prototype.moveBy = function (delta, justMe) {
if (inheritsX && inheritsY) {
instance.moveBy(delta);
} else if (inheritsX) {
instance.moveBy(delta.x, 0);
instance.moveBy(new Point(delta.x, 0));
} else if (inheritsY) {
instance.moveBy(0, delta.y);
instance.moveBy(new Point(0, delta.y));
}
});
}
};
*/
SpriteMorph.prototype.silentMoveBy = function (delta, justMe) {
SpriteMorph.uber.silentMoveBy.call(this, delta);
if (!justMe && this.parent instanceof HandMorph) {
this.parts.forEach(function (part) {
part.moveBy(delta);
});
}
};
SpriteMorph.prototype.rootForGrab = function () {
if (this.anchor) {
@ -4056,6 +4076,35 @@ 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
this.cachedSpecimens = situation.origin.children.filter(function (m) {
return m instanceof SpriteMorph && (m.exemplar === myself);
});
SpriteMorph.uber.slideBackTo.call(
this,
situation,
msecs,
function () {
// make sure to flush cached specimens
myself.cachedSpecimens = null;
if (onBeforeDrop) {onBeforeDrop(); }
},
onBeforeDrop,
onComplete
);
};
// SpriteMorph motion primitives
SpriteMorph.prototype.setPosition = function (aPoint, justMe) {
@ -4079,11 +4128,15 @@ SpriteMorph.prototype.forward = function (steps) {
(this.heading - 180)
);
}
this.shadowAttribute('x');
this.shadowAttribute('y');
this.setPosition(dest);
this.positionTalkBubble();
};
SpriteMorph.prototype.setHeading = function (degrees) {
SpriteMorph.prototype.setHeading = function (degrees, noShadow) {
var x = this.xPosition(),
y = this.yPosition(),
dir = (+degrees || 0),
@ -4108,6 +4161,16 @@ SpriteMorph.prototype.setHeading = function (degrees) {
}
part.gotoXY(trg.x, trg.y);
});
// propagate to children that inherit my direction
if (!noShadow) {
this.shadowAttribute('dir');
}
this.specimens().forEach(function (instance) {
if (instance.inheritsAttribute('dir')) {
instance.setHeading(degrees, true);
}
});
};
SpriteMorph.prototype.faceToXY = function (x, y) {
@ -4129,25 +4192,12 @@ SpriteMorph.prototype.turnLeft = function (degrees) {
this.setHeading(this.heading - (+degrees || 0));
};
SpriteMorph.prototype.xPosition = function () {
var stage = this.parentThatIsA(StageMorph);
if (!stage && this.parent.grabOrigin) { // I'm currently being dragged
stage = this.parent.grabOrigin.origin;
}
if (stage) {
return (this.rotationCenter().x - stage.center().x) / stage.scale;
}
return this.rotationCenter().x;
};
/* +++
SpriteMorph.prototype.xPosition = function () {
if (this.inheritsAttribute('x')) {
return this.exemplar.xPosition();
}
var stage;
var stage = this.parentThatIsA(StageMorph);
if (!stage && this.parent.grabOrigin) { // I'm currently being dragged
stage = this.parent.grabOrigin.origin;
@ -4157,21 +4207,7 @@ SpriteMorph.prototype.xPosition = function () {
}
return this.rotationCenter().x;
};
*/
SpriteMorph.prototype.yPosition = function () {
var stage = this.parentThatIsA(StageMorph);
if (!stage && this.parent.grabOrigin) { // I'm currently being dragged
stage = this.parent.grabOrigin.origin;
}
if (stage) {
return (stage.center().y - this.rotationCenter().y) / stage.scale;
}
return this.rotationCenter().y;
};
/* +++
SpriteMorph.prototype.yPosition = function () {
if (this.inheritsAttribute('y')) {
return this.exemplar.yPosition();
@ -4187,9 +4223,11 @@ SpriteMorph.prototype.yPosition = function () {
}
return this.rotationCenter().y;
};
*/
SpriteMorph.prototype.direction = function () {
if (this.inheritsAttribute('dir')) {
return this.exemplar.direction();
}
return this.heading;
};
@ -4197,25 +4235,6 @@ SpriteMorph.prototype.penSize = function () {
return this.size;
};
SpriteMorph.prototype.gotoXY = function (x, y, justMe) {
var stage = this.parentThatIsA(StageMorph),
newX,
newY,
dest;
if (!stage) {return; }
newX = stage.center().x + (+x || 0) * stage.scale;
newY = stage.center().y - (+y || 0) * stage.scale;
if (this.costume) {
dest = new Point(newX, newY).subtract(this.rotationOffset);
} else {
dest = new Point(newX, newY).subtract(this.extent().divideBy(2));
}
this.setPosition(dest, justMe);
this.positionTalkBubble();
};
/* +++
SpriteMorph.prototype.gotoXY = function (x, y, justMe, noShadow) {
var stage = this.parentThatIsA(StageMorph),
newX,
@ -4237,17 +4256,7 @@ SpriteMorph.prototype.gotoXY = function (x, y, justMe, noShadow) {
this.setPosition(dest, justMe);
this.positionTalkBubble();
};
*/
SpriteMorph.prototype.silentGotoXY = function (x, y, justMe) {
// move without drawing
var penState = this.isDown;
this.isDown = false;
this.gotoXY(x, y, justMe);
this.isDown = penState;
};
/* +++
SpriteMorph.prototype.silentGotoXY = function (x, y, justMe) {
// move without drawing
// don't shadow coordinate attributes
@ -4256,33 +4265,21 @@ SpriteMorph.prototype.silentGotoXY = function (x, y, justMe) {
this.gotoXY(x, y, justMe, true); // don't shadow coordinates
this.isDown = penState;
};
*/
SpriteMorph.prototype.setXPosition = function (num) {
this.gotoXY(+num || 0, this.yPosition());
};
/* +++
SpriteMorph.prototype.setXPosition = function (num) {
this.shadowAttribute('x');
this.gotoXY(+num || 0, this.yPosition(), false, true);
};
*/
SpriteMorph.prototype.changeXPosition = function (delta) {
this.setXPosition(this.xPosition() + (+delta || 0));
};
SpriteMorph.prototype.setYPosition = function (num) {
this.gotoXY(this.xPosition(), +num || 0);
};
/* +++
SpriteMorph.prototype.setYPosition = function (num) {
this.shadowAttribute('y');
this.gotoXY(this.xPosition(), +num || 0, false, true);
};
*/
SpriteMorph.prototype.changeYPosition = function (delta) {
this.setYPosition(this.yPosition() + (+delta || 0));
};
@ -4329,6 +4326,10 @@ SpriteMorph.prototype.bounceOffEdge = function () {
dirY = Math.abs(dirY);
}
this.shadowAttribute('x');
this.shadowAttribute('y');
this.shadowAttribute('dir');
this.setHeading(degrees(Math.atan2(-dirY, dirX)) + 90);
this.setPosition(this.position().add(
fb.amountToTranslateWithin(stage.bounds)
@ -5120,7 +5121,7 @@ SpriteMorph.prototype.allExemplars = function () {
SpriteMorph.prototype.specimens = function () {
// without myself
var myself = this;
return this.siblings().filter(function (m) {
return this.cachedSpecimens || this.siblings().filter(function (m) {
return m instanceof SpriteMorph && (m.exemplar === myself);
});
};
@ -5140,23 +5141,32 @@ SpriteMorph.prototype.inheritsAttribute = function (aName) {
};
SpriteMorph.prototype.shadowAttribute = function (aName) {
if (!this.exemplar) {
var ide;
if (!this.inheritsAttribute(aName)) {
return;
}
console.log('shadowing', aName); // +++
ide = this.parentThatIsA(IDE_Morph);
this.inheritedAttributes = this.inheritedAttributes.filter(
function (each) {return each === aName; }
function (each) {return each !== aName; }
);
if (ide) {
ide.flushBlocksCache(); // optimization: specify category if known
ide.refreshPalette();
}
};
SpriteMorph.prototype.inheritAttribute = function (aName) {
var ide = this.parentThatIsA(IDE_Morph);
if (!this.exemplar || !contains(this.attributes, aName)) {
return;
}
if (!this.inheritsAttribute(aName)) {
console.log('inheriting', aName); // +++
this.inheritedAttributes.push(aName);
this.refreshInheritedAttribute(aName);
if (ide) {
ide.flushBlocksCache(); // optimization: specify category if known
ide.refreshPalette();
}
}
};
@ -5164,7 +5174,13 @@ SpriteMorph.prototype.refreshInheritedAttribute = function (aName) {
switch (aName) {
case 'x':
case 'y':
this.gotoXY(this.xPosition(), this.yPosition());
this.gotoXY(this.xPosition(), this.yPosition(), false, true);
break;
case 'dir':
this.setHeading(this.direction(), true);
break;
case 'size':
this.setScale(this.getScale(), true);
break;
default:
nop();

Wyświetl plik

@ -61,7 +61,7 @@ normalizeCanvas, contains*/
// Global stuff ////////////////////////////////////////////////////////
modules.store = '2017-April-10';
modules.store = '2017-May-05';
// XML_Serializer ///////////////////////////////////////////////////////
@ -478,6 +478,7 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) {
if (exemplar) {
sprite.setExemplar(exemplar);
}
sprite.inheritedAttributes = sprite.inheritanceInfo.delegated || [];
}
if (sprite.nestingInfo) { // only sprites may have nesting info
anchor = myself.project.sprites[sprite.nestingInfo.anchor];
@ -742,9 +743,15 @@ SnapSerializer.prototype.loadObject = function (object, model) {
SnapSerializer.prototype.loadInheritanceInfo = function (object, model) {
// private
var info = model.childNamed('inherit');
var info = model.childNamed('inherit'),
delegated;
if (info) {
object.inheritanceInfo = info.attributes;
delegated = info.childNamed('list');
if (delegated) {
object.inheritanceInfo.delegated =
this.loadValue(delegated).asArray();
}
}
};
@ -1633,8 +1640,12 @@ SpriteMorph.prototype.toXML = function (serializer) {
// inheritance info
this.exemplar
? '<inherit exemplar="' +
this.exemplar.name
+ '"/>'
this.exemplar.name +
'">' +
(this.inheritedAttributes.length ?
serializer.store(new List(this.inheritedAttributes))
: '') +
'</inherit>'
: '',
// nesting info

Wyświetl plik

@ -1551,7 +1551,8 @@ Process.prototype.doDeleteAttr = function (attrName) {
} else { // attribute
name = {
xPosition: 'x',
yPosition: 'y'
yPosition: 'y',
direction: 'dir'
}[name.expression.selector];
if (!isNil(name)) {
rcvr.inheritAttribute(name);