diff --git a/blocks.js b/blocks.js index f30b8e41..f9694b91 100644 --- a/blocks.js +++ b/blocks.js @@ -156,7 +156,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2015-October-02'; +modules.blocks = '2015-November-16'; var SyntaxElementMorph; var BlockMorph; @@ -349,22 +349,18 @@ function SyntaxElementMorph() { this.init(); } -SyntaxElementMorph.prototype.init = function () { +SyntaxElementMorph.prototype.init = function (silently) { this.cachedClr = null; this.cachedClrBright = null; this.cachedClrDark = null; this.isStatic = false; // if true, I cannot be exchanged - SyntaxElementMorph.uber.init.call(this); + SyntaxElementMorph.uber.init.call(this, silently); this.defaults = []; this.cachedInputs = null; }; -// SyntaxElementMorph stepping: - -SyntaxElementMorph.prototype.step = null; - // SyntaxElementMorph accessing: SyntaxElementMorph.prototype.parts = function () { @@ -472,8 +468,7 @@ SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) { var scripts = this.parentThatIsA(ScriptsMorph), replacement = newArg, idx = this.children.indexOf(oldArg), - i = 0, - nb; + i = 0; // try to find the ArgLabel embedding the newArg, // used for the undrop() feature @@ -511,13 +506,6 @@ SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) { oldArg.moveBy(replacement.extent()); oldArg.fixBlockColor(); } - } else if (oldArg instanceof CommandSlotMorph) { - nb = oldArg.nestedBlock(); - if (nb) { - scripts.add(nb); - nb.moveBy(replacement.extent()); - nb.fixBlockColor(); - } } if (replacement instanceof MultiArgMorph || replacement instanceof ArgLabelMorph @@ -703,10 +691,11 @@ SyntaxElementMorph.prototype.dark = function () { // SyntaxElementMorph color changing: -SyntaxElementMorph.prototype.setColor = function (aColor) { +SyntaxElementMorph.prototype.setColor = function (aColor, silently) { if (aColor) { if (!this.color.eq(aColor)) { this.color = aColor; + if (silently) {return; } this.drawNew(); this.children.forEach(function (child) { child.drawNew(); @@ -942,6 +931,7 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { }, true // read-only ); + part.isStatic = true; break; case '%dates': part = new InputSlotMorph( @@ -1302,6 +1292,11 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { case '%cs': part = new CSlotMorph(); // non-static break; + case '%cl': + part = new CSlotMorph(); + part.isStatic = true; // rejects reporter drops + part.isLambda = true; // auto-reifies nested script + break; case '%clr': part = new ColorSlotMorph(); part.isStatic = true; @@ -1501,7 +1496,7 @@ SyntaxElementMorph.prototype.isObjInputFragment = function () { // SyntaxElementMorph layout: -SyntaxElementMorph.prototype.fixLayout = function () { +SyntaxElementMorph.prototype.fixLayout = function (silently) { var nb, parts = this.parts(), myself = this, @@ -1682,8 +1677,8 @@ SyntaxElementMorph.prototype.fixLayout = function () { } } - // set my extent: - this.setExtent(new Point(blockWidth, blockHeight)); + // set my extent (silently, because we'll redraw later anyway): + this.silentSetExtent(new Point(blockWidth, blockHeight)); // adjust CSlots parts.forEach(function (part) { @@ -1697,7 +1692,7 @@ SyntaxElementMorph.prototype.fixLayout = function () { }); // redraw in order to erase CSlot backgrounds - this.drawNew(); + if (!silently) {this.drawNew(); } // position next block: if (nb) { @@ -1777,6 +1772,7 @@ SyntaxElementMorph.prototype.showBubble = function (value, exportPic) { morphToShow.update(true); morphToShow.step = value.update; morphToShow.isDraggable = false; + morphToShow.expand(this.parentThatIsA(ScrollFrameMorph).extent()); isClickable = true; } else if (value instanceof Morph) { img = value.fullImage(); @@ -1954,7 +1950,9 @@ SyntaxElementMorph.prototype.endLayout = function () { %lst - chameleon colored rectangular drop-down for list names %b - chameleon colored hexagonal slot (for predicates) %l - list icon - %c - C-shaped command slot + %c - C-shaped command slot, special form for primitives + %cs - C-shaped, auto-reifying, accepts reporter drops + %cl - C-shaped, auto-reifying, rejects reporters %clr - interactive color slot %t - inline variable reporter template %anyUE - white rectangular type-in slot, unevaluated if replaced @@ -2027,7 +2025,7 @@ function BlockMorph() { this.init(); } -BlockMorph.prototype.init = function () { +BlockMorph.prototype.init = function (silently) { this.selector = null; // name of method to be triggered this.blockSpec = ''; // formal description of label and arguments this.comment = null; // optional "sticky" comment morph @@ -2036,7 +2034,7 @@ BlockMorph.prototype.init = function () { this.instantiationSpec = null; // spec to set upon fullCopy() of template this.category = null; // for zebra coloring (non persistent) - BlockMorph.uber.init.call(this); + BlockMorph.uber.init.call(this, silently); this.color = new Color(0, 17, 173); this.cashedInputs = null; }; @@ -2101,7 +2099,7 @@ BlockMorph.prototype.parseSpec = function (spec) { return result; }; -BlockMorph.prototype.setSpec = function (spec) { +BlockMorph.prototype.setSpec = function (spec, silently) { var myself = this, part, inputIdx = -1; @@ -2144,10 +2142,17 @@ BlockMorph.prototype.setSpec = function (spec) { } }); this.blockSpec = spec; - this.fixLayout(); + this.fixLayout(silently); this.cachedInputs = null; }; +BlockMorph.prototype.userSetSpec = function (spec) { + var tb = this.topBlock(); + tb.fullChanged(); + this.setSpec(spec); + tb.fullChanged(); +}; + BlockMorph.prototype.buildSpec = function () { // create my blockSpec from my parts - for demo purposes only var myself = this; @@ -2448,6 +2453,7 @@ BlockMorph.prototype.ringify = function () { center = top.fullBounds().center(); if (this.parent === null) {return null; } + top.fullChanged(); if (this.parent instanceof SyntaxElementMorph) { if (this instanceof ReporterBlockMorph) { this.parent.silentReplaceInput(this, ring); @@ -2463,11 +2469,14 @@ BlockMorph.prototype.ringify = function () { ring.setCenter(center); } this.fixBlockColor(null, true); + top.fullChanged(); + }; BlockMorph.prototype.unringify = function () { // remove a Ring around me, if any var ring = this.parentThatIsA(RingMorph), + top = this.topBlock(), scripts = this.parentThatIsA(ScriptsMorph), block, center; @@ -2476,6 +2485,7 @@ BlockMorph.prototype.unringify = function () { block = ring.contents(); center = ring.center(); + top.fullChanged(); if (ring.parent instanceof SyntaxElementMorph) { if (block instanceof ReporterBlockMorph) { ring.parent.silentReplaceInput(ring, block); @@ -2491,6 +2501,7 @@ BlockMorph.prototype.unringify = function () { ring.destroy(); } this.fixBlockColor(null, true); + top.fullChanged(); }; BlockMorph.prototype.relabel = function (alternativeSelectors) { @@ -2887,12 +2898,13 @@ BlockMorph.prototype.eraseHoles = function (context) { var w = hole.width(), h = Math.floor(hole.height()) - 2; // Opera needs this context.clearRect( - Math.floor(hole.bounds.origin.x - myself.bounds.origin.x) + 1, - Math.floor(hole.bounds.origin.y - myself.bounds.origin.y) + 1, - isReporter ? w - 1 : w + 1, + hole.bounds.origin.x - myself.bounds.origin.x + 1, + hole.bounds.origin.y - myself.bounds.origin.y + 1, + isReporter ? w - 2 : w + 1, h ); }); + }; // BlockMorph highlighting @@ -3081,7 +3093,7 @@ BlockMorph.prototype.fixBlockColor = function (nearestBlock, isForced) { BlockMorph.prototype.forceNormalColoring = function () { var clr = SpriteMorph.prototype.blockColor[this.category]; - this.setColor(clr); + this.setColor(clr, true); // silently this.setLabelColor( new Color(255, 255, 255), clr.darker(this.labelContrast), @@ -3096,10 +3108,11 @@ BlockMorph.prototype.alternateBlockColor = function () { if (this.color.eq(clr)) { this.setColor( this.zebraContrast < 0 ? clr.darker(Math.abs(this.zebraContrast)) - : clr.lighter(this.zebraContrast) + : clr.lighter(this.zebraContrast), + true // silently ); } else { - this.setColor(clr); + this.setColor(clr, true); // silently } this.fixLabelColor(); this.fixChildrensBlockColor(true); // has issues if not forced @@ -3356,6 +3369,7 @@ BlockMorph.prototype.prepareToBeGrabbed = function (hand) { }; BlockMorph.prototype.justDropped = function () { + this.alpha = 1; this.allComments().forEach(function (comment) { comment.stopFollowing(); }); @@ -3444,9 +3458,9 @@ function CommandBlockMorph() { this.init(); } -CommandBlockMorph.prototype.init = function () { - CommandBlockMorph.uber.init.call(this); - this.setExtent(new Point(200, 100)); +CommandBlockMorph.prototype.init = function (silently) { + CommandBlockMorph.uber.init.call(this, silently); + this.setExtent(new Point(200, 100), silently); this.partOfCustomCommand = false; this.exitTag = null; }; @@ -3479,7 +3493,12 @@ CommandBlockMorph.prototype.nextBlock = function (block) { if (nb) { block.bottomBlock().nextBlock(nb); } - this.fixLayout(); + block.setPosition( + new Point( + this.left(), + this.bottom() - (this.corner) + ) + ); if (affected) { affected.fixLayout(); } @@ -4125,7 +4144,7 @@ function HatBlockMorph() { } HatBlockMorph.prototype.init = function () { - HatBlockMorph.uber.init.call(this); + HatBlockMorph.uber.init.call(this, true); // silently this.setExtent(new Point(300, 150)); }; @@ -4302,10 +4321,10 @@ function ReporterBlockMorph(isPredicate) { this.init(isPredicate); } -ReporterBlockMorph.prototype.init = function (isPredicate) { - ReporterBlockMorph.uber.init.call(this); +ReporterBlockMorph.prototype.init = function (isPredicate, silently) { + ReporterBlockMorph.uber.init.call(this, silently); this.isPredicate = isPredicate || false; - this.setExtent(new Point(200, 80)); + this.setExtent(new Point(200, 80), silently); }; // ReporterBlockMorph drag & drop: @@ -4313,6 +4332,7 @@ ReporterBlockMorph.prototype.init = function (isPredicate) { ReporterBlockMorph.prototype.snap = function (hand) { // passing the hand is optional (for when blocks are dragged & dropped) var scripts = this.parent, + nb, target; if (!scripts instanceof ScriptsMorph) { @@ -4329,6 +4349,16 @@ ReporterBlockMorph.prototype.snap = function (hand) { if (target instanceof MultiArgMorph) { scripts.lastPreservedBlocks = target.inputs(); scripts.lastReplacedInput = target.fullCopy(); + } else if (target instanceof CommandSlotMorph) { + scripts.lastReplacedInput = target; + nb = target.nestedBlock(); + if (nb) { + nb = nb.fullCopy(); + scripts.add(nb); + nb.moveBy(nb.extent()); + nb.fixBlockColor(); + scripts.lastPreservedBlocks = [nb]; + } } target.parent.replaceInput(target, this); if (this.snapSound) { @@ -4352,6 +4382,7 @@ ReporterBlockMorph.prototype.prepareToBeGrabbed = function (handMorph) { this.setPosition(oldPos); } ReporterBlockMorph.uber.prepareToBeGrabbed.call(this, handMorph); + this.alpha = 0.85; }; // ReporterBlockMorph enumerating @@ -4413,7 +4444,7 @@ ReporterBlockMorph.prototype.mouseClickLeft = function (pos) { this.parent.parent.parent instanceof RingMorph; new DialogBoxMorph( this, - this.setSpec, + this.userSetSpec, this ).prompt( isRing ? "Input name" : "Script variable name", @@ -4987,6 +5018,7 @@ ScriptsMorph.prototype.init = function (owner) { ScriptsMorph.uber.init.call(this); this.setColor(new Color(70, 70, 70)); + this.noticesTransparentClick = true; }; // ScriptsMorph deep copying: @@ -5180,14 +5212,15 @@ ScriptsMorph.prototype.closestInput = function (reporter, hand) { all, function (input) { return (input instanceof InputSlotMorph - || input instanceof ArgMorph + || (input instanceof ArgMorph + && !(input instanceof CommandSlotMorph) + && !(input instanceof MultiArgMorph)) || (input instanceof RingMorph && !input.contents()) || input.isEmptySlot()) && !input.isLocked() && input.bounds.containsPoint(handPos) - && !contains(blackList, input) - && touchingVariadicArrowsIfAny(input, handPos); + && !contains(blackList, input); } ); if (target) { @@ -5550,12 +5583,12 @@ function ArgMorph(type) { this.init(type); } -ArgMorph.prototype.init = function (type) { +ArgMorph.prototype.init = function (type, silently) { this.type = type || null; this.isHole = false; - ArgMorph.uber.init.call(this); + ArgMorph.uber.init.call(this, silently); this.color = new Color(0, 17, 173); - this.setExtent(new Point(50, 50)); + this.setExtent(new Point(50, 50), silently); }; // ArgMorph drag & drop: for demo puposes only @@ -5663,10 +5696,13 @@ function CommandSlotMorph() { this.init(); } -CommandSlotMorph.prototype.init = function () { - CommandSlotMorph.uber.init.call(this); +CommandSlotMorph.prototype.init = function (silently) { + CommandSlotMorph.uber.init.call(this, null, true); // silently this.color = new Color(0, 17, 173); - this.setExtent(new Point(230, this.corner * 4 + this.cSlotPadding)); + this.setExtent( + new Point(230, this.corner * 4 + this.cSlotPadding), + silently + ); }; CommandSlotMorph.prototype.getSpec = function () { @@ -6118,8 +6154,8 @@ function RingCommandSlotMorph() { this.init(); } -RingCommandSlotMorph.prototype.init = function () { - RingCommandSlotMorph.uber.init.call(this); +RingCommandSlotMorph.prototype.init = function (silently) { + RingCommandSlotMorph.uber.init.call(this, silently); this.isHole = true; this.noticesTransparentClick = true; this.color = new Color(0, 17, 173); @@ -6269,11 +6305,15 @@ function CSlotMorph() { this.init(); } -CSlotMorph.prototype.init = function () { - CommandSlotMorph.uber.init.call(this); +CSlotMorph.prototype.init = function (silently) { + CommandSlotMorph.uber.init.call(this, null, true); // silently this.isHole = true; + this.isLambda = false; // see Process.prototype.evaluateInput this.color = new Color(0, 17, 173); - this.setExtent(new Point(230, this.corner * 4 + this.cSlotPadding)); + this.setExtent( + new Point(230, this.corner * 4 + this.cSlotPadding), + silently + ); }; CSlotMorph.prototype.getSpec = function () { @@ -6721,7 +6761,7 @@ InputSlotMorph.prototype.init = function ( this.minWidth = 0; // can be chaged for text-type inputs ("landscape") this.constant = null; - InputSlotMorph.uber.init.call(this); + InputSlotMorph.uber.init.call(this, null, true); this.color = new Color(255, 255, 255); this.add(contents); this.add(arrow); @@ -7874,7 +7914,7 @@ ArrowMorph.prototype.init = function (direction, size, padding, color) { this.size = size || ((size === 0) ? 0 : 50); this.padding = padding || 0; - ArrowMorph.uber.init.call(this); + ArrowMorph.uber.init.call(this, true); // silently this.color = color || new Color(0, 0, 0); this.setExtent(new Point(this.size, this.size)); }; @@ -7963,7 +8003,7 @@ TextSlotMorph.prototype.init = function ( this.minWidth = 0; // can be chaged for text-type inputs ("landscape") this.constant = null; - InputSlotMorph.uber.init.call(this); + InputSlotMorph.uber.init.call(this, null, null, null, null, true); // sil. this.color = new Color(255, 255, 255); this.add(contents); this.add(arrow); @@ -8084,7 +8124,7 @@ SymbolMorph.prototype.init = function ( this.shadowOffset = shadowOffset || new Point(0, 0); this.shadowColor = shadowColor || null; - SymbolMorph.uber.init.call(this); + SymbolMorph.uber.init.call(this, true); // silently this.color = color || new Color(0, 0, 0); this.drawNew(); }; @@ -9316,7 +9356,7 @@ function ColorSlotMorph(clr) { } ColorSlotMorph.prototype.init = function (clr) { - ColorSlotMorph.uber.init.call(this); + ColorSlotMorph.uber.init.call(this, null, true); // silently this.setColor(clr || new Color(145, 26, 68)); }; @@ -9494,7 +9534,7 @@ MultiArgMorph.prototype.init = function ( this.shadowOffset = shadowOffset || null; this.canBeEmpty = true; - MultiArgMorph.uber.init.call(this); + MultiArgMorph.uber.init.call(this, null, true); // silently // MultiArgMorphs are transparent by default b/c of zebra coloring this.alpha = isTransparent === false ? 1 : 0; @@ -9882,7 +9922,7 @@ ArgLabelMorph.prototype.init = function (argMorph, labelTxt) { var label; this.labelText = localize(labelTxt || 'input list:'); - ArgLabelMorph.uber.init.call(this); + ArgLabelMorph.uber.init.call(this, null, true); // silently this.isStatic = true; // I cannot be exchanged @@ -10008,14 +10048,17 @@ function FunctionSlotMorph(isPredicate) { this.init(isPredicate); } -FunctionSlotMorph.prototype.init = function (isPredicate) { - FunctionSlotMorph.uber.init.call(this); +FunctionSlotMorph.prototype.init = function (isPredicate, silently) { + FunctionSlotMorph.uber.init.call(this, null, true); // silently this.isPredicate = isPredicate || false; this.color = this.rfColor; - this.setExtent(new Point( - (this.fontSize + this.edge * 2) * 2, - this.fontSize + this.edge * 2 - )); + this.setExtent( + new Point( + (this.fontSize + this.edge * 2) * 2, + this.fontSize + this.edge * 2 + ), + silently + ); }; FunctionSlotMorph.prototype.getSpec = function () { @@ -10390,7 +10433,7 @@ function ReporterSlotMorph(isPredicate) { } ReporterSlotMorph.prototype.init = function (isPredicate) { - ReporterSlotMorph.uber.init.call(this, isPredicate); + ReporterSlotMorph.uber.init.call(this, isPredicate, true); this.add(this.emptySlot()); this.fixLayout(); }; @@ -10481,7 +10524,7 @@ function RingReporterSlotMorph(isPredicate) { } RingReporterSlotMorph.prototype.init = function (isPredicate) { - RingReporterSlotMorph.uber.init.call(this, isPredicate); + RingReporterSlotMorph.uber.init.call(this, isPredicate, true); this.alpha = RingMorph.prototype.alpha; this.contrast = RingMorph.prototype.contrast; this.isHole = true; diff --git a/byob.js b/byob.js index f4d19f98..c9267823 100644 --- a/byob.js +++ b/byob.js @@ -108,7 +108,7 @@ SymbolMorph, isNil, CursorMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.byob = '2015-October-07'; +modules.byob = '2015-November-16'; // Declarations @@ -148,6 +148,7 @@ function CustomBlockDefinition(spec, receiver) { // don't serialize (not needed for functionality): this.receiver = receiver || null; // for serialization only (pointer) + this.editorDimensions = null; // a rectangle, last bounds of the editor } // CustomBlockDefinition instantiating blocks @@ -405,9 +406,7 @@ function CustomCommandBlockMorph(definition, isProto) { CustomCommandBlockMorph.prototype.init = function (definition, isProto) { this.definition = definition; // mandatory this.isPrototype = isProto || false; // optional - - CustomCommandBlockMorph.uber.init.call(this); - + CustomCommandBlockMorph.uber.init.call(this, true); // silently this.category = definition.category; this.selector = 'evaluateCustomBlock'; if (definition) { // needed for de-serializing @@ -415,7 +414,7 @@ CustomCommandBlockMorph.prototype.init = function (definition, isProto) { } }; -CustomCommandBlockMorph.prototype.refresh = function () { +CustomCommandBlockMorph.prototype.refresh = function (silently) { var def = this.definition, newSpec = this.isPrototype ? def.spec : def.blockSpec(), @@ -429,7 +428,7 @@ CustomCommandBlockMorph.prototype.refresh = function () { } else { this.fixBlockColor(); } - this.setSpec(newSpec); + this.setSpec(newSpec, silently); this.fixLabelColor(); this.restoreInputs(oldInputs); } else { // update all input slots' drop-downs @@ -671,7 +670,7 @@ CustomCommandBlockMorph.prototype.mouseClickLeft = function () { }; CustomCommandBlockMorph.prototype.edit = function () { - var myself = this, block, hat; + var myself = this, editor, block, hat; if (this.isPrototype) { block = this.definition.blockInstance(); @@ -696,7 +695,11 @@ CustomCommandBlockMorph.prototype.edit = function () { myself.isInUse() ); } else { - new BlockEditorMorph(this.definition, this.receiver()).popUp(); + Morph.prototype.trackChanges = false; + editor = new BlockEditorMorph(this.definition, this.receiver()); + editor.popUp(); + Morph.prototype.trackChanges = true; + editor.changed(); } }; @@ -763,7 +766,7 @@ CustomCommandBlockMorph.prototype.userMenu = function () { menu.addItem( "script pic...", function () { - window.open(this.topBlock().fullImage().toDataURL()); + window.open(this.topBlock().scriptPic().toDataURL()); }, 'open a new window\nwith a picture of this script' ); @@ -936,7 +939,7 @@ CustomReporterBlockMorph.prototype.init = function ( this.definition = definition; // mandatory this.isPrototype = isProto || false; // optional - CustomReporterBlockMorph.uber.init.call(this, isPredicate); + CustomReporterBlockMorph.uber.init.call(this, isPredicate, true); // sil. this.category = definition.category; this.selector = 'evaluateCustomBlock'; @@ -946,7 +949,7 @@ CustomReporterBlockMorph.prototype.init = function ( }; CustomReporterBlockMorph.prototype.refresh = function () { - CustomCommandBlockMorph.prototype.refresh.call(this); + CustomCommandBlockMorph.prototype.refresh.call(this, true); if (!this.isPrototype) { this.isPredicate = (this.definition.type === 'predicate'); } @@ -1726,7 +1729,7 @@ BlockEditorMorph.prototype.init = function (definition, target) { this.addButton('updateDefinition', 'Apply'); this.addButton('cancel', 'Cancel'); - this.setExtent(new Point(375, 300)); + this.setExtent(new Point(375, 300)); // normal initial extent this.fixLayout(); proto.children[0].fixLayout(); scripts.fixMultiArgs(); @@ -1737,6 +1740,7 @@ BlockEditorMorph.prototype.popUp = function () { if (world) { BlockEditorMorph.uber.popUp.call(this, world); + this.setInitialDimensions(); this.handle = new HandleMorph( this, 280, @@ -1744,9 +1748,18 @@ BlockEditorMorph.prototype.popUp = function () { this.corner, this.corner ); + world.keyboardReceiver = null; } }; +BlockEditorMorph.prototype.justDropped = function () { + // override the inherited default behavior, which is to + // give keyboard focus to dialog boxes, as in this case + // we want Snap-global keyboard-shortcuts like ctrl-f + // to still work + nop(); +}; + // BlockEditorMorph ops BlockEditorMorph.prototype.accept = function (origin) { @@ -1851,6 +1864,7 @@ BlockEditorMorph.prototype.updateDefinition = function () { this.definition.spec = this.prototypeSpec(); this.definition.declarations = this.prototypeSlots(); this.definition.scripts = []; + this.definition.editorDimensions = this.bounds.copy(); this.body.contents.children.forEach(function (morph) { if (morph instanceof PrototypeHatBlockMorph) { @@ -1927,6 +1941,26 @@ BlockEditorMorph.prototype.prototypeSlots = function () { // BlockEditorMorph layout +BlockEditorMorph.prototype.setInitialDimensions = function () { + var world = this.world(), + mex = world.extent().subtract(new Point(this.padding, this.padding)), + th = fontHeight(this.titleFontSize) + this.titlePadding * 2, + bh = this.buttons.height(); + + if (this.definition.editorDimensions) { + this.setPosition(this.definition.editorDimensions.origin); + this.setExtent(this.definition.editorDimensions.extent().min(mex)); + this.keepWithin(world); + return; + } + this.setExtent( + this.body.contents.extent().add( + new Point(this.padding, this.padding + th + bh) + ).min(mex) + ); + this.setCenter(this.world().center()); +}; + BlockEditorMorph.prototype.fixLayout = function () { var th = fontHeight(this.titleFontSize) + this.titlePadding * 2; diff --git a/gui.js b/gui.js index 2a2d7bea..462fbc6d 100644 --- a/gui.js +++ b/gui.js @@ -71,7 +71,7 @@ BlockRemovalDialogMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2015-October-07'; +modules.gui = '2015-November-16'; // Declarations @@ -2227,6 +2227,15 @@ IDE_Morph.prototype.settingsMenu = function () { 'Stage size...', 'userSetStageSize' ); + if (shiftClicked) { + menu.addItem( + 'Dragging threshold...', + 'userSetDragThreshold', + 'specify the distance the hand has to move\n' + + 'before it picks up an object', + new Color(100, 0, 0) + ); + } menu.addLine(); addPreference( 'Blurred shadows', @@ -2713,7 +2722,7 @@ IDE_Morph.prototype.aboutSnap = function () { module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn, world = this.world(); - aboutTxt = 'Snap! 4.0.2\nBuild Your Own Blocks\n\n' + aboutTxt = 'Snap! 4.0.3\nBuild Your Own Blocks\n\n' + 'Copyright \u24B8 2015 Jens M\u00F6nig and ' + 'Brian Harvey\n' + 'jens@moenig.org, bh@cs.berkeley.edu\n\n' @@ -4201,6 +4210,29 @@ IDE_Morph.prototype.setStageExtent = function (aPoint) { } }; +// IDE_Morph dragging threshold (internal feature) + +IDE_Morph.prototype.userSetDragThreshold = function () { + new DialogBoxMorph( + this, + function (num) { + MorphicPreferences.grabThreshold = Math.min( + Math.max(+num, 0), + 200 + ); + }, + this + ).prompt( + "Dragging threshold", + MorphicPreferences.grabThreshold.toString(), + this.world(), + null, // pic + null, // choices + null, // read only + true // numeric + ); +}; + // IDE_Morph cloud interface IDE_Morph.prototype.initializeCloud = function () { @@ -5945,6 +5977,7 @@ SpriteIconMorph.prototype.prepareToBeGrabbed = function () { var ide = this.parentThatIsA(IDE_Morph), idx; this.mouseClickLeft(); // select me + this.alpha = 0.85; if (ide) { idx = ide.sprites.asArray().indexOf(this.object); ide.sprites.remove(idx + 1); @@ -5953,6 +5986,10 @@ SpriteIconMorph.prototype.prepareToBeGrabbed = function () { } }; +SpriteIconMorph.prototype.justDropped = function () { + this.alpha = 1; +}; + SpriteIconMorph.prototype.wantsDropOf = function (morph) { // allow scripts & media to be copied from one sprite to another // by drag & drop diff --git a/history.txt b/history.txt index 00d5fab5..8209ac64 100755 --- a/history.txt +++ b/history.txt @@ -2603,3 +2603,75 @@ ______ * Store: Fix deserialization support for projects using inheritance * German translation update + +++++++++++++++++++++++++++ +new stuff - bulk of 151116 +++++++++++++++++++++++++++ + +151030 +------ +* Blocks: Tweak precision of rendering of transparent “holes” +* updated Czech translation (remember to integrate from email!) +* Morphic: Streamlined nop-stepping +* Blocks: Let SyntaxElements step (again), for better input slot editing experience + +151101 +------ +* BYOB: Script pic: Always export comments attached to custom block definitions +* Blocks: fixed #982 (made %interaction slot static) +* Morphic: removed an obsolete line (“dragOrigin”) +* BYOB: make block editor big enough to show the whole definition, if possible +* BYOB: remember user-set position and size of block editor when pressing “OK”, per session (not serialized in project) +* Blocks: speed up stacking of commands (also when done programmatically) by suppressing redraws +* Morphic, Blocks, BYOB: Suppress redundant redraws + +151104 +------ +* Morphic: new grabTheshold preference to suppress accidental grabbing through micro-movements of the hand +* GUI: hidden (shift-click) option to adjust the grabThreshold for the current session +* Lists, Blocks: Expand list watchers inside result bubbles to show everything +* Objects: Expand list watchers inside speech/thought bubbles to show everything +* Morphic: fixed a bug that occasionally expanded the Hand’s bounds when dragging morphs + +151107 +------ +* Threads: invoke a block synchronously + +151009 +------ +* Morphic: cache fullImage and fullBounds when dragging +* Blocks: make reporters semi-transparent while dragging +* GUI: make SpriteIcons semi-transparent while dragging +* Blocks: make it harder to drop reporters onto filled custom C-slots and variadic slot arrows +* Blocks: make ScriptsMorphs notice transparent clicks (addresses #997) +* Blocks: fixed “undrop” for replacing C-slots with reporters +* BYOB: fixed ctrl-f for the BlockEditor in all situations + +151111 +------ +* Objects: fixed a between slideBackTo() and possible running scripts in sprites, thanks, Paul, for reporting it! + +151112 +------ +* Blocks, Objects, Threads: new internal slot type: %cl for auto-reifying C-slots that reject reporter drops. Changed (hidden) “for each” to reject reporters in C-slot + +151113 +------ +* Frames, snap.html: initial version of a new general purpose prototypal single inheritance object system +* snap_slo.html: alternative animation-frame based outer scheduler, experimental +* Threads: added optional timeout to the new synchronous invoke(block) function +* Blocks: fixed too brutally optimized redraw for “ringify” and “unringify” + +151114 +------ +* Frames, snap.html, snap_slo.html: remove initial version for now, needs more low-levelish rewrite (Map-based “shortcut” design doesn’t cut it). + +151116 +------ +* Blocks, GUI: Slightly less transparency for dragged reporters and sprite icons + +=== v4.0.3 (unreleased) === + +++++++++++++++++++++++++++ +end - bulk of 151116 +++++++++++++++++++++++++++ diff --git a/lists.js b/lists.js index 54e3fdad..fa99b0cc 100644 --- a/lists.js +++ b/lists.js @@ -61,7 +61,7 @@ PushButtonMorph, SyntaxElementMorph, Color, Point, WatcherMorph, StringMorph, SpriteMorph, ScrollFrameMorph, CellMorph, ArrowMorph, MenuMorph, snapEquals, Morph, isNil, localize, MorphicPreferences*/ -modules.lists = '2015-October-07'; +modules.lists = '2015-November-16'; var List; var ListWatcherMorph; @@ -682,13 +682,14 @@ ListWatcherMorph.prototype.arrangeCells = function () { this.frame.contents.adjustBounds(); }; -ListWatcherMorph.prototype.expand = function () { +ListWatcherMorph.prototype.expand = function (maxExtent) { // make sure to show all (first 100) cells - // used for exporting a project summary var fe = this.frame.contents.extent(), - w = fe.x + 6, - h = fe.y + this.label.height() + 6; - this.setExtent(new Point(w, h)); + ext = new Point(fe.x + 6, fe.y + this.label.height() + 6); + if (maxExtent) { + ext = ext.min(maxExtent); + } + this.setExtent(ext); this.handle.setRight(this.right() - 3); this.handle.setBottom(this.bottom() - 3); }; diff --git a/morphic.js b/morphic.js index 78e86287..071382d9 100644 --- a/morphic.js +++ b/morphic.js @@ -1052,7 +1052,7 @@ /*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio, FileList, getBlurredShadowSupport*/ -var morphicVersion = '2015-September-23'; +var morphicVersion = '2015-November-16'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug @@ -1072,7 +1072,8 @@ var standardSettings = { useVirtualKeyboard: true, isTouchDevice: false, // turned on by touch events, don't set rasterizeSVGs: false, - isFlat: false + isFlat: false, + grabThreshold: 5 }; var touchScreenSettings = { @@ -1091,7 +1092,8 @@ var touchScreenSettings = { useVirtualKeyboard: true, isTouchDevice: false, rasterizeSVGs: false, - isFlat: false + isFlat: false, + grabThreshold: 5 }; var MorphicPreferences = standardSettings; @@ -2207,7 +2209,10 @@ function Morph() { Morph.prototype.init = function (noDraw) { Morph.uber.init.call(this); this.isMorph = true; + this.image = null; this.bounds = new Rectangle(0, 0, 50, 40); + this.cachedFullImage = null; + this.cachedFullBounds = null; this.color = new Color(80, 80, 80); this.texture = null; // optional url of a fill-image this.cachedTexture = null; // internal cache of actual bg image @@ -2284,9 +2289,7 @@ Morph.prototype.nextSteps = function (arrayOfFunctions) { } }; -Morph.prototype.step = function () { - nop(); -}; +Morph.prototype.step = nop; // Morph accessing - geometry getting: @@ -2412,6 +2415,9 @@ Morph.prototype.silentMoveBy = function (delta) { var children = this.children, i = children.length; this.bounds = this.bounds.translateBy(delta); + if (this.cachedFullBounds) { + this.cachedFullBounds = this.cachedFullBounds.translateBy(delta); + } // ugly optimization avoiding forEach() for (i; i > 0; i -= 1) { children[i - 1].silentMoveBy(delta); @@ -2533,7 +2539,12 @@ Morph.prototype.scrollIntoView = function () { // Morph accessing - dimensional changes requiring a complete redraw -Morph.prototype.setExtent = function (aPoint) { +Morph.prototype.setExtent = function (aPoint, silently) { + // silently avoids redrawing the receiver + if (silently) { + this.silentSetExtent(aPoint); + return; + } if (!aPoint.eq(this.extent())) { this.changed(); this.silentSetExtent(aPoint); @@ -2640,29 +2651,31 @@ Morph.prototype.drawCachedTexture = function () { */ Morph.prototype.drawOn = function (aCanvas, aRect) { - var rectangle, area, delta, src, context, w, h, sl, st; + var rectangle, area, delta, src, context, w, h, sl, st, + pic = this.cachedFullImage || this.image, + bounds = this.cachedFullBounds || this.bounds; if (!this.isVisible) { return null; } - rectangle = aRect || this.bounds(); - area = rectangle.intersect(this.bounds); + rectangle = aRect || bounds; + area = rectangle.intersect(bounds); if (area.extent().gt(new Point(0, 0))) { - delta = this.position().neg(); + delta = bounds.position().neg(); src = area.copy().translateBy(delta); context = aCanvas.getContext('2d'); context.globalAlpha = this.alpha; sl = src.left(); st = src.top(); - w = Math.min(src.width(), this.image.width - sl); - h = Math.min(src.height(), this.image.height - st); + w = Math.min(src.width(), pic.width - sl); + h = Math.min(src.height(), pic.height - st); if (w < 1 || h < 1) { return null; } context.drawImage( - this.image, + pic, sl, st, w, @@ -2672,42 +2685,6 @@ Morph.prototype.drawOn = function (aCanvas, aRect) { w, h ); - - /* "for debugging purposes:" - - try { - context.drawImage( - this.image, - sl, - st, - w, - h, - area.left(), - area.top(), - w, - h - ); - } catch (err) { - alert('internal error\n\n' + err - + '\n ---' - + '\n canvas: ' + aCanvas - + '\n canvas.width: ' + aCanvas.width - + '\n canvas.height: ' + aCanvas.height - + '\n ---' - + '\n image: ' + this.image - + '\n image.width: ' + this.image.width - + '\n image.height: ' + this.image.height - + '\n ---' - + '\n w: ' + w - + '\n h: ' + h - + '\n sl: ' + sl - + '\n st: ' + st - + '\n area.left: ' + area.left() - + '\n area.top ' + area.top() - ); - } - */ - } }; @@ -2716,8 +2693,9 @@ Morph.prototype.fullDrawOn = function (aCanvas, aRect) { if (!this.isVisible) { return null; } - rectangle = aRect || this.fullBounds(); + rectangle = aRect || this.cachedFullBounds || this.fullBounds(); this.drawOn(aCanvas, rectangle); + if (this.cachedFullImage) {return; } this.children.forEach(function (child) { child.fullDrawOn(aCanvas, rectangle); }); @@ -2911,7 +2889,9 @@ Morph.prototype.fullChanged = function () { if (this.trackChanges) { var w = this.root(); if (w instanceof WorldMorph) { - w.broken.push(this.fullBounds().spread()); + w.broken.push( + (this.cachedFullBounds || this.fullBounds()).spread() + ); } } }; @@ -9448,7 +9428,7 @@ function HandMorph(aWorld) { // HandMorph initialization: HandMorph.prototype.init = function (aWorld) { - HandMorph.uber.init.call(this); + HandMorph.uber.init.call(this, true); this.bounds = new Rectangle(); // additional properties: @@ -9456,6 +9436,7 @@ HandMorph.prototype.init = function (aWorld) { this.mouseButton = null; this.mouseOverList = []; this.morphToGrab = null; + this.grabPosition = null; this.grabOrigin = null; this.temporaries = []; this.touchHoldTimeout = null; @@ -9520,6 +9501,8 @@ HandMorph.prototype.grab = function (aMorph) { if (aMorph.prepareToBeGrabbed) { aMorph.prepareToBeGrabbed(this); } + aMorph.cachedFullImage = aMorph.fullImageClassic(); + aMorph.cachedFullBounds = aMorph.fullBounds(); this.add(aMorph); this.changed(); if (oldParent && oldParent.reactToGrabOf) { @@ -9535,6 +9518,8 @@ HandMorph.prototype.drop = function () { target = this.dropTargetFor(morphToDrop); this.changed(); target.add(morphToDrop); + morphToDrop.cachedFullImage = null; + morphToDrop.cachedFullBounds = null; morphToDrop.changed(); morphToDrop.removeShadow(); this.children = []; @@ -9545,7 +9530,6 @@ HandMorph.prototype.drop = function () { if (target.reactToDropOf) { target.reactToDropOf(morphToDrop, this); } - this.dragOrigin = null; } }; @@ -9572,6 +9556,7 @@ HandMorph.prototype.processMouseDown = function (event) { this.destroyTemporaries(); this.contextMenuEnabled = true; this.morphToGrab = null; + this.grabPosition = null; if (this.children.length !== 0) { this.drop(); this.mouseButton = null; @@ -9599,6 +9584,7 @@ HandMorph.prototype.processMouseDown = function (event) { } if (!morph.mouseMove) { this.morphToGrab = morph.rootForGrab(); + this.grabPosition = this.bounds.origin.copy(); } if (event.button === 2 || event.ctrlKey) { this.mouseButton = 'right'; @@ -9708,8 +9694,7 @@ HandMorph.prototype.processMouseMove = function (event) { mouseOverNew, myself = this, morph, - topMorph, - fb; + topMorph; pos = new Point( event.pageX - posInDocument.x, @@ -9733,7 +9718,11 @@ HandMorph.prototype.processMouseMove = function (event) { } // if a morph is marked for grabbing, just grab it - if (this.mouseButton === 'left' && this.morphToGrab) { + if (this.mouseButton === 'left' && + this.morphToGrab && + (this.grabPosition.distanceTo(this.bounds.origin) > + MorphicPreferences.grabThreshold)) { + this.setPosition(this.grabPosition); if (this.morphToGrab.isDraggable) { morph = this.morphToGrab; this.grab(morph); @@ -9747,16 +9736,7 @@ HandMorph.prototype.processMouseMove = function (event) { this.grab(morph); this.grabOrigin = this.morphToGrab.situation(); } - if (morph) { - // if the mouse has left its fullBounds, allow to center it - fb = morph.fullBounds(); - if (!fb.containsPoint(pos) && - morph.isCorrectingOutsideDrag()) { - this.bounds.origin = fb.center(); - this.grab(morph); - this.setPosition(pos); - } - } + this.setPosition(pos); } } diff --git a/objects.js b/objects.js index e51fe07f..5cad377f 100644 --- a/objects.js +++ b/objects.js @@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.objects = '2015-October-07'; +modules.objects = '2015-November-16'; var SpriteMorph; var StageMorph; @@ -1184,7 +1184,7 @@ SpriteMorph.prototype.initBlocks = function () { dev: true, type: 'command', category: 'lists', - spec: 'for %upvar in %l %cs', + spec: 'for %upvar in %l %cl', defaults: [localize('each item')] }, @@ -2326,7 +2326,7 @@ SpriteMorph.prototype.freshPalette = function (category) { x = block.right() + unit / 2; ry = block.bottom(); } else { - if (block.fixLayout) {block.fixLayout(); } + // if (block.fixLayout) {block.fixLayout(); } x = 0; y += block.height(); } @@ -3249,7 +3249,7 @@ SpriteMorph.prototype.bubble = function (data, isThought, isQuestion) { if (data === '' || isNil(data)) {return; } bubble = new SpriteBubbleMorph( data, - stage ? stage.scale : 1, + stage, isThought, isQuestion ); @@ -3539,6 +3539,7 @@ SpriteMorph.prototype.gotoXY = function (x, y, justMe) { newY, dest; + if (!stage) {return; } newX = stage.center().x + (+x || 0) * stage.scale; newY = stage.center().y - (+y || 0) * stage.scale; if (this.costume) { @@ -6054,18 +6055,19 @@ SpriteBubbleMorph.uber = SpeechBubbleMorph.prototype; // SpriteBubbleMorph instance creation: -function SpriteBubbleMorph(data, scale, isThought, isQuestion) { - this.init(data, scale, isThought, isQuestion); +function SpriteBubbleMorph(data, stage, isThought, isQuestion) { + this.init(data, stage, isThought, isQuestion); } SpriteBubbleMorph.prototype.init = function ( data, - scale, + stage, isThought, isQuestion ) { var sprite = SpriteMorph.prototype; - this.scale = scale || 1; + this.stage = stage; + this.scale = stage ? stage.scale : 1; this.data = data; this.isQuestion = isQuestion; @@ -6125,6 +6127,11 @@ SpriteBubbleMorph.prototype.dataAsMorph = function (data) { contents.isDraggable = false; contents.update(true); contents.step = contents.update; + if (this.stage) { + contents.expand(this.stage.extent().translateBy( + -2 * (this.edge + this.border + this.padding) + )); + } } else if (data instanceof Context) { img = data.image(); contents = new Morph(); diff --git a/snap_slo.html b/snap_slo.html new file mode 100755 index 00000000..ffc54b7c --- /dev/null +++ b/snap_slo.html @@ -0,0 +1,38 @@ + + +
+ +