diff options
| -rw-r--r-- | blocks.js | 96 | ||||
| -rw-r--r-- | byob.js | 86 | ||||
| -rw-r--r-- | cloud.js | 15 | ||||
| -rw-r--r-- | gui.js | 167 | ||||
| -rwxr-xr-x | history.txt | 62 | ||||
| -rw-r--r-- | lang-de.js | 10 | ||||
| -rwxr-xr-x | lang-pt.js | 86 | ||||
| -rw-r--r-- | lists.js | 4 | ||||
| -rw-r--r-- | locale.js | 6 | ||||
| -rw-r--r-- | morphic.js | 78 | ||||
| -rw-r--r-- | objects.js | 425 | ||||
| -rw-r--r-- | paint.js | 59 | ||||
| -rwxr-xr-x | snap.html | 1 | ||||
| -rw-r--r-- | store.js | 4 | ||||
| -rw-r--r-- | threads.js | 25 |
15 files changed, 1004 insertions, 120 deletions
@@ -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); } @@ -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 //////////////////////////////////////////////////// /* @@ -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; } @@ -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 @@ -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': @@ -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': @@ -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; } @@ -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 = { @@ -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 ); @@ -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(); @@ -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); @@ -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); }; @@ -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)); } } @@ -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 |
