Prepare for v4.0.3

* fixed occasional horizontal rendering artifacts in block “holes”
(C-shaped slots and Ring holes)
* improved user editing of input slots and fixed cursor behavior (note:
the text cursor inside input slots also blinks again, as it should have)
* always export comments attached to prototype hat blocks in the block
editor along with script pic
* when first opening a block editor on a custom block definition make
it big enough to show everything (as much as fits on the screen)
* remember block editor position and dimensions for each edited custom
block definition when acknowledging (pressing OK or APPLY) for the
session
* speed up stacking of commands and loading of projects by suppressing
redundant block redraws
* introducing a “grab threshold” preference to suppress accidental
grabbing through micro-movements of the hand. This addresses the
“cannot-click-on-drop-downs-in-Chrome-under-Windows” set of bug
reports, and also the issues that arise out of accidentally dragging
off a copy of a parameter blob when trying to just click on it to
rename it
* new hidden (shift-click) option to adjust the grab threshold in the
settings menu for the current session
* expand list watchers inside result bubbles and speech/thought
balloons to show everything (as much of the list’s first level as fist
into either the scripting area for result bubbles or the stage for
speech balloons - note resizing the stage affects the size of the list
watchers inside speech balloons, i.e. making the stage bigger increases
the number of visible elements even while the balloon is showing)
* fixed a bug that make an occasional gray rectangle appear at the
mouse pointer
* added a way to invoke blocks synchronously in JavaScript - under
construction (possibly to be used for generic “When” hat blocks and
user-customizable drop-downs)
* added methods to cache morphs’ fullImage and fullBounds while dragging
* Reporters (also nested reporters) and sprite icons (in the corral)
are now semi-transparent when being dragged, so you can see possible
drop target halos /through/ them
* in “prefer empty slot drops” mode (default) it is now much harder to
drop reporters onto non-static C-slots inside custom blocks (e.g. in
FOR loops) and onto variadic input arrowheads (to replace the whole
input with an input list)
* ScriptsMorphs are now noticing transparent clicks (addresses #997)
* %interaction slots are now static, fixed #982 (it is no longer
possible to drop reporters into the input slot of a “When I am…” hat
block (never was intended that it should be possible)
* fixed ctrl-f for the block editor in all situations (thanks, Brian,
for the bug report)
dev
Jens Mönig 2015-11-16 12:18:40 +01:00
rodzic 72bf4cfb8c
commit 6061403a89
9 zmienionych plików z 400 dodań i 167 usunięć

183
blocks.js
Wyświetl plik

@ -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;

58
byob.js
Wyświetl plik

@ -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;

41
gui.js
Wyświetl plik

@ -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

Wyświetl plik

@ -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 Hands 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 doesnt cut it).
151116
------
* Blocks, GUI: Slightly less transparency for dragged reporters and sprite icons
=== v4.0.3 (unreleased) ===
++++++++++++++++++++++++++
end - bulk of 151116
++++++++++++++++++++++++++

Wyświetl plik

@ -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);
};

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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();

38
snap_slo.html 100755
Wyświetl plik

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Snap! Build Your Own Blocks</title>
<link rel="shortcut icon" href="favicon.ico">
<script type="text/javascript" src="morphic.js"></script>
<script type="text/javascript" src="widgets.js"></script>
<script type="text/javascript" src="blocks.js"></script>
<script type="text/javascript" src="threads.js"></script>
<script type="text/javascript" src="objects.js"></script>
<script type="text/javascript" src="gui.js"></script>
<script type="text/javascript" src="paint.js"></script>
<script type="text/javascript" src="lists.js"></script>
<script type="text/javascript" src="byob.js"></script>
<script type="text/javascript" src="xml.js"></script>
<script type="text/javascript" src="store.js"></script>
<script type="text/javascript" src="locale.js"></script>
<script type="text/javascript" src="cloud.js"></script>
<script type="text/javascript" src="sha512.js"></script>
<script type="text/javascript">
var world;
window.onload = function () {
world = new WorldMorph(document.getElementById('world'));
world.worldCanvas.focus();
new IDE_Morph().openIn(world);
loop();
};
function loop() {
requestAnimationFrame(loop);
world.doOneCycle();
}
</script>
</head>
<body style="margin: 0;">
<canvas id="world" tabindex="1" style="position: absolute;" />
</body>
</html>

Wyświetl plik

@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
// Global stuff ////////////////////////////////////////////////////////
modules.threads = '2015-October-02';
modules.threads = '2015-November-16';
var ThreadManager;
var Process;
@ -128,6 +128,26 @@ function snapEquals(a, b) {
return x === y;
}
function invoke(block, timeout) {
// exectue the given block synchronously, i.e. without yielding.
// if a timeout (in milliseconds) is specified, abort execution
// after the timeout has been reached and throw an error.
// For debugging purposes only.
// Caution: Kids, do not try this at home!
// use ThreadManager::startProcess instead
var proc = new Process(block),
startTime = Date.now();
while (proc.isRunning()) {
if (timeout && ((Date.now() - startTime) > timeout)) {
throw (new Error("a synchronous Snap! script has timed out"));
}
proc.runStep();
}
if (block instanceof ReporterBlockMorph) {
return proc.homeContext.inputs[0];
}
}
// ThreadManager ///////////////////////////////////////////////////////
function ThreadManager() {
@ -638,7 +658,8 @@ Process.prototype.evaluateInput = function (input) {
if (contains(
[CommandSlotMorph, ReporterSlotMorph],
input.constructor
) || (input instanceof CSlotMorph && !input.isStatic)) {
) || (input instanceof CSlotMorph &&
(!input.isStatic || input.isLambda))) {
// I know, this still needs yet to be done right....
ans = this.reify(ans, new List());
}