diff --git a/HISTORY.md b/HISTORY.md
index 337fda37..4c4fca3c 100755
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2,12 +2,14 @@
## in development:
* **New Features:**
+ * new experimental "paste on" block in the "pen" category, currently hidden in dev mode
* **Notable Changes:**
* **Notable Fixes:**
* **Translation Updates:**
### 2019-08-06
* new dev version
+* objects, threads: new experimental "paste on" block in the "pen" category, hidden in dev mode
## v5.0.8
* **Notable Fix:**
diff --git a/snap.html b/snap.html
index 859d395d..102ae66d 100755
--- a/snap.html
+++ b/snap.html
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/src/objects.js b/src/objects.js
index d80f5f12..cccbfd44 100644
--- a/src/objects.js
+++ b/src/objects.js
@@ -84,7 +84,7 @@ BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, BooleanSlotMorph,
localize, TableMorph, TableFrameMorph, normalizeCanvas, VectorPaintEditorMorph,
HandleMorph, AlignmentMorph, Process, XML_Element, WorldMap*/
-modules.objects = '2019-July-15';
+modules.objects = '2019-August-06';
var SpriteMorph;
var StageMorph;
@@ -678,6 +678,14 @@ SpriteMorph.prototype.initBlocks = function () {
spec: 'pen trails'
},
+ // Pen - experimental primitives for development mode
+ doPasteOn: {
+ dev: true,
+ type: 'command',
+ category: 'pen',
+ spec: 'paste on %spr'
+ },
+
// Control
receiveGo: {
type: 'hat',
@@ -2316,6 +2324,24 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push(block('write'));
blocks.push('-');
blocks.push(block('reportPenTrailsAsCostume'));
+
+ // for debugging: ///////////////
+
+ if (this.world().isDevMode) {
+
+ blocks.push('-');
+ txt = new TextMorph(localize(
+ 'development mode \ndebugging primitives:'
+ ));
+ txt.fontSize = 9;
+ txt.setColor(this.paletteTextColor);
+ blocks.push(txt);
+ blocks.push('-');
+ blocks.push(block('doPasteOn'));
+ }
+
+ /////////////////////////////////
+
blocks.push('=');
blocks.push(this.makeBlockButton(cat));
@@ -4311,6 +4337,114 @@ SpriteMorph.prototype.changeSize = function (delta) {
this.setSize(this.size + (+delta || 0));
};
+// SpriteMorph printing on another sprite:
+
+SpriteMorph.prototype.pasteOn = function (target) {
+ // draw my costume onto a copy of the target's costume scaled and rotated
+ // so it appears as though I'm "stamped" onto it.
+
+ var sourceHeading = (this.rotationStyle === 1) ? this.heading : 90,
+ targetHeading = (target.rotationStyle === 1) ? target.heading : 90,
+ sourceCostume, targetCostume, ctx,
+ relRot, relScale, stageScale,
+ centerDist, centerDelta, centerAngleRadians, center,
+ originDist, originAngleRadians,
+ spriteCenter, thisCenter, relPos, pos;
+
+ // prevent pasting an object onto itself
+ if (this === target) {return; }
+
+ // check if both source and target have costumes,
+ // rasterize copy of target costume if it's an SVG
+ if (this.costume && target.costume) {
+ sourceCostume = this.costume;
+ if (sourceCostume instanceof SVG_Costume) {
+ sourceCostume = sourceCostume.rasterized();
+ }
+ if (target.costume instanceof SVG_Costume) {
+ targetCostume = target.costume.rasterized();
+ } else {
+ targetCostume = target.costume.copy();
+ }
+ } else {
+ return;
+ }
+
+ // do the math:
+ if (target instanceof SpriteMorph) {
+ if (this instanceof SpriteMorph) {
+ // stamp a sprite on a sprite:
+ relRot = sourceHeading - targetHeading;
+ relScale = this.scale / target.scale;
+ stageScale = this.parentThatIsA(StageMorph).scale;
+ centerDist = target.center().distanceTo(this.center());
+ centerDelta = this.center().subtract(target.center());
+ centerAngleRadians = Math.atan2(centerDelta.y, centerDelta.x);
+ center = new Point(
+ sourceCostume.width(),
+ sourceCostume.height()
+ ).multiplyBy(0.5 * this.scale * stageScale);
+ originDist = center.distanceTo(new Point(0, 0));
+ originAngleRadians = Math.atan2(center.y, center.x);
+ spriteCenter = new Point(
+ target.costume.width(),
+ target.costume.height()
+ ).multiplyBy(0.5 * target.scale * stageScale)
+ .rotateBy(radians(relRot));
+ thisCenter = spriteCenter.distanceAngle(
+ centerDist,
+ degrees(centerAngleRadians) - sourceHeading + 180
+ );
+ relPos = thisCenter.distanceAngle(
+ originDist,
+ degrees(originAngleRadians) -90
+ );
+ pos = relPos.divideBy(stageScale)
+ .divideBy(relScale)
+ .divideBy(target.scale);
+ } else { // if the stage is the source
+ // stamp the stage on a sprite:
+ relRot = 90 - targetHeading;
+ relScale = 1 / target.scale;
+ centerDist = target.center().distanceTo(this.position());
+ centerDelta = this.position().subtract(target.center());
+ centerAngleRadians = Math.atan2(centerDelta.y, centerDelta.x);
+ center = new Point(
+ target.costume.width(),
+ target.costume.height()
+ ).multiplyBy(0.5 * target.scale)
+ .rotateBy(radians(90 - targetHeading));
+ pos = center.distanceAngle(
+ centerDist / this.scale,
+ degrees(centerAngleRadians) + 90
+ );
+ }
+ } else { // if the stage is the target
+ // stamp a sprite on the stage:
+ relRot = sourceHeading - 90;
+ relScale = this.scale;
+ center = this.center().subtract(target.position())
+ .divideBy(this.scale * target.scale)
+ .rotateBy(radians(sourceHeading - 90));
+ pos = center.subtract(
+ new Point(
+ sourceCostume.width(),
+ sourceCostume.height()
+ ).multiplyBy(0.5)
+ );
+ }
+
+ // draw my costume onto the target's costume copy:
+ ctx = targetCostume.contents.getContext('2d');
+ ctx.rotate(radians(relRot));
+ ctx.scale(relScale, relScale);
+ ctx.globalCompositeOperation = 'source-atop';
+ ctx.drawImage(sourceCostume.contents, pos.x, pos.y);
+
+ // make the target wear the new costume
+ target.doSwitchToCostume(targetCostume);
+};
+
// SpriteMorph pen up and down:
SpriteMorph.prototype.down = function () {
@@ -8162,6 +8296,24 @@ StageMorph.prototype.blockTemplates = function (category) {
blocks.push(block('setBackgroundHSVA'));
blocks.push('-');
blocks.push(block('reportPenTrailsAsCostume'));
+
+ // for debugging: ///////////////
+
+ if (this.world().isDevMode) {
+
+ blocks.push('-');
+ txt = new TextMorph(localize(
+ 'development mode \ndebugging primitives:'
+ ));
+ txt.fontSize = 9;
+ txt.setColor(this.paletteTextColor);
+ blocks.push(txt);
+ blocks.push('-');
+ blocks.push(block('doPasteOn'));
+ }
+
+ /////////////////////////////////
+
blocks.push('=');
blocks.push(this.makeBlockButton(cat));
@@ -8652,6 +8804,10 @@ StageMorph.prototype.setBackgroundColor = StageMorph.prototype.setColor;
StageMorph.prototype.getPenAttribute
= SpriteMorph.prototype.getPenAttribute;
+// StageMorph printing on another sprite:
+
+StageMorph.prototype.pasteOn = SpriteMorph.prototype.pasteOn;
+
// StageMorph pseudo-inherited behavior
StageMorph.prototype.categories = SpriteMorph.prototype.categories;
diff --git a/src/threads.js b/src/threads.js
index bc79cb82..823fe76f 100644
--- a/src/threads.js
+++ b/src/threads.js
@@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy,
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph, Color,
TableFrameMorph, ColorSlotMorph, isSnapObject, Map, newCanvas, Symbol*/
-modules.threads = '2019-July-15';
+modules.threads = '2019-August-06';
var ThreadManager;
var Process;
@@ -3762,6 +3762,31 @@ Process.prototype.changePenHSVA = Process.prototype.changeHSVA;
Process.prototype.setBackgroundHSVA = Process.prototype.setHSVA;
Process.prototype.changeBackgroundHSVA = Process.prototype.changeHSVA;
+// Process pasting primitives
+
+Process.prototype.doPasteOn = function (name, thisObj, stage) {
+ // allow for lists of sprites and also check for temparary clones,
+ // as in Scratch 2.0,
+ var myself = this,
+ those;
+ thisObj = thisObj || this.blockReceiver();
+ stage = stage || thisObj.parentThatIsA(StageMorph);
+ if (stage.name === name) {
+ name = stage;
+ }
+ if (isSnapObject(name)) {
+ return thisObj.pasteOn(name);
+ }
+ if (name instanceof List) { // assume all elements to be sprites
+ those = name.itemsArray();
+ } else {
+ those = this.getObjectsNamed(name, thisObj, stage); // clones
+ }
+ those.forEach(function (each) {
+ myself.doPasteOn(each, thisObj, stage);
+ });
+};
+
// Process temporary cloning (Scratch-style)
Process.prototype.createClone = function (name) {