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) {