summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--blocks.js96
-rw-r--r--byob.js86
-rw-r--r--cloud.js15
-rw-r--r--gui.js167
-rwxr-xr-xhistory.txt62
-rw-r--r--lang-de.js10
-rwxr-xr-xlang-pt.js86
-rw-r--r--lists.js4
-rw-r--r--locale.js6
-rw-r--r--morphic.js78
-rw-r--r--objects.js425
-rw-r--r--paint.js59
-rwxr-xr-xsnap.html1
-rw-r--r--store.js4
-rw-r--r--threads.js25
15 files changed, 1004 insertions, 120 deletions
diff --git a/blocks.js b/blocks.js
index 69ae610..f02001b 100644
--- a/blocks.js
+++ b/blocks.js
@@ -155,7 +155,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
// Global stuff ////////////////////////////////////////////////////////
-modules.blocks = '2014-May-02';
+modules.blocks = '2014-July-08';
var SyntaxElementMorph;
@@ -913,17 +913,13 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
part = new InputSlotMorph(
null,
false,
- {
- /*
- color : 'color',
- fisheye : 'fisheye',
- whirl : 'whirl',
- pixelate : 'pixelate',
- mosaic : 'mosaic',
- brightness : 'brightness',
- */
- ghost : ['ghost']
- },
+ { brightness : ['brightness'],
+ ghost : ['ghost'],
+ negative : ['negative'],
+ comic: ['comic'],
+ duplicate: ['duplicate'],
+ confetti: ['confetti']
+ },
true
);
part.setContents(['ghost']);
@@ -2013,6 +2009,7 @@ BlockMorph.prototype.userMenu = function () {
var menu = new MenuMorph(this),
world = this.world(),
myself = this,
+ alternatives,
blck;
menu.addItem(
@@ -2070,6 +2067,14 @@ BlockMorph.prototype.userMenu = function () {
);
}
);
+ } else if (this.definition && this.alternatives) { // custom block
+ alternatives = this.alternatives();
+ if (alternatives.length > 0) {
+ menu.addItem(
+ 'relabel...',
+ function () {myself.relabel(alternatives); }
+ );
+ }
}
menu.addItem(
@@ -2105,6 +2110,7 @@ BlockMorph.prototype.userMenu = function () {
if (this.parentThatIsA(RingMorph)) {
menu.addLine();
menu.addItem("unringify", 'unringify');
+ menu.addItem("ringify", 'ringify');
return menu;
}
if (this.parent instanceof ReporterSlotMorph
@@ -2151,16 +2157,18 @@ BlockMorph.prototype.developersMenu = function () {
BlockMorph.prototype.hidePrimitive = function () {
var ide = this.parentThatIsA(IDE_Morph),
+ dict,
cat;
if (!ide) {return; }
StageMorph.prototype.hiddenPrimitives[this.selector] = true;
- cat = {
+ dict = {
doWarp: 'control',
reifyScript: 'operators',
reifyReporter: 'operators',
reifyPredicate: 'operators',
doDeclareVariables: 'variables'
- }[this.selector] || this.category;
+ };
+ cat = dict[this.selector] || this.category;
if (cat === 'lists') {cat = 'variables'; }
ide.flushBlocksCache(cat);
ide.refreshPalette();
@@ -2268,6 +2276,7 @@ BlockMorph.prototype.relabel = function (alternativeSelectors) {
alternativeSelectors.forEach(function (sel) {
var block = SpriteMorph.prototype.blockForSelector(sel);
block.restoreInputs(oldInputs);
+ block.fixBlockColor(null, true);
block.addShadow(new Point(3, 3));
menu.addItem(
block,
@@ -2287,7 +2296,7 @@ BlockMorph.prototype.setSelector = function (aSelector) {
var oldInputs = this.inputs(),
info;
info = SpriteMorph.prototype.blocks[aSelector];
- this.category = info.category;
+ this.setCategory(info.category);
this.selector = aSelector;
this.setSpec(localize(info.spec));
this.restoreInputs(oldInputs);
@@ -2299,6 +2308,7 @@ BlockMorph.prototype.restoreInputs = function (oldInputs) {
// try to restore my previous inputs when my spec has been changed
var i = 0,
old,
+ nb,
myself = this;
this.inputs().forEach(function (inp) {
@@ -2309,7 +2319,14 @@ BlockMorph.prototype.restoreInputs = function (oldInputs) {
// original - turns empty numberslots to 0:
// inp.setContents(old.evaluate());
// "fix" may be wrong b/c constants
- inp.setContents(old.contents().text);
+ if (old.contents) {
+ inp.setContents(old.contents().text);
+ }
+ } else if (old instanceof CSlotMorph && inp instanceof CSlotMorph) {
+ nb = old.nestedBlock();
+ if (nb) {
+ inp.nestedBlock(nb.fullCopy());
+ }
}
i += 1;
});
@@ -3330,6 +3347,10 @@ CommandBlockMorph.prototype.isStop = function () {
// CommandBlockMorph deleting
CommandBlockMorph.prototype.userDestroy = function () {
+ if (this.nextBlock()) {
+ this.userDestroyJustThis();
+ return;
+ }
var cslot = this.parentThatIsA(CSlotMorph);
this.destroy();
if (cslot) {
@@ -3337,6 +3358,37 @@ CommandBlockMorph.prototype.userDestroy = function () {
}
};
+CommandBlockMorph.prototype.userDestroyJustThis = function () {
+ // delete just this one block, reattach next block to the previous one,
+ var scripts = this.parentThatIsA(ScriptsMorph),
+ cs = this.parentThatIsA(CommandSlotMorph),
+ pb,
+ nb = this.nextBlock(),
+ above,
+ cslot = this.parentThatIsA(CSlotMorph);
+
+ if (this.parent) {
+ pb = this.parent.parentThatIsA(CommandBlockMorph);
+ }
+ if (pb && (pb.nextBlock() === this)) {
+ above = pb;
+ } else if (cs && (cs.nestedBlock() === this)) {
+ above = cs;
+ }
+ this.destroy();
+ if (nb) {
+ if (above instanceof CommandSlotMorph) {
+ above.nestedBlock(nb);
+ } else if (above instanceof CommandBlockMorph) {
+ above.nextBlock(nb);
+ } else {
+ scripts.add(nb);
+ }
+ } else if (cslot) {
+ cslot.fixLayout();
+ }
+};
+
// CommandBlockMorph drawing:
CommandBlockMorph.prototype.drawNew = function () {
@@ -4957,6 +5009,14 @@ ScriptsMorph.prototype.cleanUp = function () {
};
ScriptsMorph.prototype.exportScriptsPicture = function () {
+ var pic = this.scriptsPicture();
+ if (pic) {
+ window.open(pic.toDataURL());
+ }
+};
+
+ScriptsMorph.prototype.scriptsPicture = function () {
+ // private - answer a canvas containing the pictures of all scripts
var boundingBox, pic, ctx;
if (this.children.length === 0) {return; }
boundingBox = this.children[0].fullBounds();
@@ -4977,7 +5037,7 @@ ScriptsMorph.prototype.exportScriptsPicture = function () {
);
}
});
- window.open(pic.toDataURL());
+ return pic;
};
ScriptsMorph.prototype.addComment = function () {
@@ -6444,7 +6504,7 @@ InputSlotMorph.prototype.collidablesMenu = function () {
allNames = [];
stage.children.forEach(function (morph) {
- if (morph instanceof SpriteMorph) {
+ if (morph instanceof SpriteMorph && !morph.isClone) {
if (morph.name !== rcvr.name) {
allNames = allNames.concat(morph.name);
}
diff --git a/byob.js b/byob.js
index 6a10110..a17f976 100644
--- a/byob.js
+++ b/byob.js
@@ -106,7 +106,7 @@ SymbolMorph, isNil*/
// Global stuff ////////////////////////////////////////////////////////
-modules.byob = '2014-May-02';
+modules.byob = '2014-Jun-06';
// Declarations
@@ -336,6 +336,43 @@ CustomBlockDefinition.prototype.parseSpec = function (spec) {
return parts;
};
+// CustomBlockDefinition picturing
+
+CustomBlockDefinition.prototype.scriptsPicture = function () {
+ var scripts, proto, block, comment;
+
+ scripts = new ScriptsMorph();
+ scripts.cleanUpMargin = 10;
+ proto = new PrototypeHatBlockMorph(this);
+ proto.setPosition(scripts.position().add(10));
+ if (this.comment !== null) {
+ comment = this.comment.fullCopy();
+ proto.comment = comment;
+ comment.block = proto;
+ }
+ if (this.body !== null) {
+ proto.nextBlock(this.body.expression.fullCopy());
+ }
+ scripts.add(proto);
+ proto.fixBlockColor(null, true);
+ this.scripts.forEach(function (element) {
+ block = element.fullCopy();
+ block.setPosition(scripts.position().add(element.position()));
+ scripts.add(block);
+ if (block instanceof BlockMorph) {
+ block.allComments().forEach(function (comment) {
+ comment.align(block);
+ });
+ }
+ });
+ proto.allComments().forEach(function (comment) {
+ comment.align(proto);
+ });
+ proto.children[0].fixLayout();
+ scripts.fixMultiArgs();
+ return scripts.scriptsPicture();
+};
+
// CustomCommandBlockMorph /////////////////////////////////////////////
// CustomCommandBlockMorph inherits from CommandBlockMorph:
@@ -708,6 +745,7 @@ CustomCommandBlockMorph.prototype.userMenu = function () {
} else {
menu.addLine();
}
+
// menu.addItem("export definition...", 'exportBlockDefinition');
menu.addItem("delete block definition...", 'deleteBlockDefinition');
}
@@ -809,6 +847,44 @@ CustomCommandBlockMorph.prototype.popUpbubbleHelp = function (
).popUp(this.world(), this.rightCenter().add(new Point(-8, 0)));
};
+// CustomCommandBlockMorph relabelling
+
+CustomCommandBlockMorph.prototype.relabel = function (alternatives) {
+ var menu = new MenuMorph(this),
+ oldInputs = this.inputs().map(
+ function (each) {return each.fullCopy(); }
+ ),
+ myself = this;
+ alternatives.forEach(function (def) {
+ var block = def.blockInstance();
+ block.restoreInputs(oldInputs);
+ block.fixBlockColor(null, true);
+ block.addShadow(new Point(3, 3));
+ menu.addItem(
+ block,
+ function () {
+ myself.definition = def;
+ myself.refresh();
+ }
+ );
+ });
+ menu.popup(this.world(), this.bottomLeft().subtract(new Point(
+ 8,
+ this instanceof CommandBlockMorph ? this.corner : 0
+ )));
+};
+
+CustomCommandBlockMorph.prototype.alternatives = function () {
+ var rcvr = this.receiver(),
+ stage = rcvr.parentThatIsA(StageMorph),
+ allDefs = rcvr.customBlocks.concat(stage.globalBlocks),
+ myself = this;
+ return allDefs.filter(function (each) {
+ return each !== myself.definition &&
+ each.type === myself.definition.type;
+ });
+};
+
// CustomReporterBlockMorph ////////////////////////////////////////////
// CustomReporterBlockMorph inherits from ReporterBlockMorph:
@@ -924,6 +1000,14 @@ CustomReporterBlockMorph.prototype.bubbleHelp
CustomReporterBlockMorph.prototype.popUpbubbleHelp
= CustomCommandBlockMorph.prototype.popUpbubbleHelp;
+// CustomReporterBlockMorph relabelling
+
+CustomReporterBlockMorph.prototype.relabel
+ = CustomCommandBlockMorph.prototype.relabel;
+
+CustomReporterBlockMorph.prototype.alternatives
+ = CustomCommandBlockMorph.prototype.alternatives;
+
// JaggedBlockMorph ////////////////////////////////////////////////////
/*
diff --git a/cloud.js b/cloud.js
index 17fc76b..54189d8 100644
--- a/cloud.js
+++ b/cloud.js
@@ -27,9 +27,10 @@
// Global settings /////////////////////////////////////////////////////
-/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop*/
+/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop,
+localize*/
-modules.cloud = '2014-January-09';
+modules.cloud = '2014-May-26';
// Global stuff
@@ -107,7 +108,7 @@ Cloud.prototype.signup = function (
errorCall.call(
null,
myself.url + 'SignUp',
- 'could not connect to:'
+ localize('could not connect to:')
);
}
}
@@ -164,7 +165,7 @@ Cloud.prototype.getPublicProject = function (
errorCall.call(
null,
myself.url + 'Public',
- 'could not connect to:'
+ localize('could not connect to:')
);
}
}
@@ -217,7 +218,7 @@ Cloud.prototype.resetPassword = function (
errorCall.call(
null,
myself.url + 'ResetPW',
- 'could not connect to:'
+ localize('could not connect to:')
);
}
}
@@ -264,7 +265,7 @@ Cloud.prototype.connect = function (
errorCall.call(
null,
myself.url,
- 'could not connect to:'
+ localize('could not connect to:')
);
}
}
@@ -533,7 +534,7 @@ Cloud.prototype.callService = function (
errorCall.call(
this,
request.responseText,
- 'Service: ' + serviceName
+ localize('Service:') + ' ' + localize(serviceName)
);
return;
}
diff --git a/gui.js b/gui.js
index 0f4f2eb..1454ba3 100644
--- a/gui.js
+++ b/gui.js
@@ -64,11 +64,12 @@ standardSettings, Sound, BlockMorph, ToggleMorph, InputSlotDialogMorph,
ScriptsMorph, isNil, SymbolMorph, BlockExportDialogMorph,
BlockImportDialogMorph, SnapTranslator, localize, List, InputSlotMorph,
SnapCloud, Uint8Array, HandleMorph, SVG_Costume, fontHeight, hex_sha512,
-sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio*/
+sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio,
+SpeechBubbleMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.gui = '2014-February-13';
+modules.gui = '2014-July-08';
// Declarations
@@ -848,7 +849,7 @@ IDE_Morph.prototype.createCategories = function () {
this.add(this.categories);
};
-IDE_Morph.prototype.createPalette = function () {
+IDE_Morph.prototype.createPalette = function (forSearching) {
// assumes that the logo pane has already been created
// needs the categories pane for layout
var myself = this;
@@ -857,7 +858,15 @@ IDE_Morph.prototype.createPalette = function () {
this.palette.destroy();
}
- this.palette = this.currentSprite.palette(this.currentCategory);
+ if (forSearching) {
+ this.palette = new ScrollFrameMorph(
+ null,
+ null,
+ this.currentSprite.sliderColor
+ );
+ } else {
+ this.palette = this.currentSprite.palette(this.currentCategory);
+ }
this.palette.isDraggable = false;
this.palette.acceptsDrops = true;
this.palette.contents.acceptsDrops = false;
@@ -880,8 +889,7 @@ IDE_Morph.prototype.createPalette = function () {
this.palette.setWidth(this.logo.width());
this.add(this.palette);
- this.palette.scrollX(this.palette.padding);
- this.palette.scrollY(this.palette.padding);
+ return this.palette;
};
IDE_Morph.prototype.createStage = function () {
@@ -914,6 +922,8 @@ IDE_Morph.prototype.createSpriteBar = function () {
tabColors = this.tabColors,
tabBar = new AlignmentMorph('row', -tabCorner * 2),
tab,
+ symbols = ['\u2192', '\u21BB', '\u2194'],
+ labels = ['don\'t rotate', 'can rotate', 'only face left/right'],
myself = this;
if (this.spriteBar) {
@@ -942,17 +952,13 @@ IDE_Morph.prototype.createSpriteBar = function () {
each.refresh();
});
},
- ['\u2192', '\u21BB', '\u2194'][rotationStyle], // label
+ symbols[rotationStyle], // label
function () { // query
return myself.currentSprite instanceof SpriteMorph
&& myself.currentSprite.rotationStyle === rotationStyle;
},
null, // environment
- localize(
- [
- 'don\'t rotate', 'can rotate', 'only face left/right'
- ][rotationStyle]
- )
+ localize(labels[rotationStyle])
);
button.corner = 8;
@@ -1937,7 +1943,7 @@ IDE_Morph.prototype.cloudMenu = function () {
);
} else {
menu.addItem(
- 'Logout',
+ localize('Logout') + ' ' + SnapCloud.username,
'logout'
);
menu.addItem(
@@ -2260,36 +2266,9 @@ IDE_Morph.prototype.projectMenu = function () {
menu = new MenuMorph(this);
menu.addItem('Project notes...', 'editProjectNotes');
menu.addLine();
- menu.addItem(
- 'New',
- function () {
- myself.confirm(
- 'Replace the current project with a new one?',
- 'New Project',
- function () {
- myself.newProject();
- }
- );
- }
- );
+ menu.addItem('New', 'createNewProject');
menu.addItem('Open...', 'openProjectsBrowser');
- menu.addItem(
- 'Save',
- function () {
- if (myself.source === 'examples') {
- myself.source = 'local'; // cannot save to examples
- }
- if (myself.projectName) {
- if (myself.source === 'local') { // as well as 'examples'
- myself.saveProject(myself.projectName);
- } else { // 'cloud'
- myself.saveProjectToCloud(myself.projectName);
- }
- } else {
- myself.saveProjectsBrowser();
- }
- }
- );
+ menu.addItem('Save', "save");
if (shiftClicked) {
menu.addItem(
'Save to disk',
@@ -2356,6 +2335,15 @@ IDE_Morph.prototype.projectMenu = function () {
'show global custom block definitions as XML\nin a new browser window'
);
+ if (shiftClicked) {
+ menu.addItem(
+ 'Export all scripts as pic...',
+ function () {myself.exportScriptsPicture(); },
+ 'show a picture of all scripts\nand block definitions',
+ new Color(100, 0, 0)
+ );
+ }
+
menu.addLine();
menu.addItem(
'Import tools',
@@ -2532,6 +2520,7 @@ IDE_Morph.prototype.aboutSnap = function () {
+ '\n\nNathan Dinsmore: Saving/Loading, Snap-Logo Design, '
+ 'countless bugfixes'
+ '\nKartik Chandra: Paint Editor'
+ + '\nYuan Yuan: Graphic Effects'
+ '\nIan Reynolds: UI Design, Event Bindings, '
+ 'Sound primitives'
+ '\nIvan Motyashov: Initial Squeak Porting'
@@ -2720,6 +2709,22 @@ IDE_Morph.prototype.newProject = function () {
this.fixLayout();
};
+IDE_Morph.prototype.save = function () {
+ if (this.source === 'examples') {
+ this.source = 'local'; // cannot save to examples
+ }
+ if (this.projectName) {
+ if (this.source === 'local') { // as well as 'examples'
+ this.saveProject(this.projectName);
+ } else { // 'cloud'
+ this.saveProjectToCloud(this.projectName);
+ }
+ } else {
+ this.saveProjectsBrowser();
+ }
+};
+
+
IDE_Morph.prototype.saveProject = function (name) {
var myself = this;
this.nextSteps([
@@ -2837,6 +2842,56 @@ IDE_Morph.prototype.exportSprite = function (sprite) {
+ '</sprites>');
};
+IDE_Morph.prototype.exportScriptsPicture = function () {
+ var pics = [],
+ pic,
+ padding = 20,
+ w = 0,
+ h = 0,
+ y = 0,
+ ctx;
+
+ // collect all script pics
+ this.sprites.asArray().forEach(function (sprite) {
+ pics.push(sprite.image);
+ pics.push(sprite.scripts.scriptsPicture());
+ sprite.customBlocks.forEach(function (def) {
+ pics.push(def.scriptsPicture());
+ });
+ });
+ pics.push(this.stage.image);
+ pics.push(this.stage.scripts.scriptsPicture());
+ this.stage.customBlocks.forEach(function (def) {
+ pics.push(def.scriptsPicture());
+ });
+
+ // collect global block pics
+ this.stage.globalBlocks.forEach(function (def) {
+ pics.push(def.scriptsPicture());
+ });
+
+ pics = pics.filter(function (each) {return !isNil(each); });
+
+ // determine dimensions of composite
+ pics.forEach(function (each) {
+ w = Math.max(w, each.width);
+ h += (each.height);
+ h += padding;
+ });
+ h -= padding;
+ pic = newCanvas(new Point(w, h));
+ ctx = pic.getContext('2d');
+
+ // draw all parts
+ pics.forEach(function (each) {
+ ctx.drawImage(each, 0, y);
+ y += padding;
+ y += each.height;
+ });
+
+ window.open(pic.toDataURL());
+};
+
IDE_Morph.prototype.openProjectString = function (str) {
var msg,
myself = this;
@@ -3294,6 +3349,15 @@ IDE_Morph.prototype.toggleStageSize = function (isSmall) {
}
};
+IDE_Morph.prototype.createNewProject = function () {
+ var myself = this;
+ this.confirm(
+ 'Replace the current project with a new one?',
+ 'New Project',
+ function () {myself.newProject(); }
+ );
+};
+
IDE_Morph.prototype.openProjectsBrowser = function () {
new ProjectDialogMorph(this, 'open').popUp();
};
@@ -4524,6 +4588,17 @@ ProjectDialogMorph.prototype.installCloudProjectList = function (pl) {
myself.preview.texture = item.Thumbnail || null;
myself.preview.cachedTexture = null;
myself.preview.drawNew();
+ (new SpeechBubbleMorph(new TextMorph(
+ localize('last changed') + '\n' + item.Updated,
+ null,
+ null,
+ null,
+ null,
+ 'center'
+ ))).popUp(
+ myself.world(),
+ myself.preview.rightCenter().add(new Point(2, 0))
+ );
}
if (item.Public === 'true') {
myself.shareButton.hide();
@@ -4755,9 +4830,13 @@ ProjectDialogMorph.prototype.shareProject = function () {
function () {
SnapCloud.disconnect();
proj.Public = 'true';
+ myself.unshareButton.show();
+ myself.shareButton.hide();
entry.label.isBold = true;
entry.label.drawNew();
entry.label.changed();
+ myself.buttons.fixLayout();
+ myself.drawNew();
myself.ide.showMessage('shared.', 2);
},
myself.ide.cloudError(),
@@ -4792,9 +4871,13 @@ ProjectDialogMorph.prototype.unshareProject = function () {
function () {
SnapCloud.disconnect();
proj.Public = 'false';
+ myself.shareButton.show();
+ myself.unshareButton.hide();
entry.label.isBold = false;
entry.label.drawNew();
entry.label.changed();
+ myself.buttons.fixLayout();
+ myself.drawNew();
myself.ide.showMessage('unshared.', 2);
},
myself.ide.cloudError(),
diff --git a/history.txt b/history.txt
index af54766..29d71ec 100755
--- a/history.txt
+++ b/history.txt
@@ -2125,3 +2125,65 @@ ______
------
* error message when trying to import a non-text file into a variable, thanks, Nate!
* fixed #407 (custom-block coloring w/ zebra off)
+
+140520
+------
+* Morphic: Prevent default action for ctrl-/cmd-key event
+* Snap.html: Focus the world canvas on startup, so Snap reacts to keyboard events right away
+* Threads: new Variable data structure, for refactoring upvar references, not yet used anywhere
+* Objects, GUI: Search Blocks, feature. Thanks, Kyle, for architecting and designing this!!!
+* Objects, GUI: Keyboard-shortcuts for opening (cmd-o), saving (cmd-s) projects and for finding blocks (cmd-f)
+
+140526
+------
+* Objects: Fixed #445 (minor search + zoom issues)
+* Localization additions and Portuguese translation update, thanks, Manuel!
+* GUI, cloud: Show last-changed-timestamp when opening cloud projects
+
+140604
+------
+* Blocks: refactor “script pics” feature
+* BYOB: new scriptsPicture() method for custom block definitions
+* GUI: new (hidden) feature: “Export all scripts as pic” (including custom block refs)
+* Graphic effects!!! Yay, thanks, Yuan!
+* Bug fixes from Nathan, yay, thanks, Nathan!!
+* German translation update
+* Paint Editor transforms, yay, thanks, Kartik!!
+
+140605
+------
+* Objects: stop replacing the empty string with the number zero in watchers
+* Threads: initialize new variables with zero (instead of null)
+* Objects: fixed #465
+* Objects: fixed #457
+
+140605
+------
+* Objects: gracefully hide & show the stage, fixed #281
+* Objects: add hide and show blocks to the stage’s “looks” category
+* Objects: added more relabelling options to SAY and THINK variants
+* Blocks, objects: enable relabelling blocks with C-Slots
+* Blocks: enable relabelling blocks across categories
+* Objects: more relabelling options for SAY, THINK, ASK
+* BYOB, Blocks: relabelling custom blocks (experimental)
+
+140623
+------
+* Morphic: Inspector enhancements (dynamic property update, keyboard shortcuts)
+* GUI: update visibility of share/unshare buttons, Thanks, Kunal!
+
+140706
+------
+* Blocks: add “ringify” to every context menu that already has “unringify”
+
+
+140708
+------
+* Threads: show error messages for custom blocks (propagating to the script’s top block)
+* Threads: adjust to Doug Crockford’s latest infuriating nitpickings in JSLint
+* GUI: show username in ‘logout’ entry of cloud menu
+* GUI, Objects: fixed scrolling glitch in the palette, thanks, Kunal!
+* GUI, Objects: add keyboard shortcut for “new project”: ctr-n
+* revert changes made for JSLint’s sake after the issue was fixed in JSLint
+* Blocks: change “delete” behavior in context menus to only delete this particular blocks (and reconnect the next block to the previous one)
+* fixed #490
diff --git a/lang-de.js b/lang-de.js
index 9ce0c50..0a62da0 100644
--- a/lang-de.js
+++ b/lang-de.js
@@ -185,7 +185,7 @@ SnapTranslator.dict.de = {
'translator_e-mail':
'jens@moenig.org', // optional
'last_changed':
- '2014-02-13', // this, too, will appear in the Translators tab
+ '2014-06-04', // this, too, will appear in the Translators tab
// GUI
// control bar:
@@ -1122,8 +1122,16 @@ SnapTranslator.dict.de = {
'Leer',
// graphical effects
+ 'brightness':
+ 'Helligeit',
'ghost':
'Durchsichtigkeit',
+ 'negative':
+ 'Farbumkehr',
+ 'comic':
+ 'Moire',
+ 'confetti':
+ 'Farbverschiebung',
// keys
'space':
diff --git a/lang-pt.js b/lang-pt.js
index c5e33b0..d28c290 100755
--- a/lang-pt.js
+++ b/lang-pt.js
@@ -185,7 +185,7 @@ SnapTranslator.dict.pt = {
'translator_e-mail':
'mmsequeira@gmail.com',
'last_changed':
- '2014-01-12',
+ '2014-05-26',
// GUI
// control bar:
@@ -685,6 +685,16 @@ SnapTranslator.dict.pt = {
'Língua…',
'Zoom blocks...':
'Ampliação dos blocos…',
+ 'Stage size...':
+ 'Tamanho do palco…',
+ 'Stage size':
+ 'Tamanho do palco',
+ 'Stage width':
+ 'Largura do palco',
+ 'Stage height':
+ 'Altura do palco',
+ 'Default':
+ 'Normal',
'Blurred shadows':
'Sombras desfocadas',
'uncheck to use solid drop\nshadows and highlights':
@@ -767,6 +777,12 @@ SnapTranslator.dict.pt = {
'Desassinalar para aumentar a velocidade\npermitindo ritmos variáveis das tramas.',
'check for smooth, predictable\nanimations across computers':
'Assinalar para obter animações mais suaves\ne previsíveis de computador para computador.',
+ 'Flat line ends':
+ 'Extremos das linhas planos',
+ 'check for flat ends of lines':
+ 'Assinalar para que os extremos das linhas\ndesenhadas pela caneta sejam planos.',
+ 'uncheck for round ends of lines':
+ 'Desassinalar para que os extremos das linhas\ndesenhadas pela caneta sejam redondos.',
// entradas
'with inputs':
@@ -1106,8 +1122,16 @@ SnapTranslator.dict.pt = {
'vazio',
// efeitos gráficos
+ 'brightness':
+ 'brilho',
'ghost':
'fantasma',
+ 'negative':
+ 'negativo',
+ 'comic':
+ 'ondeado',
+ 'confetti':
+ 'cor',
// teclas
'space':
@@ -1254,20 +1278,74 @@ SnapTranslator.dict.pt = {
'um item ao acaso',
// em falta no ficheiro lang-de.js
+ 'grow':
+ 'aumentar',
+ 'shrink':
+ 'reduzir',
+ 'flip ↔':
+ 'inverter ↔',
+ 'flip ↕':
+ 'inverter ↕',
+ 'Export all scripts as pic...':
+ 'Exportar todos os guiões como fotografia…',
+ 'show a picture of all scripts\nand block definitions':
+ 'Mostra uma imagem com todos\nos guiões e definições de blocos',
+ 'current %dates':
+ '%dates corrente',
+ 'year':
+ 'ano',
+ 'month':
+ 'mês',
+ 'date':
+ 'dia',
+ 'day of week':
+ 'dia da semana',
+ 'hour':
+ 'hora',
+ 'minute':
+ 'minuto',
+ 'second':
+ 'segundo',
+ 'time in milliseconds':
+ 'tempo (em milisegundos)',
+ 'find blocks...':
+ 'procurar blocos…',
'costume name':
'o nome do traje',
'Open':
'Abrir',
'Share':
'Partilhar',
+ 'Snap!Cloud':
+ 'Snap!Nuvem',
'Cloud':
'Nuvem',
+ 'could not connect to:':
+ 'Não foi possível ligar a:',
+ 'Service:':
+ 'Serviço:',
+ 'login':
+ 'autenticação',
+ 'ERROR: INVALID PASSWORD':
+ 'ERRO: PALAVRA-PASSE INVÁLIDA',
'Browser':
'Navegador',
'Sign up':
'Registar nova conta',
+ 'Signup':
+ 'Registo de nova conta',
'Sign in':
'Entrar',
+ 'Logout':
+ 'Sair',
+ 'Change Password...':
+ 'Alterar palavra-passe…',
+ 'Change Password':
+ 'Alterar palavra-passe',
+ 'Account created.':
+ 'Conta criada.',
+ 'An e-mail with your password\nhas been sent to the address provided':
+ 'Foi enviada uma mensagem para\no endereço disponibilizado\ncontendo a sua palavra-passe.',
'now connected.':
'entrou.',
'disconnected.':
@@ -1280,6 +1358,12 @@ SnapTranslator.dict.pt = {
'Nome de utilizador:',
'Password:':
'Palavra-passe:',
+ 'Old password:':
+ 'Palavra-passe actual:',
+ 'New password:':
+ 'Nova palavra-passe:',
+ 'Repeat new password:':
+ 'Repita a nova palavra-passe:',
'Birth date:':
'Data de nascimento:',
'January':
diff --git a/lists.js b/lists.js
index 3166bf1..ea553b0 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 = '2014-January-09';
+modules.lists = '2014-Jun-04';
var List;
var ListWatcherMorph;
@@ -305,7 +305,7 @@ List.prototype.equalTo = function (other) {
if (this.length() !== other.length()) {
return false;
}
- for (i = 0; i < this.length(); i += 1) {
+ for (i = 1; i <= this.length(); i += 1) {
if (!snapEquals(this.at(i), other.at(i))) {
return false;
}
diff --git a/locale.js b/locale.js
index d981960..614b38c 100644
--- a/locale.js
+++ b/locale.js
@@ -42,7 +42,7 @@
/*global modules, contains*/
-modules.locale = '2014-May-02';
+modules.locale = '2014-Jun-04';
// Global stuff
@@ -149,7 +149,7 @@ SnapTranslator.dict.de = {
'translator_e-mail':
'jens@moenig.org',
'last_changed':
- '2014-02-13'
+ '2014-06-04'
};
SnapTranslator.dict.it = {
@@ -209,7 +209,7 @@ SnapTranslator.dict.pt = {
'translator_e-mail':
'mmsequeira@gmail.com',
'last_changed':
- '2014-01-12'
+ '2014-05-26'
};
SnapTranslator.dict.cs = {
diff --git a/morphic.js b/morphic.js
index b55b180..3aa5972 100644
--- a/morphic.js
+++ b/morphic.js
@@ -1035,7 +1035,7 @@
/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio,
FileList, getBlurredShadowSupport*/
-var morphicVersion = '2014-February-03';
+var morphicVersion = '2014-June-23';
var modules = {}; // keep track of additional loaded modules
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
@@ -4761,8 +4761,17 @@ CursorMorph.prototype.ctrl = function (aChar) {
this.insert(']');
} else if (aChar === 64) {
this.insert('@');
+ } else if (!isNil(this.target.receiver)) {
+ if (aChar === 68) {
+ this.target.doIt();
+ } else if (aChar === 73) {
+ this.target.inspectIt();
+ } else if (aChar === 80) {
+ this.target.showIt();
+ }
}
+
};
CursorMorph.prototype.cmd = function (aChar) {
@@ -4770,6 +4779,14 @@ CursorMorph.prototype.cmd = function (aChar) {
this.target.selectAll();
} else if (aChar === 90) {
this.undo();
+ } else if (!isNil(this.target.receiver)) {
+ if (aChar === 68) {
+ this.target.doIt();
+ } else if (aChar === 73) {
+ this.target.inspectIt();
+ } else if (aChar === 80) {
+ this.target.showIt();
+ }
}
};
@@ -6157,6 +6174,7 @@ InspectorMorph.prototype.init = function (target) {
this.edge = MorphicPreferences.isFlat ? 1 : 5;
this.color = new Color(60, 60, 60);
this.borderColor = new Color(95, 95, 95);
+ this.fps = 25;
this.drawNew();
// panes:
@@ -6181,6 +6199,28 @@ InspectorMorph.prototype.setTarget = function (target) {
this.buildPanes();
};
+InspectorMorph.prototype.updateCurrentSelection = function () {
+ var val, txt, cnts,
+ sel = this.list.selected;
+
+ if (isNil(sel)) {return; }
+ val = this.target[sel];
+ this.currentProperty = val;
+ if (isNil(val)) {
+ txt = 'NULL';
+ } else if (isString(val)) {
+ txt = val;
+ } else {
+ txt = val.toString();
+ }
+ if (this.detail.contents.children[0].text === txt) {return; }
+ cnts = new TextMorph(txt);
+ cnts.isEditable = true;
+ cnts.enableSelecting();
+ cnts.setReceiver(this.target);
+ this.detail.setContents(cnts);
+};
+
InspectorMorph.prototype.buildPanes = function () {
var attribs = [], property, myself = this, ctrl, ev, doubleClickAction;
@@ -6248,23 +6288,8 @@ InspectorMorph.prototype.buildPanes = function () {
doubleClickAction
);
- this.list.action = function (selected) {
- var val, txt, cnts;
- if (selected === undefined) {return; }
- val = myself.target[selected];
- myself.currentProperty = val;
- if (val === null) {
- txt = 'NULL';
- } else if (isString(val)) {
- txt = val;
- } else {
- txt = val.toString();
- }
- cnts = new TextMorph(txt);
- cnts.isEditable = true;
- cnts.enableSelecting();
- cnts.setReceiver(myself.target);
- myself.detail.setContents(cnts);
+ this.list.action = function () {
+ myself.updateCurrentSelection();
};
this.list.hBar.alpha = 0.6;
@@ -6582,6 +6607,17 @@ InspectorMorph.prototype.removeProperty = function () {
}
};
+// InspectorMorph stepping
+
+InspectorMorph.prototype.step = function () {
+ this.updateCurrentSelection();
+ var lbl = this.target.toString();
+ if (this.label.text === lbl) {return; }
+ this.label.text = lbl;
+ this.label.drawNew();
+ this.fixLayout();
+};
+
// MenuMorph ///////////////////////////////////////////////////////////
// MenuMorph: referenced constructors
@@ -7924,7 +7960,7 @@ TextMorph.prototype.inspectIt = function () {
var result = this.receiver.evaluateString(this.selection()),
world = this.world(),
inspector;
- if (result !== null) {
+ if (isObject(result)) {
inspector = new InspectorMorph(result);
inspector.setPosition(world.hand.position());
inspector.keepWithin(world);
@@ -9046,6 +9082,7 @@ ListMorph.prototype.buildListContents = function () {
};
ListMorph.prototype.select = function (item, trigger) {
+ if (isNil(item)) {return; }
this.selected = item;
this.active = trigger;
if (this.action) {
@@ -10268,6 +10305,9 @@ WorldMorph.prototype.initEventListeners = function () {
}
event.preventDefault();
}
+ if (event.ctrlKey || event.metaKey) {
+ event.preventDefault();
+ }
},
false
);
diff --git a/objects.js b/objects.js
index d1e99ff..1a9bb63 100644
--- a/objects.js
+++ b/objects.js
@@ -61,6 +61,7 @@
sound handling
Achal Dave contributed research and prototyping for creating music
using the Web Audio API
+ Yuan Yuan contributed graphic effects for costumes
*/
@@ -124,7 +125,7 @@ PrototypeHatBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.objects = '2014-May-02';
+modules.objects = '2014-July-08';
var SpriteMorph;
var StageMorph;
@@ -203,90 +204,106 @@ SpriteMorph.prototype.initBlocks = function () {
// Motion
forward: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'move %n steps',
defaults: [10]
},
turn: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'turn %clockwise %n degrees',
defaults: [15]
},
turnLeft: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'turn %counterclockwise %n degrees',
defaults: [15]
},
setHeading: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'point in direction %dir'
},
doFaceTowards: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'point towards %dst'
},
gotoXY: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'go to x: %n y: %n',
defaults: [0, 0]
},
doGotoObject: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'go to %dst'
},
doGlide: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'glide %n secs to x: %n y: %n',
defaults: [1, 0, 0]
},
changeXPosition: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'change x by %n',
defaults: [10]
},
setXPosition: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'set x to %n',
defaults: [0]
},
changeYPosition: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'change y by %n',
defaults: [10]
},
setYPosition: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'set y to %n',
defaults: [0]
},
bounceOffEdge: {
+ only: SpriteMorph,
type: 'command',
category: 'motion',
spec: 'if on edge, bounce'
},
xPosition: {
+ only: SpriteMorph,
type: 'reporter',
category: 'motion',
spec: 'x position'
},
yPosition: {
+ only: SpriteMorph,
type: 'reporter',
category: 'motion',
spec: 'y position'
},
direction: {
+ only: SpriteMorph,
type: 'reporter',
category: 'motion',
spec: 'direction'
@@ -309,24 +326,28 @@ SpriteMorph.prototype.initBlocks = function () {
spec: 'costume #'
},
doSayFor: {
+ only: SpriteMorph,
type: 'command',
category: 'looks',
spec: 'say %s for %n secs',
defaults: [localize('Hello!'), 2]
},
bubble: {
+ only: SpriteMorph,
type: 'command',
category: 'looks',
spec: 'say %s',
defaults: [localize('Hello!')]
},
doThinkFor: {
+ only: SpriteMorph,
type: 'command',
category: 'looks',
spec: 'think %s for %n secs',
defaults: [localize('Hmm...'), 2]
},
doThink: {
+ only: SpriteMorph,
type: 'command',
category: 'looks',
spec: 'think %s',
@@ -350,38 +371,45 @@ SpriteMorph.prototype.initBlocks = function () {
spec: 'clear graphic effects'
},
changeScale: {
+ only: SpriteMorph,
type: 'command',
category: 'looks',
spec: 'change size by %n',
defaults: [10]
},
setScale: {
+ only: SpriteMorph,
type: 'command',
category: 'looks',
spec: 'set size to %n %',
defaults: [100]
},
getScale: {
+ only: SpriteMorph,
type: 'reporter',
category: 'looks',
spec: 'size'
},
show: {
+ only: SpriteMorph,
type: 'command',
category: 'looks',
spec: 'show'
},
hide: {
+ only: SpriteMorph,
type: 'command',
category: 'looks',
spec: 'hide'
},
comeToFront: {
+ only: SpriteMorph,
type: 'command',
category: 'looks',
spec: 'go to front'
},
goBack: {
+ only: SpriteMorph,
type: 'command',
category: 'looks',
spec: 'go back %n layers',
@@ -390,17 +418,20 @@ SpriteMorph.prototype.initBlocks = function () {
// Looks - Debugging primitives for development mode
reportCostumes: {
+ dev: true,
type: 'reporter',
category: 'looks',
spec: 'wardrobe'
},
alert: {
+ dev: true,
type: 'command',
category: 'looks',
spec: 'alert %mult%s'
},
log: {
+ dev: true,
type: 'command',
category: 'looks',
spec: 'console log %mult%s'
@@ -454,6 +485,7 @@ SpriteMorph.prototype.initBlocks = function () {
// Sound - Debugging primitives for development mode
reportSounds: {
+ dev: true,
type: 'reporter',
category: 'sound',
spec: 'jukebox'
@@ -466,57 +498,67 @@ SpriteMorph.prototype.initBlocks = function () {
spec: 'clear'
},
down: {
+ only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'pen down'
},
up: {
+ only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'pen up'
},
setColor: {
+ only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'set pen color to %clr'
},
changeHue: {
+ only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'change pen color by %n',
defaults: [10]
},
setHue: {
+ only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'set pen color to %n',
defaults: [0]
},
changeBrightness: {
+ only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'change pen shade by %n',
defaults: [10]
},
setBrightness: {
+ only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'set pen shade to %n',
defaults: [100]
},
changeSize: {
+ only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'change pen size by %n',
defaults: [1]
},
setSize: {
+ only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'set pen size to %n',
defaults: [1]
},
doStamp: {
+ only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'stamp'
@@ -710,31 +752,37 @@ SpriteMorph.prototype.initBlocks = function () {
// Sensing
reportTouchingObject: {
+ only: SpriteMorph,
type: 'predicate',
category: 'sensing',
spec: 'touching %col ?'
},
reportTouchingColor: {
+ only: SpriteMorph,
type: 'predicate',
category: 'sensing',
spec: 'touching %clr ?'
},
reportColorIsTouchingColor: {
+ only: SpriteMorph,
type: 'predicate',
category: 'sensing',
spec: 'color %clr is touching %clr ?'
},
colorFiltered: {
+ dev: true,
type: 'reporter',
category: 'sensing',
spec: 'filtered for %clr'
},
reportStackSize: {
+ dev: true,
type: 'reporter',
category: 'sensing',
spec: 'stack size'
},
reportFrameCount: {
+ dev: true,
type: 'reporter',
category: 'sensing',
spec: 'frames'
@@ -746,6 +794,7 @@ SpriteMorph.prototype.initBlocks = function () {
defaults: [localize('what\'s your name?')]
},
reportLastAnswer: { // retained for legacy compatibility
+ dev: true,
type: 'reporter',
category: 'sensing',
spec: 'answer'
@@ -786,6 +835,7 @@ SpriteMorph.prototype.initBlocks = function () {
spec: 'reset timer'
},
reportTimer: { // retained for legacy compatibility
+ dev: true,
type: 'reporter',
category: 'sensing',
spec: 'timer'
@@ -969,12 +1019,14 @@ SpriteMorph.prototype.initBlocks = function () {
defaults: [localize('hello') + ' ' + localize('world'), " "]
},
reportTypeOf: { // only in dev mode for debugging
+ dev: true,
type: 'reporter',
category: 'operators',
spec: 'type of %s',
defaults: [5]
},
reportTextFunction: { // only in dev mode - experimental
+ dev: true,
type: 'reporter',
category: 'operators',
spec: '%txtfun of %s',
@@ -1083,6 +1135,7 @@ SpriteMorph.prototype.initBlocks = function () {
// MAP - experimental
reportMap: {
+ dev: true,
type: 'reporter',
category: 'lists',
spec: 'map %repRing over %l'
@@ -1146,10 +1199,10 @@ SpriteMorph.prototype.blockAlternatives = {
yPosition: ['xPosition'],
// looks:
- doSayFor: ['doThinkFor'],
- doThinkFor: ['doSayFor'],
- bubble: ['doThink'],
- doThink: ['bubble'],
+ doSayFor: ['doThinkFor', 'bubble', 'doThink', 'doAsk'],
+ doThinkFor: ['doSayFor', 'doThink', 'bubble', 'doAsk'],
+ bubble: ['doThink', 'doAsk', 'doSayFor', 'doThinkFor'],
+ doThink: ['bubble', 'doAsk', 'doSayFor', 'doThinkFor'],
show: ['hide'],
hide: ['show'],
changeEffect: ['setEffect'],
@@ -1180,8 +1233,13 @@ SpriteMorph.prototype.blockAlternatives = {
receiveClick: ['receiveGo'],
doBroadcast: ['doBroadcastAndWait'],
doBroadcastAndWait: ['doBroadcast'],
+ doIf: ['doIfElse', 'doUntil'],
+ doIfElse: ['doIf', 'doUntil'],
+ doRepeat: ['doUntil'],
+ doUntil: ['doRepeat', 'doIf'],
// sensing:
+ doAsk: ['bubble', 'doThink', 'doSayFor', 'doThinkFor'],
getLastAnswer: ['getTimer'],
getTimer: ['getLastAnswer'],
reportMouseX: ['reportMouseY'],
@@ -1240,6 +1298,18 @@ SpriteMorph.prototype.init = function (globals) {
this.idx = 0; // not to be serialized (!) - used for de-serialization
this.wasWarped = false; // not to be serialized, used for fast-tracking
+ this.graphicsValues = { 'negative': 0,
+ 'fisheye': 0,
+ 'whirl': 0,
+ 'pixelate': 0,
+ 'mosaic': 0,
+ 'brightness': 0,
+ 'color': 0,
+ 'comic': 0,
+ 'duplicate': 0,
+ 'confetti': 0
+ };
+
SpriteMorph.uber.init.call(this);
this.isDraggable = true;
@@ -1369,6 +1439,9 @@ SpriteMorph.prototype.drawNew = function () {
ctx.rotate(radians(facing - 90));
ctx.drawImage(pic.contents, 0, 0);
+ // apply graphics effects to image
+ this.image = this.applyGraphicsEffects(this.image);
+
// adjust my position to the rotation
this.setCenter(currentCenter, true); // just me
@@ -1391,6 +1464,7 @@ SpriteMorph.prototype.drawNew = function () {
this.setCenter(currentCenter, true); // just me
SpriteMorph.uber.drawNew.call(this, facing);
this.rotationOffset = this.extent().divideBy(2);
+ this.image = this.applyGraphicsEffects(this.image);
if (isLoadingCostume) { // retry until costume is done loading
cst = this.costume;
handle = setInterval(
@@ -2068,6 +2142,7 @@ SpriteMorph.prototype.freshPalette = function (category) {
});
}
+ menu.addItem('find blocks...', function () {myself.searchBlocks(); });
if (canHidePrimitives()) {
menu.addItem(
'hide primitives',
@@ -2195,10 +2270,146 @@ SpriteMorph.prototype.freshPalette = function (category) {
}
});
+ //layout
+
+ palette.scrollX(palette.padding);
+ palette.scrollY(palette.padding);
+
Morph.prototype.trackChanges = oldFlag;
return palette;
};
+// SpriteMorph blocks searching
+
+SpriteMorph.prototype.blocksMatching = function (searchString, strictly) {
+ // answer an array of block templates whose spec contains
+ // the given search string, ordered by descending relevance
+ var blocks = [],
+ blocksDict,
+ myself = this,
+ search = searchString.toLowerCase(),
+ stage = this.parentThatIsA(StageMorph);
+
+ function labelOf(aBlockSpec) {
+ var words = (BlockMorph.prototype.parseSpec(aBlockSpec)),
+ filtered = words.filter(
+ function (each) {return (each.indexOf('%') !== 0); }
+ );
+ return filtered.join(' ');
+ }
+
+ function fillDigits(anInt, totalDigits, fillChar) {
+ var ans = String(anInt);
+ while (ans.length < totalDigits) {ans = fillChar + ans; }
+ return ans;
+ }
+
+ function relevance(aBlockLabel, aSearchString) {
+ var lbl = ' ' + aBlockLabel,
+ idx = lbl.indexOf(aSearchString),
+ atWord;
+ if (idx === -1) {return -1; }
+ atWord = (lbl.charAt(idx - 1) === ' ');
+ if (strictly && !atWord) {return -1; }
+ return (atWord ? '1' : '2') + fillDigits(idx, 4, '0');
+ }
+
+ function primitive(selector) {
+ var newBlock = SpriteMorph.prototype.blockForSelector(selector, true);
+ newBlock.isTemplate = true;
+ return newBlock;
+ }
+
+ // custom blocks
+ [this.customBlocks, stage.globalBlocks].forEach(function (blocksList) {
+ blocksList.forEach(function (definition) {
+ var spec = localize(definition.blockSpec()).toLowerCase(),
+ rel = relevance(labelOf(spec), search);
+ if (rel !== -1) {
+ blocks.push([definition.templateInstance(), rel + '1']);
+ }
+ });
+ });
+ // primitives
+ blocksDict = SpriteMorph.prototype.blocks;
+ Object.keys(blocksDict).forEach(function (selector) {
+ if (!StageMorph.prototype.hiddenPrimitives[selector]) {
+ var block = blocksDict[selector],
+ spec = localize(block.spec).toLowerCase(),
+ rel = relevance(labelOf(spec), search);
+ if (
+ (rel !== -1) &&
+ (!block.dev) &&
+ (!block.only || (block.only === myself.constructor))
+ ) {
+ blocks.push([primitive(selector), rel + '2']);
+ }
+ }
+ });
+ blocks.sort(function (x, y) {return x[1] < y[1] ? -1 : 1; });
+ return blocks.map(function (each) {return each[0]; });
+};
+
+SpriteMorph.prototype.searchBlocks = function () {
+ var myself = this,
+ unit = SyntaxElementMorph.prototype.fontSize,
+ ide = this.parentThatIsA(IDE_Morph),
+ oldSearch = '',
+ searchBar = new InputFieldMorph(''),
+ searchPane = ide.createPalette('forSearch');
+
+ function show(blocks) {
+ var oldFlag = Morph.prototype.trackChanges,
+ x = searchPane.contents.left() + 5,
+ y = (searchBar.bottom() + unit);
+ Morph.prototype.trackChanges = false;
+ searchPane.contents.children = [searchPane.contents.children[0]];
+ blocks.forEach(function (block) {
+ block.setPosition(new Point(x, y));
+ searchPane.addContents(block);
+ y += block.height();
+ y += unit * 0.3;
+ });
+ Morph.prototype.trackChanges = oldFlag;
+ searchPane.changed();
+ }
+
+ searchPane.owner = this;
+ searchPane.color = myself.paletteColor;
+ searchPane.contents.color = myself.paletteColor;
+ searchPane.addContents(searchBar);
+ searchBar.drawNew();
+ searchBar.setWidth(ide.logo.width() - 30);
+ searchBar.contrast = 90;
+ searchBar.setPosition(
+ searchPane.contents.topLeft().add(new Point(10, 10))
+ );
+ searchBar.drawNew();
+
+ searchPane.accept = function () {
+ var search = searchBar.getValue();
+ if (search.length > 0) {
+ show(myself.blocksMatching(search));
+ }
+ };
+
+ searchPane.reactToKeystroke = function () {
+ var search = searchBar.getValue();
+ if (search !== oldSearch) {
+ oldSearch = search;
+ show(myself.blocksMatching(search, search.length < 2));
+ }
+ };
+
+ searchBar.cancel = function () {
+ ide.refreshPalette();
+ ide.palette.adjustScrollBars();
+ };
+
+ ide.fixLayout('refreshPalette');
+ searchBar.edit();
+};
+
// SpriteMorph variable management
SpriteMorph.prototype.addVariable = function (name, isGlobal) {
@@ -2647,14 +2858,132 @@ SpriteMorph.prototype.changeScale = function (delta) {
this.setScale(this.getScale() + (+delta || 0));
};
-// SpriteMorph graphic effects
+// Spritemorph graphic effects
+
+SpriteMorph.prototype.graphicsChanged = function () {
+ var myself = this;
+ return Object.keys(this.graphicsValues).some(
+ function (any) {
+ return myself.graphicsValues[any] < 0 ||
+ myself.graphicsValues[any] > 0;
+ }
+ );
+};
+
+SpriteMorph.prototype.applyGraphicsEffects = function (canvas) {
+// For every effect: apply transform of that effect(canvas, stored value)
+// The future: write more effects here
+ var ctx, imagedata, pixels, newimagedata;
+
+ function transform_negative(p, value) {
+ var i, rcom, gcom, bcom;
+ if (value !== 0) {
+ for (i = 0; i < p.length; i += 4) {
+ rcom = 255 - p[i];
+ gcom = 255 - p[i + 1];
+ bcom = 255 - p[i + 2];
+
+ if (p[i] < rcom) { //compare to the complement
+ p[i] += value;
+ } else if (p[i] > rcom) {
+ p[i] -= value;
+ }
+ if (p[i + 1] < gcom) {
+ p[i + 1] += value;
+ } else if (p[i + 1] > gcom) {
+ p[i + 1] -= value;
+ }
+ if (p[i + 2] < bcom) {
+ p[i + 2] += value;
+ } else if (p[i + 2] > bcom) {
+ p[i + 2] -= value;
+ }
+ }
+ }
+ return p;
+ }
+
+ function transform_brightness(p, value) {
+ var i;
+ if (value !== 0) {
+ for (i = 0; i < p.length; i += 4) {
+ p[i] += value; //255 = 100% of this color
+ p[i + 1] += value;
+ p[i + 2] += value;
+ }
+ }
+ return p;
+ }
+
+ function transform_comic(p, value) {
+ var i;
+ if (value !== 0) {
+ for (i = 0; i < p.length; i += 4) {
+ p[i] += Math.sin(i * value) * 127 + 128;
+ p[i + 1] += Math.sin(i * value) * 127 + 128;
+ p[i + 2] += Math.sin(i * value) * 127 + 128;
+ }
+ }
+ return p;
+ }
+
+ function transform_duplicate(p, value) {
+ var i;
+ if (value !== 0) {
+ for (i = 0; i < p.length; i += 4) {
+ p[i] = p[i * value];
+ p[i + 1] = p[i * value + 1];
+ p[i + 2] = p[i * value + 2];
+ p[i + 3] = p[i * value + 3];
+ }
+ }
+ return p;
+ }
+
+ function transform_confetti(p, value) {
+ var i;
+ if (value !== 0) {
+ for (i = 0; i < p.length; i += 1) {
+ p[i] = Math.sin(value * p[i]) * 127 + p[i];
+ }
+ }
+ return p;
+ }
+
+ if (this.graphicsChanged()) {
+ ctx = canvas.getContext("2d");
+ imagedata = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ pixels = imagedata.data;
+
+ //A sprite should wear all 7 effects at once
+ /*pixels = transform_whirl(pixels, this.graphicsValues.whirl);*/
+ pixels = transform_negative(pixels, this.graphicsValues.negative);
+ pixels = transform_brightness(pixels, this.graphicsValues.brightness);
+ pixels = transform_comic(pixels, this.graphicsValues.comic);
+ /*pixels = transform_pixelate(pixels, this.graphicsValues.pixelate);*/
+ pixels = transform_duplicate(pixels, this.graphicsValues.duplicate);
+ /*pixels = transform_color(pixels, this.graphicsValues.color);*/
+ /*pixels = transform_fisheye(pixels, this.graphicsValues.fisheye);*/
+ pixels = transform_confetti(pixels, this.graphicsValues.confetti);
+
+ //the last object will have all the transformations done on it
+ newimagedata = ctx.createImageData(imagedata); //make imgdata object
+ newimagedata.data.set(pixels); //add transformed pixels
+ ctx.putImageData(newimagedata, 0, 0);
+ }
+
+ return canvas;
+};
SpriteMorph.prototype.setEffect = function (effect, value) {
var eff = effect instanceof Array ? effect[0] : null;
if (eff === 'ghost') {
this.alpha = 1 - Math.min(Math.max(+value || 0, 0), 100) / 100;
- this.changed();
+ } else {
+ this.graphicsValues[eff] = value;
}
+ this.drawNew();
+ this.changed();
};
SpriteMorph.prototype.getGhostEffect = function () {
@@ -2665,10 +2994,18 @@ SpriteMorph.prototype.changeEffect = function (effect, value) {
var eff = effect instanceof Array ? effect[0] : null;
if (eff === 'ghost') {
this.setEffect(effect, this.getGhostEffect() + (+value || 0));
+ } else {
+ this.setEffect(effect, this.graphicsValues[eff] + value);
}
};
SpriteMorph.prototype.clearEffects = function () {
+ var effect;
+ for (effect in this.graphicsValues) {
+ if (this.graphicsValues.hasOwnProperty(effect)) {
+ this.setEffect([effect], 0);
+ }
+ }
this.setEffect(['ghost'], 0);
};
@@ -2877,11 +3214,12 @@ SpriteMorph.prototype.forward = function (steps) {
SpriteMorph.prototype.setHeading = function (degrees) {
var x = this.xPosition(),
y = this.yPosition(),
- turn = degrees - this.heading;
+ dir = (+degrees || 0),
+ turn = dir - this.heading;
// apply to myself
this.changed();
- SpriteMorph.uber.setHeading.call(this, degrees);
+ SpriteMorph.uber.setHeading.call(this, dir);
this.silentGotoXY(x, y, true); // just me
this.positionTalkBubble();
@@ -3816,12 +4154,24 @@ StageMorph.prototype.init = function (globals) {
this.keysPressed = {}; // for handling keyboard events, do not persist
this.blocksCache = {}; // not to be serialized (!)
this.paletteCache = {}; // not to be serialized (!)
- this.lastAnswer = null; // last user input, do not persist
+ this.lastAnswer = ''; // last user input, do not persist
this.activeSounds = []; // do not persist
this.trailsCanvas = null;
this.isThreadSafe = false;
+ this.graphicsValues = { 'negative': 0,
+ 'fisheye': 0,
+ 'whirl': 0,
+ 'pixelate': 0,
+ 'mosaic': 0,
+ 'brightness': 0,
+ 'color': 0,
+ 'comic': 0,
+ 'duplicate': 0,
+ 'confetti': 0
+ };
+
StageMorph.uber.init.call(this);
this.acceptsDrops = false;
@@ -3886,6 +4236,7 @@ StageMorph.prototype.drawNew = function () {
(this.width() / this.scale - this.costume.width()) / 2,
(this.height() / this.scale - this.costume.height()) / 2
);
+ this.image = this.applyGraphicsEffects(this.image);
}
};
@@ -4210,6 +4561,9 @@ StageMorph.prototype.processKeyEvent = function (event, action) {
break;
default:
keyName = String.fromCharCode(event.keyCode || event.charCode);
+ if (event.ctrlKey || event.metaKey) {
+ keyName = 'ctrl ' + (event.shiftKey ? 'shift ' : '') + keyName;
+ }
}
action.call(this, keyName);
};
@@ -4224,6 +4578,21 @@ StageMorph.prototype.fireKeyEvent = function (key) {
if (evt === 'ctrl enter') {
return this.fireGreenFlagEvent();
}
+ if (evt === 'ctrl f') {
+ return this.parentThatIsA(IDE_Morph).currentSprite.searchBlocks();
+ }
+ if (evt === 'ctrl n') {
+ return this.parentThatIsA(IDE_Morph).createNewProject();
+ }
+ if (evt === 'ctrl o') {
+ return this.parentThatIsA(IDE_Morph).openProjectsBrowser();
+ }
+ if (evt === 'ctrl s') {
+ return this.parentThatIsA(IDE_Morph).save();
+ }
+ if (evt === 'ctrl shift s') {
+ return this.parentThatIsA(IDE_Morph).saveProjectsBrowser();
+ }
if (evt === 'esc') {
return this.fireStopAllEvent();
}
@@ -4383,6 +4752,9 @@ StageMorph.prototype.blockTemplates = function (category) {
blocks.push(block('changeEffect'));
blocks.push(block('setEffect'));
blocks.push(block('clearEffects'));
+ blocks.push('-');
+ blocks.push(block('show'));
+ blocks.push(block('hide'));
// for debugging: ///////////////
@@ -4835,6 +5207,23 @@ StageMorph.prototype.thumbnail = function (extentPoint, excludedSprite) {
return trg;
};
+// StageMorph hiding and showing:
+
+/*
+ override the inherited behavior to recursively hide/show all
+ children.
+*/
+
+StageMorph.prototype.hide = function () {
+ this.isVisible = false;
+ this.changed();
+};
+
+StageMorph.prototype.show = function () {
+ this.isVisible = true;
+ this.changed();
+};
+
// StageMorph cloning overrice
StageMorph.prototype.createClone = nop;
@@ -4847,6 +5236,8 @@ StageMorph.prototype.paletteColor = SpriteMorph.prototype.paletteColor;
StageMorph.prototype.setName = SpriteMorph.prototype.setName;
StageMorph.prototype.palette = SpriteMorph.prototype.palette;
StageMorph.prototype.freshPalette = SpriteMorph.prototype.freshPalette;
+StageMorph.prototype.blocksMatching = SpriteMorph.prototype.blocksMatching;
+StageMorph.prototype.searchBlocks = SpriteMorph.prototype.searchBlocks;
StageMorph.prototype.showingWatcher = SpriteMorph.prototype.showingWatcher;
StageMorph.prototype.addVariable = SpriteMorph.prototype.addVariable;
StageMorph.prototype.deleteVariable = SpriteMorph.prototype.deleteVariable;
@@ -4895,6 +5286,12 @@ StageMorph.prototype.reportCostumes
// StageMorph graphic effects
+StageMorph.prototype.graphicsChanged
+ = SpriteMorph.prototype.graphicsChanged;
+
+StageMorph.prototype.applyGraphicsEffects
+ = SpriteMorph.prototype.applyGraphicsEffects;
+
StageMorph.prototype.setEffect
= SpriteMorph.prototype.setEffect;
@@ -6228,9 +6625,11 @@ WatcherMorph.prototype.update = function () {
} else {
newValue = this.target[this.getter]();
}
- num = +newValue;
- if (typeof newValue !== 'boolean' && !isNaN(num)) {
- newValue = Math.round(newValue * 1000000000) / 1000000000;
+ if (newValue !== '' && !isNil(newValue)) {
+ num = +newValue;
+ if (typeof newValue !== 'boolean' && !isNaN(num)) {
+ newValue = Math.round(newValue * 1000000000) / 1000000000;
+ }
}
if (newValue !== this.currentValue) {
this.changed();
diff --git a/paint.js b/paint.js
index ee40b23..7105b72 100644
--- a/paint.js
+++ b/paint.js
@@ -3,12 +3,12 @@
a paint editor for Snap!
inspired by the Scratch paint editor.
-
+
written by Kartik Chandra
Copyright (C) 2014 by Kartik Chandra
-
+
This file is part of Snap!.
-
+
Snap! is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of
@@ -53,6 +53,8 @@
Jan 08 - mouse leave dragging fix (Kartik)
Feb 11 - dynamically adjust to stage dimensions (Jens)
Apr 30 - localizations (Manuel)
+ June 3 - transformations (Kartik)
+ June 4 - tweaks (Jens)
*/
@@ -66,7 +68,7 @@
// Global stuff ////////////////////////////////////////////////////////
-modules.paint = '2014-May-02';
+modules.paint = '2014-June-4';
// Declarations
@@ -111,10 +113,10 @@ PaintEditorMorph.prototype.buildContents = function () {
this.paper.setExtent(StageMorph.prototype.dimensions);
this.addBody(new AlignmentMorph('row', this.padding));
- this.controls = new AlignmentMorph('column', this.padding);
+ this.controls = new AlignmentMorph('column', this.padding / 2);
this.controls.alignment = 'left';
- this.edits = new AlignmentMorph('row', this.padding);
+ this.edits = new AlignmentMorph('row', this.padding / 2);
this.buildEdits();
this.controls.add(this.edits);
@@ -133,6 +135,10 @@ PaintEditorMorph.prototype.buildContents = function () {
this.buildToolbox();
this.controls.add(this.toolbox);
+ this.scaleBox = new AlignmentMorph('row', this.padding / 2);
+ this.buildScaleBox();
+ this.controls.add(this.scaleBox);
+
this.propertiesControls = {
colorpicker: null,
penSizeSlider: null,
@@ -218,6 +224,27 @@ PaintEditorMorph.prototype.buildEdits = function () {
this.edits.fixLayout();
};
+PaintEditorMorph.prototype.buildScaleBox = function () {
+ var paper = this.paper;
+ this.scaleBox.add(this.pushButton(
+ "grow",
+ function () {paper.scale(0.05, 0.05); }
+ ));
+ this.scaleBox.add(this.pushButton(
+ "shrink",
+ function () {paper.scale(-0.05, -0.05); }
+ ));
+ this.scaleBox.add(this.pushButton(
+ "flip ↔",
+ function () {paper.scale(-2, 0); }
+ ));
+ this.scaleBox.add(this.pushButton(
+ "flip ↕",
+ function () {paper.scale(0, -2); }
+ ));
+ this.scaleBox.fixLayout();
+};
+
PaintEditorMorph.prototype.openIn = function (world, oldim, oldrc, callback) {
// Open the editor in a world with an optional image to edit
this.oldim = oldim;
@@ -558,6 +585,26 @@ PaintCanvasMorph.prototype.init = function (shift) {
this.buildContents();
};
+PaintCanvasMorph.prototype.scale = function (x, y) {
+ this.mask = newCanvas(this.extent());
+ var c = newCanvas(this.extent());
+ c.getContext("2d").save();
+ c.getContext("2d").translate(
+ this.rotationCenter.x,
+ this.rotationCenter.y
+ );
+ c.getContext("2d").scale(1 + x, 1 + y);
+ c.getContext("2d").drawImage(
+ this.paper,
+ -this.rotationCenter.x,
+ -this.rotationCenter.y
+ );
+ c.getContext("2d").restore();
+ this.paper = c;
+ this.drawNew();
+ this.changed();
+};
+
PaintCanvasMorph.prototype.cacheUndo = function () {
var cachecan = newCanvas(this.extent());
this.merge(this.paper, cachecan);
diff --git a/snap.html b/snap.html
index 66604c5..904aba8 100755
--- a/snap.html
+++ b/snap.html
@@ -22,6 +22,7 @@
var world;
window.onload = function () {
world = new WorldMorph(document.getElementById('world'));
+ world.worldCanvas.focus();
new IDE_Morph().openIn(world);
setInterval(loop, 1);
};
diff --git a/store.js b/store.js
index e143245..11e2757 100644
--- a/store.js
+++ b/store.js
@@ -61,7 +61,7 @@ SyntaxElementMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.store = '2014-May-02';
+modules.store = '2014-Jun-04';
// XML_Serializer ///////////////////////////////////////////////////////
@@ -1032,7 +1032,7 @@ SnapSerializer.prototype.loadInput = function (model, input, block) {
input.setColor(this.loadColor(model.contents));
} else {
val = this.loadValue(model);
- if (val) {
+ if (!isNil(val) && input.setContents) {
input.setContents(this.loadValue(model));
}
}
diff --git a/threads.js b/threads.js
index b82a8c5..104e129 100644
--- a/threads.js
+++ b/threads.js
@@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
// Global stuff ////////////////////////////////////////////////////////
-modules.threads = '2014-May-02';
+modules.threads = '2014-July-08';
var ThreadManager;
var Process;
@@ -673,11 +673,13 @@ Process.prototype.doYield = function () {
// Process Exception Handling
Process.prototype.handleError = function (error, element) {
+ var m = element;
this.stop();
this.errorFlag = true;
this.topBlock.addErrorHighlight();
- (element || this.topBlock).showBubble(
- (element ? '' : 'Inside: ')
+ if (isNil(m) || isNil(m.world())) {m = this.topBlock; }
+ m.showBubble(
+ (m === element ? '' : 'Inside: ')
+ error.name
+ '\n'
+ error.message
@@ -1057,7 +1059,6 @@ Process.prototype.evaluateCustomBlock = function () {
}
};
-
// Process variables primitives
Process.prototype.doDeclareVariables = function (varNames) {
@@ -3054,7 +3055,7 @@ VariableFrame.prototype.getVar = function (name, upvars) {
VariableFrame.prototype.addVar = function (name, value) {
this.vars[name] = (value === 0 ? 0
: value === false ? false
- : value === '' ? '' : value || null);
+ : value === '' ? '' : value || 0);
};
VariableFrame.prototype.deleteVar = function (name) {
@@ -3110,6 +3111,20 @@ VariableFrame.prototype.allNames = function () {
return answer;
};
+// Variable /////////////////////////////////////////////////////////////////
+
+function Variable(value) {
+ this.value = value;
+}
+
+Variable.prototype.toString = function () {
+ return 'a Variable [' + this.value + ']';
+};
+
+Variable.prototype.copy = function () {
+ return new Variable(this.value);
+};
+
// UpvarReference ///////////////////////////////////////////////////////////
// ... quasi-inherits some features from VariableFrame