diff --git a/README.md b/README.md index 97ceddcc..a9b84fec 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,6 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . + +Want to use Snap! but scared by the open-source license? Get in touch with us, +we'll make it work. diff --git a/blocks.js b/blocks.js index d0dfab79..a4ed5c38 100644 --- a/blocks.js +++ b/blocks.js @@ -145,11 +145,11 @@ radians, useBlurredShadows, SpeechBubbleMorph, modules, StageMorph, fontHeight, TableFrameMorph, SpriteMorph, Context, ListWatcherMorph, CellMorph, DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume, IDE_Morph, BlockDialogMorph, BlockEditorMorph, localize, isNil, -isSnapObject, copy, PushButtonMorph, SpriteIconMorph*/ +isSnapObject, copy, PushButtonMorph, SpriteIconMorph, Process*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2016-July-15'; +modules.blocks = '2016-October-27'; var SyntaxElementMorph; var BlockMorph; @@ -186,7 +186,7 @@ WorldMorph.prototype.customMorphs = function () { /* return [ new SymbolMorph( - 'pipette', + 'stepForward', 50, new Color(250, 250, 250), new Point(-1, -1), @@ -346,6 +346,7 @@ SyntaxElementMorph.prototype.init = function (silently) { this.cachedClr = null; this.cachedClrBright = null; this.cachedClrDark = null; + this.cachedNormalColor = null; // for single-stepping this.isStatic = false; // if true, I cannot be exchanged SyntaxElementMorph.uber.init.call(this, silently); @@ -731,6 +732,20 @@ SyntaxElementMorph.prototype.setLabelColor = function ( }); }; +SyntaxElementMorph.prototype.flash = function () { + if (!this.cachedNormalColor) { + this.cachedNormalColor = this.color; + this.setColor(this.activeHighlight); + } +}; + +SyntaxElementMorph.prototype.unflash = function () { + if (this.cachedNormalColor) { + var clr = this.cachedNormalColor; + this.cachedNormalColor = null; + this.setColor(clr); + } +}; // SyntaxElementMorph zebra coloring @@ -2332,7 +2347,7 @@ BlockMorph.prototype.userMenu = function () { function () { new DialogBoxMorph( myself, - myself.setSpec, + myself.userSetSpec, myself ).prompt( "Variable name", @@ -2430,7 +2445,7 @@ BlockMorph.prototype.userMenu = function () { } return menu; } - if (this.parentThatIsA(RingMorph)) { + if (this.parent.parentThatIsA(RingMorph)) { menu.addLine(); menu.addItem("unringify", 'unringify'); menu.addItem("ringify", 'ringify'); @@ -2527,10 +2542,10 @@ BlockMorph.prototype.deleteBlock = function () { } }); } - if (this instanceof ReporterBlockMorph) { - if (this.parent instanceof BlockMorph) { - this.parent.revertToDefaultInput(this); - } + if ((this.parent instanceof BlockMorph) + || (this.parent instanceof MultiArgMorph) + || (this.parent instanceof ReporterSlotMorph)) { + this.parent.revertToDefaultInput(this); } else { // CommandBlockMorph if (this.parent) { if (this.parent.fixLayout) { @@ -2583,7 +2598,7 @@ BlockMorph.prototype.ringify = function () { BlockMorph.prototype.unringify = function () { // remove a Ring around me, if any - var ring = this.parentThatIsA(RingMorph), + var ring = this.parent.parentThatIsA(RingMorph), top = this.topBlock(), scripts = this.parentThatIsA(ScriptsMorph), block, @@ -5835,7 +5850,8 @@ ArgMorph.prototype.reactToSliderEdit = function () { } if (receiver) { stage = receiver.parentThatIsA(StageMorph); - if (stage && stage.isThreadSafe) { + if (stage && (stage.isThreadSafe || + Process.prototype.enableSingleStepping)) { stage.threads.startProcess(top, stage.isThreadSafe); } else { top.mouseClickLeft(); @@ -7529,7 +7545,6 @@ InputSlotMorph.prototype.mouseDownLeft = function (pos) { this.escalateEvent('mouseDownLeft', pos); } else { this.contents().edit(); - this.contents().selectAll(); } }; @@ -7540,7 +7555,6 @@ InputSlotMorph.prototype.mouseClickLeft = function (pos) { this.dropDownMenu(); } else { this.contents().edit(); - this.contents().selectAll(); } }; @@ -7558,6 +7572,12 @@ InputSlotMorph.prototype.reactToEdit = function () { this.contents().clearSelection(); }; +InputSlotMorph.prototype.freshTextEdit = function (aStringOrTextMorph) { + this.onNextStep = function () { + aStringOrTextMorph.selectAll(); + }; +}; + // InputSlotMorph menu: InputSlotMorph.prototype.userMenu = function () { @@ -7639,6 +7659,29 @@ InputSlotMorph.prototype.isEmptySlot = function () { return this.contents().text === ''; }; +// InputSlotMorph single-stepping: + +InputSlotMorph.prototype.flash = function () { + // don't redraw the label b/c zebra coloring + if (!this.cachedNormalColor) { + this.cachedNormalColor = this.color; + this.color = this.activeHighlight; + this.drawNew(); + this.changed(); + } +}; + +InputSlotMorph.prototype.unflash = function () { + // don't redraw the label b/c zebra coloring + if (this.cachedNormalColor) { + var clr = this.cachedNormalColor; + this.cachedNormalColor = null; + this.color = clr; + this.drawNew(); + this.changed(); + } +}; + // InputSlotMorph drawing: InputSlotMorph.prototype.drawNew = function () { @@ -7647,13 +7690,15 @@ InputSlotMorph.prototype.drawNew = function () { // initialize my surface property this.image = newCanvas(this.extent()); context = this.image.getContext('2d'); - if (this.parent) { + if (this.cachedNormalColor) { // if flashing + borderColor = this.color; + } else if (this.parent) { borderColor = this.parent.color; } else { borderColor = new Color(120, 120, 120); } context.fillStyle = this.color.toString(); - if (this.isReadOnly) { + if (this.isReadOnly && !this.cachedNormalColor) { // unless flashing context.fillStyle = borderColor.darker().toString(); } @@ -7994,6 +8039,16 @@ TemplateSlotMorph.prototype.drawNew = function () { TemplateSlotMorph.prototype.drawRounded = ReporterBlockMorph .prototype.drawRounded; +// TemplateSlotMorph single-stepping + +TemplateSlotMorph.prototype.flash = function () { + this.template().flash(); +}; + +TemplateSlotMorph.prototype.unflash = function () { + this.template().unflash(); +}; + // BooleanSlotMorph //////////////////////////////////////////////////// /* @@ -8142,7 +8197,10 @@ BooleanSlotMorph.prototype.drawNew = function (progress) { this.fontSize + this.edge * 2 )); } - this.color = this.parent ? this.parent.color : new Color(200, 200, 200); + if (!(this.cachedNormalColor)) { // unless flashing + this.color = this.parent ? + this.parent.color : new Color(200, 200, 200); + } this.cachedClr = this.color.toString(); this.cachedClrBright = this.bright(); this.cachedClrDark = this.dark(); @@ -8162,15 +8220,19 @@ BooleanSlotMorph.prototype.drawDiamond = function (context, progress) { gradient; // draw the 'flat' shape: - switch (this.value) { - case true: - context.fillStyle = 'rgb(0, 200, 0)'; - break; - case false: - context.fillStyle = 'rgb(200, 0, 0)'; - break; - default: - context.fillStyle = this.color.darker(25).toString(); + if (this.cachedNormalColor) { // if flashing + context.fillStyle = this.color.toString(); + } else { + switch (this.value) { + case true: + context.fillStyle = 'rgb(0, 200, 0)'; + break; + case false: + context.fillStyle = 'rgb(200, 0, 0)'; + break; + default: + context.fillStyle = this.color.darker(25).toString(); + } } if (progress && !this.isEmptySlot()) { @@ -8680,6 +8742,7 @@ SymbolMorph.uber = Morph.prototype; SymbolMorph.prototype.names = [ 'square', 'pointRight', + 'stepForward', 'gears', 'file', 'fullScreen', @@ -8802,6 +8865,8 @@ SymbolMorph.prototype.symbolCanvasColored = function (aColor) { return this.drawSymbolStop(canvas, aColor); case 'pointRight': return this.drawSymbolPointRight(canvas, aColor); + case 'stepForward': + return this.drawSymbolStepForward(canvas, aColor); case 'gears': return this.drawSymbolGears(canvas, aColor); case 'file': @@ -8945,6 +9010,28 @@ SymbolMorph.prototype.drawSymbolPointRight = function (canvas, color) { return canvas; }; +SymbolMorph.prototype.drawSymbolStepForward = function (canvas, color) { + // answer a canvas showing a right-pointing triangle + // followed by a vertical bar + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(canvas.width * 0.75, Math.round(canvas.height / 2)); + ctx.lineTo(0, canvas.height); + ctx.lineTo(0, 0); + ctx.closePath(); + ctx.fill(); + ctx.fillRect( + canvas.width * 0.75, + 0, + canvas.width * 0.25, + canvas.height + ); + return canvas; +}; + SymbolMorph.prototype.drawSymbolGears = function (canvas, color) { // answer a canvas showing gears var ctx = canvas.getContext('2d'), @@ -10371,7 +10458,7 @@ MultiArgMorph.prototype.mouseClickLeft = function (pos) { this.escalateEvent('mouseClickLeft', pos); return; } - // if the key is pressed, repeat action 5 times + // if the key is pressed, repeat action 3 times var arrows = this.arrows(), leftArrow = arrows.children[0], rightArrow = arrows.children[1], @@ -12188,6 +12275,13 @@ ScriptFocusMorph.prototype.insertBlock = function (block) { } } } + + // experimental: if the inserted block has inputs, go to the first one + if (block.inputs && block.inputs().length) { + this.element = block; + this.atEnd = false; + this.nextElement(); + } }; ScriptFocusMorph.prototype.insertVariableGetter = function () { diff --git a/byob.js b/byob.js index 16b2b985..d56ed8d5 100644 --- a/byob.js +++ b/byob.js @@ -108,7 +108,7 @@ WatcherMorph, Variable*/ // Global stuff //////////////////////////////////////////////////////// -modules.byob = '2016-July-14'; +modules.byob = '2016-September-27'; // Declarations @@ -150,6 +150,7 @@ function CustomBlockDefinition(spec, receiver) { // don't serialize (not needed for functionality): this.receiver = receiver || null; // for serialization only (pointer) this.editorDimensions = null; // a rectangle, last bounds of the editor + this.cachedIsRecursive = null; // for automatic yielding } // CustomBlockDefinition instantiating blocks @@ -343,6 +344,26 @@ CustomBlockDefinition.prototype.parseSpec = function (spec) { return parts; }; +CustomBlockDefinition.prototype.isDirectlyRecursive = function () { + var myspec; + if (this.cachedIsRecursive !== null) { + return this.cachedIsRecursive; + } + if (!this.body) { + this.cachedIsRecursive = false; + } else { + myspec = this.spec; + this.cachedIsRecursive = this.body.expression.anyChild( + function (morph) { + return morph instanceof BlockMorph && + morph.definition && + morph.definition.spec === myspec; + } + ); + } + return this.cachedIsRecursive; +}; + // CustomBlockDefinition picturing CustomBlockDefinition.prototype.scriptsPicture = function () { @@ -769,7 +790,7 @@ CustomCommandBlockMorph.prototype.attachTargets = function () { }; CustomCommandBlockMorph.prototype.isInUse = function () { - // anser true if an instance of my definition is found + // answer true if an instance of my definition is found // in any of my receiver's scripts or block definitions var def = this.definition, ide = this.receiver().parentThatIsA(IDE_Morph); @@ -1558,7 +1579,7 @@ BlockDialogMorph.prototype.createScopeButtons = function () { var myself = this; this.addScopeButton( - function () {myself.setScope('gobal'); }, + function () {myself.setScope('global'); }, "for all sprites", function () {return myself.isGlobal; } ); @@ -1591,7 +1612,7 @@ BlockDialogMorph.prototype.addScopeButton = function (action, label, query) { BlockDialogMorph.prototype.setScope = function (varType) { - this.isGlobal = (varType === 'gobal'); + this.isGlobal = (varType === 'global'); this.scopes.children.forEach(function (c) { c.refresh(); }); @@ -1754,7 +1775,9 @@ function BlockEditorMorph(definition, target) { } BlockEditorMorph.prototype.init = function (definition, target) { - var scripts, proto, scriptsFrame, block, comment, myself = this; + var scripts, proto, scriptsFrame, block, comment, myself = this, + isLive = Process.prototype.enableLiveCoding || + Process.prototype.enableSingleStepping; // additional properties: this.definition = definition; @@ -1789,7 +1812,9 @@ BlockEditorMorph.prototype.init = function (definition, target) { comment.block = proto; } if (definition.body !== null) { - proto.nextBlock(definition.body.expression.fullCopy()); + proto.nextBlock(isLive ? definition.body.expression + : definition.body.expression.fullCopy() + ); } scripts.add(proto); proto.fixBlockColor(null, true); @@ -1819,8 +1844,10 @@ BlockEditorMorph.prototype.init = function (definition, target) { this.addBody(scriptsFrame); this.addButton('ok', 'OK'); - this.addButton('updateDefinition', 'Apply'); - this.addButton('cancel', 'Cancel'); + if (!isLive) { + this.addButton('updateDefinition', 'Apply'); + this.addButton('cancel', 'Cancel'); + } this.setExtent(new Point(375, 300)); // normal initial extent this.fixLayout(); @@ -1962,6 +1989,7 @@ BlockEditorMorph.prototype.updateDefinition = function () { this.definition.variableNames = this.variableNames(); this.definition.scripts = []; this.definition.editorDimensions = this.bounds.copy(); + this.definition.cachedIsRecursive = null; // flush the cache, don't update this.body.contents.children.forEach(function (morph) { if (morph instanceof PrototypeHatBlockMorph) { @@ -3282,7 +3310,7 @@ VariableDialogMorph.prototype.createTypeButtons = function () { var myself = this; this.addTypeButton( - function () {myself.setType('gobal'); }, + function () {myself.setType('global'); }, "for all sprites", function () {return myself.isGlobal; } ); @@ -3297,7 +3325,7 @@ VariableDialogMorph.prototype.addTypeButton = BlockDialogMorph.prototype.addTypeButton; VariableDialogMorph.prototype.setType = function (varType) { - this.isGlobal = (varType === 'gobal'); + this.isGlobal = (varType === 'global'); this.types.children.forEach(function (c) { c.refresh(); }); diff --git a/gui.js b/gui.js index 7d9f0456..8a6d4997 100644 --- a/gui.js +++ b/gui.js @@ -68,11 +68,11 @@ fontHeight, hex_sha512, sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio, SpeechBubbleMorph, ScriptFocusMorph, XML_Element, WatcherMorph, BlockRemovalDialogMorph, saveAs, TableMorph, isSnapObject, isRetinaEnabled, disableRetinaSupport, enableRetinaSupport, -isRetinaSupported*/ +isRetinaSupported, SliderMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2016-August-12'; +modules.gui = '2016-October-27'; // Declarations @@ -536,6 +536,7 @@ IDE_Morph.prototype.createControlBar = function () { // assumes the logo has already been created var padding = 5, button, + slider, stopButton, pauseButton, startButton, @@ -718,6 +719,23 @@ IDE_Morph.prototype.createControlBar = function () { this.controlBar.add(startButton); this.controlBar.startButton = startButton; + // steppingSlider + slider = new SliderMorph( + 61, + 1, + Process.prototype.flashTime * 100 + 1, + 6, + 'horizontal' + ); + slider.action = function (num) { + Process.prototype.flashTime = (num - 1) / 100; + myself.controlBar.refreshResumeSymbol(); + }; + slider.alpha = MorphicPreferences.isFlat ? 0.1 : 0.3; + slider.setExtent(new Point(50, 14)); + this.controlBar.add(slider); + this.controlBar.steppingSlider = slider; + // projectButton button = new PushButtonMorph( this, @@ -814,6 +832,9 @@ IDE_Morph.prototype.createControlBar = function () { } ); + slider.setCenter(myself.controlBar.center()); + slider.setRight(stageSizeButton.left() - padding); + settingsButton.setCenter(myself.controlBar.center()); settingsButton.setLeft(this.left()); @@ -823,9 +844,41 @@ IDE_Morph.prototype.createControlBar = function () { projectButton.setCenter(myself.controlBar.center()); projectButton.setRight(cloudButton.left() - padding); + this.refreshSlider(); this.updateLabel(); }; + this.controlBar.refreshSlider = function () { + if (Process.prototype.enableSingleStepping && !myself.isAppMode) { + slider.drawNew(); + slider.show(); + } else { + slider.hide(); + } + this.refreshResumeSymbol(); + }; + + this.controlBar.refreshResumeSymbol = function () { + var pauseSymbols; + if (Process.prototype.enableSingleStepping && + Process.prototype.flashTime > 0.5) { + myself.stage.threads.pauseAll(myself.stage); + pauseSymbols = [ + new SymbolMorph('pause', 12), + new SymbolMorph('stepForward', 14) + ]; + } else { + pauseSymbols = [ + new SymbolMorph('pause', 12), + new SymbolMorph('pointRight', 14) + ]; + } + pauseButton.labelString = pauseSymbols; + pauseButton.createLabel(); + pauseButton.fixLayout(); + pauseButton.refresh(); + }; + this.controlBar.updateLabel = function () { var suffix = myself.world().isDevMode ? ' - ' + localize('development mode') : ''; @@ -967,6 +1020,7 @@ IDE_Morph.prototype.createPalette = function (forSearching) { } this.palette.isDraggable = false; this.palette.acceptsDrops = true; + this.palette.enableAutoScrolling = false; this.palette.contents.acceptsDrops = false; this.palette.reactToDropOf = function (droppedMorph) { @@ -1767,6 +1821,11 @@ IDE_Morph.prototype.toggleVariableFrameRate = function () { } }; +IDE_Morph.prototype.toggleSingleStepping = function () { + this.stage.threads.toggleSingleStepping(); + this.controlBar.refreshSlider(); +}; + IDE_Morph.prototype.startFastTracking = function () { this.stage.isFastTracked = true; this.stage.fps = 0; @@ -2551,6 +2610,14 @@ IDE_Morph.prototype.settingsMenu = function () { 'EXPERIMENTAL! check to enable\n live custom control structures', true ); + addPreference( + 'Visible stepping', + 'toggleSingleStepping', + Process.prototype.enableSingleStepping, + 'uncheck to turn off\nvisible stepping', + 'check to turn on\n visible stepping (slow)', + false + ); menu.addLine(); // everything below this line is stored in the project addPreference( 'Thread safe scripts', @@ -2982,7 +3049,7 @@ IDE_Morph.prototype.aboutSnap = function () { module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn, world = this.world(); - aboutTxt = 'Snap! 4.0.8.7\nBuild Your Own Blocks\n\n' + aboutTxt = 'Snap! 4.0.9\nBuild Your Own Blocks\n\n' + 'Copyright \u24B8 2016 Jens M\u00F6nig and ' + 'Brian Harvey\n' + 'jens@moenig.org, bh@cs.berkeley.edu\n\n' @@ -3013,7 +3080,10 @@ IDE_Morph.prototype.aboutSnap = function () { + 'You should have received a copy of the\n' + 'GNU Affero General Public License along with this program.\n' - + 'If not, see http://www.gnu.org/licenses/'; + + 'If not, see http://www.gnu.org/licenses/\n\n' + + + 'Want to use Snap! but scared by the open-source license?\n' + + 'Get in touch with us, we\'ll make it work.'; creditsTxt = localize('Contributors') + '\n\nNathan Dinsmore: Saving/Loading, Snap-Logo Design, ' @@ -3021,8 +3091,10 @@ IDE_Morph.prototype.aboutSnap = function () { + '\nKartik Chandra: Paint Editor' + '\nMichael Ball: Time/Date UI, many bugfixes' + '\nBartosz Leper: Retina Display Support' + + '\nBernat Romagosa: Countless contributions' + '\n"Ava" Yuan Yuan, Dylan Servilla: Graphic Effects' + '\nKyle Hotchkiss: Block search design' + + '\nBrian Broll: Many bugfixes and optimizations' + '\nIan Reynolds: UI Design, Event Bindings, ' + 'Sound primitives' + '\nIvan Motyashov: Initial Squeak Porting' @@ -5440,16 +5512,19 @@ ProjectDialogMorph.prototype.setSource = function (source) { if (myself.task === 'open') { src = localStorage['-snap-project-' + item.name]; - xml = myself.ide.serializer.parse(src); - myself.notesText.text = xml.childNamed('notes').contents - || ''; - myself.notesText.drawNew(); - myself.notesField.contents.adjustBounds(); - myself.preview.texture = xml.childNamed('thumbnail').contents - || null; - myself.preview.cachedTexture = null; - myself.preview.drawNew(); + if (src) { + xml = myself.ide.serializer.parse(src); + + myself.notesText.text = xml.childNamed('notes').contents + || ''; + myself.notesText.drawNew(); + myself.notesField.contents.adjustBounds(); + myself.preview.texture = + xml.childNamed('thumbnail').contents || null; + myself.preview.cachedTexture = null; + myself.preview.drawNew(); + } } myself.edit(); }; diff --git a/history.txt b/history.txt index d469d53f..ee479a3c 100755 --- a/history.txt +++ b/history.txt @@ -3004,3 +3004,89 @@ http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=rotation * Morphic: replace deprecated KeyboardEvent.keyIdentifier with .key == v4.0.8.7 ==== + +*** in development *** + +160915 +------ +* new single stepping feature (like Scratch 1.4) with flashing blocks +* slider for single-stepping speed +* pausing now flashes the currently active blocks + +160916 +------ +* enable single stepping for clone-scripts w. multiple blocks flashing per script +* enable single stepping for custom block definitions +* Objects: fixed #1410 (duplicating a sprite does not duplicate its sounds) + +160918 +------ +* Treat single-stepping as thread safe (reverted on 160923) +* Allow user to trigger one step at a time, both in normal and single-stepping mode + +160919 +------ +* new “stepForward” symbol +* dragging the single-step speed slider all the way to the left turns the “resume” side of the “pause” button into “stepForward” + +160920 +------ +* atomic synching of single-stepping + +160921 +------ +* remove shift-click-to-forward-one-frame option for the “resume” button + +160922 +------ +* renamed “single stepping” to “visible stepping”, thanks, Brian! +* updated German translation + +160923 +------ +* custom block execution: only yield if directly recursive and unwrapped (“speed up”) +* new feature: “wait 0” or “wait ” now yields once, unless warped +* revert treating visual stepping as thread safe, because of music scheduling + +160924 +------ +* don’t update the recursion cache when updating a custom block definition + +160929 +------ +* Objects: fixed #1437 + +161007 +------ +* Blocks: [Keyboard-Entry] if an inserted block has inputs, go to the first one + +161010 +------ +* Morphic: configure autoscrolling +* GUI: suppress autoscrolling for the palette and the project dialog + +161011 +------ +* Blocks: make sure to fix multi-args when deleting a custom reporter definition + +161011 +------ +* Objects: fixed #1456 (collect message names from all scripts, including custom block definitions) + +161020 +------ +* Blocks: Tweak Keyboard-Entry + +161021 +------ +* Threads: Fixed #1422 + +161024 +------ +* Text Editing Tweaks, thanks, Bernat!! +* Store: fixed #1472 +* Threads: Tweak continuations + +161027 +------ +== v4.0.9 ==== diff --git a/lang-de.js b/lang-de.js index 49162484..35b626f1 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': - '2016-07-12', // this, too, will appear in the Translators tab + '2016-10-27', // this, too, will appear in the Translators tab // GUI // control bar: @@ -821,6 +821,8 @@ SnapTranslator.dict.de = { 'Tabellenunterstützung', 'Table lines': 'Tabellen mit Linien', + 'Visible stepping': + 'Programmausführung verfolgen', 'Thread safe scripts': 'Threadsicherheit', 'uncheck to allow\nscript reentrance': diff --git a/lang-fr.js b/lang-fr.js index dcea697f..5128d83c 100644 --- a/lang-fr.js +++ b/lang-fr.js @@ -1,27 +1,27 @@ /* - lang-de.js + lang-de.js - German translation for SNAP! + German translation for SNAP! - written by Jens Mönig + written by Jens Mönig - Copyright (C) 2012 by Jens Mönig + Copyright (C) 2012 by Jens Mönig - This file is part of Snap!. + 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 - the License, or (at your option) any later version. + 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 + the License, or (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . @@ -175,8 +175,10 @@ SnapTranslator.dict.fr = { à \u00E0 É \u00C9 è \u00E8 + é \u00E9 ê \u00EA ç \u00E7 + ï \u00EF ô \u00F4 ù \u00F9 ° \u00B0 @@ -195,7 +197,7 @@ SnapTranslator.dict.fr = { 'translator_e-mail': 'i.scool@mac.com', // optional 'last_changed': - '2016-01-27', // this, too, will appear in the Translators tab + '2016-10-27', // this, too, will appear in the Translators tab // GUI // control bar: @@ -431,8 +433,8 @@ SnapTranslator.dict.fr = { 'Quand %keyHat est press\u00E9', 'when I am clicked': 'Quand je suis press\u00E9 ', - 'when I am %interaction': - 'Quand je suis %interaction', + 'when I am %interaction': + 'Quand je suis %interaction', 'when I receive %msgHat': 'Quand je re\u00E7ois %msgHat', 'broadcast %msg': @@ -461,10 +463,10 @@ SnapTranslator.dict.fr = { 'arr\u00EAter le bloc', 'stop script': 'arr\u00EAter le script', - 'stop %stopOthersChoices': - 'arr\u00EAter %stopOthersChoices', - 'stop %stopChoices': - 'arr\u00EAter %stopChoices', + 'stop %stopOthersChoices': + 'arr\u00EAter %stopOthersChoices', + 'stop %stopChoices': + 'arr\u00EAter %stopChoices', 'stop all %stop': 'arr\u00EAter tout %stop', 'run %cmdRing %inputs': @@ -489,14 +491,14 @@ SnapTranslator.dict.fr = { 'supprime ce clone', 'pause all %pause': 'mettre en pause %pause', - 'all but this script': - 'tout sauf ce script', - 'other scripts in sprite': - 'les autres scripts de ce lutin', - 'this script': - 'ce script', - 'this block': - 'ce bloc', + 'all but this script': + 'tout sauf ce script', + 'other scripts in sprite': + 'les autres scripts de ce lutin', + 'this script': + 'ce script', + 'this block': + 'ce bloc', // sensing: 'touching %col ?': @@ -527,6 +529,8 @@ SnapTranslator.dict.fr = { 'chronom\u00E8tre', '%att of %spr': '%att de %spr', + 'my %get': + 'attribut %get', 'http:// %s': 'http:// %s', 'turbo mode?': @@ -671,6 +675,8 @@ SnapTranslator.dict.fr = { 'Exporter le projet comme texte...', 'Export project...': 'Exporter le projet...', + 'save project data as XML\nto your downloads folder': + 'sauvegarder le projet au\nformat XML dans votre\ndossier T\u00E9l\u00E9chargements', 'show project data as XML\nin a new browser window': 'ouvrir le projet au format XML\ndans une nouvelle fen\u00EAtre de votre navigateur', 'Export blocks...': @@ -722,7 +728,7 @@ SnapTranslator.dict.fr = { + 'lors du glisser-d\u00E9poser d\u0027un reporter', 'uncheck to allow dropped\nreporters to kick out others': 'd\u00E9cocher pour ne pas pr\u00E9f\u00E9rer des entr\u00E9es vides \n' - + 'lors du glisser-d\u00E9poser d\u0027un reporter', + + 'lors du glisser-d\u00E9poser d\u0027un reporter', 'Long form input dialog': 'Bo\u00EEte d\u0027entr\u00E9e en mode d\u00E9taill\u00E9', 'check to always show slot\ntypes in the input dialog': @@ -745,10 +751,10 @@ SnapTranslator.dict.fr = { 'Cliquetis', 'uncheck to turn\nblock clicking\nsound off': 'd\u00E9cocher pour d\u00E9sactiver le cliquetis \n' - +'lors de l\u0027embo\u00EEtement des blocs' , + +'lors de l\u0027embo\u00EEtement des blocs' , 'check to turn\nblock clicking\nsound on': 'cocher pour activer le cliquetis \n' - +'lors de l\u0027embo\u00EEtement des blocs', + +'lors de l\u0027embo\u00EEtement des blocs', 'Turbo mode': 'Mode turbo', 'check to prioritize\nscript execution': @@ -760,7 +766,7 @@ SnapTranslator.dict.fr = { 'check for alternative\nGUI design': 'cocher pour un style d\'interface alternatif', 'uncheck for default\nGUI design': - 'd\u00E9cocher pour le style classique d\'interface', + 'd\u00E9cocher pour le style classique d\'interface', 'Keyboard Editing': '\u00C9dition au clavier', 'uncheck to disable\nkeyboard editing support': @@ -771,10 +777,10 @@ SnapTranslator.dict.fr = { 'Scripts réentrants', 'check to disallow\nscript reentrance': 'cocher pour interdire\n la r\u00E9entrance des scripts\n' - + 'et les ex\u00E9cuter s\u00E9par\u00E9ment', + + 'et les ex\u00E9cuter s\u00E9par\u00E9ment', 'uncheck to allow\nscript reentrance': 'd\u00E9cocher pour permettre\n la r\u00E9entrance des scripts\n' - + 'o\u00F9 certains s\'ex\u00E9cutent en paral\u00E8lle', + + 'o\u00F9 certains s\'ex\u00E9cutent en paral\u00E8lle', 'Prefer smooth animations': 'Vitesse d\'animation fixe', 'uncheck for greater speed\nat variable frame rates': @@ -884,6 +890,12 @@ SnapTranslator.dict.fr = { 'Oui', 'No': 'Non', + 'Open': + 'Ouvrir', + 'Browser': + 'Navigateur', + 'Examples': + 'Exemples', // help 'Help': @@ -1020,8 +1032,8 @@ SnapTranslator.dict.fr = { 'tabulations', 'cr': 'retours de ligne', - 'letter': - 'lettres', + 'letter': + 'lettres', // About Snap 'About Snap': @@ -1095,8 +1107,28 @@ SnapTranslator.dict.fr = { 'Vide', // graphical effects + 'color': + 'couleur', + 'fisheye': + 'fisheye', + 'whirl': + 'tourbillon', + 'pixelate': + 'pixelisation', + 'mosaic': + 'mosa\u00EFque', + 'saturation': + 'saturation', + 'brightness': + 'luminosit\u00E9', 'ghost': 'transparence', + 'negative': + 'n\u00E9gatif', + 'comic': + 'moir\u00E9', + 'confetti': + 'confetti', // keys 'space': @@ -1230,145 +1262,145 @@ SnapTranslator.dict.fr = { 'any': 'n\u0027importe quel', - // miscellaneous - 'find blocks...': - 'chercher des blocs...', - 'hide primitives': - 'cacher les primitives', - 'show primitives': - 'montrer les primitives', - 'Login...': - 'Connexion...', - 'Signup...': - 'S\u0027enregistrer...', - 'Reset Password...': - 'Remise \u00E0 z\u00E9ro du mot de passe...', - 'show all': - 'tout montrer', - 'pic...': - 'image...', - 'open a new window\nwith a picture of the stage': - 'ouvre une nouvelle fen\u00EAtre\navec une image de la sc\u00E8ne', - 'scripts pic...': - 'image des scripts...', - 'open a new window\nwith a picture of all scripts': - 'ouvre une nouvelle fen\u00EAtre\navec une image de tous les scripts', - 'Stage size...': - 'Taille de la sc\u00E8ne...', - 'Zoom blocks...': - 'Agrandir les blocs...', + // miscellaneous + 'find blocks...': + 'chercher des blocs...', + 'hide primitives': + 'cacher les primitives', + 'show primitives': + 'montrer les primitives', + 'Login...': + 'Connexion...', + 'Signup...': + 'S\u0027enregistrer...', + 'Reset Password...': + 'Remise \u00E0 z\u00E9ro du mot de passe...', + 'show all': + 'tout montrer', + 'pic...': + 'image...', + 'open a new window\nwith a picture of the stage': + 'ouvre une nouvelle fen\u00EAtre\navec une image de la sc\u00E8ne', + 'scripts pic...': + 'image des scripts...', + 'open a new window\nwith a picture of all scripts': + 'ouvre une nouvelle fen\u00EAtre\navec une image de tous les scripts', + 'Stage size...': + 'Taille de la sc\u00E8ne...', + 'Zoom blocks...': + 'Agrandir les blocs...', - 'Plain prototype labels': - '\u00C9tiquettes simples de d\u00E9finition', - 'uncheck to always show (+) symbols\nin block prototype labels': - 'd\u00E9cocher pour montrer en permanance le symbole (+)\ndans les \u00e9tiquettes de d\u00E9finition de bloc', - 'check to hide (+) symbols\nin block prototype labels': - 'cocher pour cacher le symbole (+)\ndans les \u00e9tiquettes de d\u00E9finition de bloc', + 'Plain prototype labels': + '\u00C9tiquettes simples de d\u00E9finition', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'd\u00E9cocher pour montrer en permanance le symbole (+)\ndans les \u00e9tiquettes de d\u00E9finition de bloc', + 'check to hide (+) symbols\nin block prototype labels': + 'cocher pour cacher le symbole (+)\ndans les \u00e9tiquettes de d\u00E9finition de bloc', - 'check for flat ends of lines': - 'cocher pour dessiner des fins de ligne plates', - 'uncheck for round ends of lines': - 'd\u00E9cocher pour dessiner des fins de lignes arrondies', - 'Flat line ends': - 'Fins de ligne plates', + 'check for flat ends of lines': + 'cocher pour dessiner des fins de ligne plates', + 'uncheck for round ends of lines': + 'd\u00E9cocher pour dessiner des fins de lignes arrondies', + 'Flat line ends': + 'Fins de ligne plates', - 'Codification support': - 'Support de la \u00AB codification \u00BB', - 'uncheck to disable\nblock to text mapping features': - 'd\u00E9cocher pour d\u00E9activer\nla fonction de transformation :\nbloc vers texte', - 'check for block\nto text mapping features': - 'cocher pour activer\nla fonction de transformation :\nbloc vers texte', + 'Codification support': + 'Support de la \u00AB codification \u00BB', + 'uncheck to disable\nblock to text mapping features': + 'd\u00E9cocher pour d\u00E9activer\nla fonction de transformation :\nbloc vers texte', + 'check for block\nto text mapping features': + 'cocher pour activer\nla fonction de transformation :\nbloc vers texte', 'Inheritance support': 'Support de l\'h\u00E9ritage', - 'current %dates': - 'date courante %dates', - 'year': + 'current %dates': + 'date courante %dates', + 'year': 'ann\u00E9e', - 'month': + 'month': 'mois', - 'date': + 'date': 'jour', - 'hour': + 'hour': 'heure', - 'minute': + 'minute': 'minute', - 'second': + 'second': 'seconde', - 'time in milliseconds': - 'heure en millisecondes', - 'day of week': - 'jour de la semaine', + 'time in milliseconds': + 'heure en millisecondes', + 'day of week': + 'jour de la semaine', - 'brightness': - 'luminosit\u00E9', - 'transparence': - 'transparence', - 'negative': - 'n\u00E9gatif', - 'comic': - 'bande dessin\u00E9e', + 'brightness': + 'luminosit\u00E9', + 'transparence': + 'transparence', + 'negative': + 'n\u00E9gatif', + 'comic': + 'bande dessin\u00E9e', - 'clicked': - 'cliqu\u00E9', - 'pressed': - 'press\u00E9', - 'dropped': - 'd\u00E9pos\u00E9', - 'mouse-entered': - 'survol\u00E9', - 'mouse-departed': - 'quitt\u00E9', + 'clicked': + 'cliqu\u00E9', + 'pressed': + 'press\u00E9', + 'dropped': + 'd\u00E9pos\u00E9', + 'mouse-entered': + 'survol\u00E9', + 'mouse-departed': + 'quitt\u00E9', 'when %b': 'Quand %b', - 'JavaScript function ( %mult%s ) { %code }': - 'fonction JavaScript ( %mult%s ) { %code }', + 'JavaScript function ( %mult%s ) { %code }': + 'fonction JavaScript ( %mult%s ) { %code }', - // Copy / Paste - 'Press CTRL+C one more time to effectively copy to clipboard.': - 'Taper une nouvelle fois sur CTRL+C pour copier effectivement vers le presse-papier.', - 'Press CTRL+V one more time to effectively paste from clipboard.': - 'Taper une nouvelle fois sur CTRL+V pour coller effectivement depuis le presse-papier.', - 'Press CTRL+X one more time to effectively cut to clipboard.': - 'Taper une nouvelle fois sur CTRL+X pour couper effectivement vers le presse-papier.', + // Copy / Paste + 'Press CTRL+C one more time to effectively copy to clipboard.': + 'Taper une nouvelle fois sur CTRL+C pour copier effectivement vers le presse-papier.', + 'Press CTRL+V one more time to effectively paste from clipboard.': + 'Taper une nouvelle fois sur CTRL+V pour coller effectivement depuis le presse-papier.', + 'Press CTRL+X one more time to effectively cut to clipboard.': + 'Taper une nouvelle fois sur CTRL+X pour couper effectivement vers le presse-papier.', - // Paint.js - 'undo': + // Paint.js + 'undo': 'd\u00E9faire', - 'Paintbrush tool\n(free draw)': - 'Pinceau\n(dessin \u00E0 main lev\u00E9e)', - 'Stroked Rectangle\n(shift: square)': - 'Rectangle\n(Maj : carr\u00E9)', - 'Stroked Ellipse\n(shift: circle)': - 'Ellipse\n(Maj : cercle)', - 'Eraser tool': - 'Gomme', - 'Set the rotation center': - 'Fixer le centre de rotation', - 'Line tool\n(shift: vertical/horizontal)': - 'Ligne\n(Maj: verticale/horizontale)', - 'Filled Rectangle\n(shift: square)': - 'Rectangle plein\n(Maj: carr\u00E9)', - 'Filled Ellipse\n(shift: circle)': - 'Ellipse pleine\n(Maj: cercle)', - 'Fill a region': - 'Remplir une r\u00E9gion', - 'Pipette tool\n(pick a color anywhere)': - 'Pipette\n(s\u00E9lectionnez une couleur n\u0027importe o\u00F9)', - 'grow': + 'Paintbrush tool\n(free draw)': + 'Pinceau\n(dessin \u00E0 main lev\u00E9e)', + 'Stroked Rectangle\n(shift: square)': + 'Rectangle\n(Maj : carr\u00E9)', + 'Stroked Ellipse\n(shift: circle)': + 'Ellipse\n(Maj : cercle)', + 'Eraser tool': + 'Gomme', + 'Set the rotation center': + 'Fixer le centre de rotation', + 'Line tool\n(shift: vertical/horizontal)': + 'Ligne\n(Maj: verticale/horizontale)', + 'Filled Rectangle\n(shift: square)': + 'Rectangle plein\n(Maj: carr\u00E9)', + 'Filled Ellipse\n(shift: circle)': + 'Ellipse pleine\n(Maj: cercle)', + 'Fill a region': + 'Remplir une r\u00E9gion', + 'Pipette tool\n(pick a color anywhere)': + 'Pipette\n(s\u00E9lectionnez une couleur n\u0027importe o\u00F9)', + 'grow': 'agrandir', - 'shrink': + 'shrink': 'r\u00E9duire', - 'flip \u2194': - 'miroir \u2194', - 'flip \u2195': - 'miroir \u2195', - 'Brush size': - 'Taille de pinceau', - 'Constrain proportions of shapes?\n(you can also hold shift)': - 'Contraindre les proportions de la forme ?\n(vous pouvez aussi maintenir appuy\u00E9 Maj)' + 'flip \u2194': + 'miroir \u2194', + 'flip \u2195': + 'miroir \u2195', + 'Brush size': + 'Taille de pinceau', + 'Constrain proportions of shapes?\n(you can also hold shift)': + 'Contraindre les proportions de la forme ?\n(vous pouvez aussi maintenir appuy\u00E9 Maj)' }; diff --git a/locale.js b/locale.js index 5a586e2c..328cdd01 100644 --- a/locale.js +++ b/locale.js @@ -42,7 +42,7 @@ /*global modules, contains*/ -modules.locale = '2016-July-14'; +modules.locale = '2016-October-27'; // Global stuff @@ -160,7 +160,7 @@ SnapTranslator.dict.de = { 'translator_e-mail': 'jens@moenig.org', 'last_changed': - '2016-07-12' + '2016-10-27' }; SnapTranslator.dict.it = { @@ -259,7 +259,7 @@ SnapTranslator.dict.fr = { 'translator_e-mail': 'i.scool@mac.com', 'last_changed': - '2016-02-24' + '2016-10-27' }; SnapTranslator.dict.si = { diff --git a/morphic.js b/morphic.js index 08e41a7b..3dc14eab 100644 --- a/morphic.js +++ b/morphic.js @@ -1103,7 +1103,7 @@ /*global window, HTMLCanvasElement, FileReader, Audio, FileList*/ -var morphicVersion = '2016-August-12'; +var morphicVersion = '2016-October-27'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug @@ -1245,6 +1245,11 @@ function fontHeight(height) { return minHeight * 1.2; // assuming 1/5 font size for ascenders } +function isWordChar(aCharacter) { + // can't use \b or \w because they ignore diacritics + return aCharacter.match(/[A-zÀ-ÿ0-9]/); +} + function newCanvas(extentPoint, nonRetina) { // answer a new empty instance of Canvas, don't display anywhere // nonRetina - optional Boolean "false" @@ -4960,7 +4965,8 @@ CursorMorph.prototype.init = function (aStringOrTextMorph) { CursorMorph.prototype.initializeClipboardHandler = function () { // Add hidden text box for copying and pasting - var myself = this; + var myself = this, + wrrld = this.target.world(); this.clipboardHandler = document.createElement('textarea'); this.clipboardHandler.style.position = 'absolute'; @@ -4986,6 +4992,9 @@ CursorMorph.prototype.initializeClipboardHandler = function () { 'keydown', function (event) { myself.processKeyDown(event); + if (event.shiftKey) { + wrrld.currentKey = 16; + } this.value = myself.target.selection(); this.select(); @@ -4998,7 +5007,15 @@ CursorMorph.prototype.initializeClipboardHandler = function () { }, false ); - + + this.clipboardHandler.addEventListener( + 'keyup', + function (event) { + wrrld.currentKey = null; + }, + false + ); + this.clipboardHandler.addEventListener( 'input', function (event) { @@ -5056,28 +5073,47 @@ CursorMorph.prototype.processKeyPress = function (event) { CursorMorph.prototype.processKeyDown = function (event) { // this.inspectKeyEvent(event); - var shift = event.shiftKey; + var shift = event.shiftKey, + wordNavigation = event.ctrlKey || event.altKey, + selecting = this.target.selection().length > 0; + this.keyDownEventUsed = false; if (event.ctrlKey && (!event.altKey)) { this.ctrl(event.keyCode, event.shiftKey); // notify target's parent of key event this.target.escalateEvent('reactToKeystroke', event); - return; } if (event.metaKey) { this.cmd(event.keyCode, event.shiftKey); // notify target's parent of key event this.target.escalateEvent('reactToKeystroke', event); - return; } switch (event.keyCode) { case 37: - this.goLeft(shift); + if (selecting && !shift && !wordNavigation) { + this.gotoSlot(Math.min(this.target.startMark, this.target.endMark)); + this.target.clearSelection(); + } else { + this.goLeft( + shift, + wordNavigation ? + this.slot - this.target.previousWordFrom(this.slot) + : 1); + } this.keyDownEventUsed = true; break; case 39: - this.goRight(shift); + if (selecting && !shift && !wordNavigation) { + this.gotoSlot(Math.max(this.target.startMark, this.target.endMark)); + this.target.clearSelection(); + } else { + this.goRight( + shift, + wordNavigation ? + this.target.nextWordFrom(this.slot) - this.slot + : 1); + } this.keyDownEventUsed = true; break; case 38: @@ -5168,9 +5204,9 @@ CursorMorph.prototype.gotoSlot = function (slot) { } }; -CursorMorph.prototype.goLeft = function (shift) { +CursorMorph.prototype.goLeft = function (shift, howMany) { this.updateSelection(shift); - this.gotoSlot(this.slot - 1); + this.gotoSlot(this.slot - (howMany || 1)); this.updateSelection(shift); }; @@ -5213,7 +5249,7 @@ CursorMorph.prototype.gotoPos = function (aPoint) { CursorMorph.prototype.updateSelection = function (shift) { if (shift) { - if (!this.target.endMark && !this.target.startMark) { + if (isNil(this.target.endMark) && isNil(this.target.startMark)) { this.target.startMark = this.slot; this.target.endMark = this.slot; } else if (this.target.endMark !== this.slot) { @@ -7793,7 +7829,7 @@ StringMorph.prototype.renderWithBlanks = function (context, startX, y) { }); }; -// StringMorph mesuring: +// StringMorph measuring: StringMorph.prototype.slotPosition = function (slot) { // answer the position point of the given index ("slot") @@ -7818,8 +7854,10 @@ StringMorph.prototype.slotPosition = function (slot) { }; StringMorph.prototype.slotAt = function (aPoint) { - // answer the slot (index) closest to the given point + // answer the slot (index) closest to the given point taking + // in account how far from the middle of the character it is, // so the cursor can be moved accordingly + var txt = this.isPassword ? this.password('*', this.text.length) : this.text, idx = 0, @@ -7837,7 +7875,14 @@ StringMorph.prototype.slotAt = function (aPoint) { } } } - return idx - 1; + + // see where our click fell with respect to the middle of the char + if (aPoint.x - this.left() > + charX - context.measureText(txt[idx - 1]).width / 2) { + return idx; + } else { + return idx - 1; + } }; StringMorph.prototype.upFrom = function (slot) { @@ -7860,6 +7905,41 @@ StringMorph.prototype.endOfLine = function () { return this.text.length; }; +StringMorph.prototype.previousWordFrom = function (aSlot) { + // answer the slot (index) slots indicating the position of the + // previous word to the left of aSlot + var index = aSlot - 1; + + // while the current character is non-word one, we skip it, so that + // if we are in the middle of a non-alphanumeric sequence, we'll get + // right to the beginning of the previous word + while (index > 0 && !isWordChar(this.text[index])) { + index -= 1; + } + + // while the current character is a word one, we skip it until we + // find the beginning of the current word + while (index > 0 && isWordChar(this.text[index - 1])) { + index -= 1; + } + + return index; +}; + +StringMorph.prototype.nextWordFrom = function (aSlot) { + var index = aSlot; + + while (index < this.endOfLine() && !isWordChar(this.text[index])) { + index += 1; + } + + while (index < this.endOfLine() && isWordChar(this.text[index])) { + index += 1; + } + + return index; +}; + StringMorph.prototype.rawHeight = function () { // answer my corrected fontSize return this.height() / 1.2; @@ -8025,13 +8105,13 @@ StringMorph.prototype.selectionStartSlot = function () { StringMorph.prototype.clearSelection = function () { if (!this.currentlySelecting && - this.startMark === 0 && - this.endMark === 0) { + isNil(this.startMark) && + isNil(this.endMark)) { return; } this.currentlySelecting = false; - this.startMark = 0; - this.endMark = 0; + this.startMark = null; + this.endMark = null; this.drawNew(); this.changed(); }; @@ -8047,8 +8127,13 @@ StringMorph.prototype.deleteSelection = function () { }; StringMorph.prototype.selectAll = function () { + var cursor; if (this.isEditable) { this.startMark = 0; + cursor = this.root().cursor; + if (cursor) { + cursor.gotoSlot(this.text.length); + } this.endMark = this.text.length; this.drawNew(); this.changed(); @@ -8056,13 +8141,31 @@ StringMorph.prototype.selectAll = function () { }; StringMorph.prototype.mouseDownLeft = function (pos) { - if (this.isEditable) { + if (this.world().currentKey === 16) { + this.shiftClick(pos); + } else if (this.isEditable) { this.clearSelection(); } else { this.escalateEvent('mouseDownLeft', pos); } }; +StringMorph.prototype.shiftClick = function (pos) { + var cursor = this.root().cursor; + + if (cursor) { + if (!this.startMark) { + this.startMark = cursor.slot; + } + cursor.gotoPos(pos); + this.endMark = cursor.slot; + this.drawNew(); + this.changed(); + } + this.currentlySelecting = false; + this.escalateEvent('mouseDownLeft', pos); +}; + StringMorph.prototype.mouseClickLeft = function (pos) { var cursor; if (this.isEditable) { @@ -8079,18 +8182,79 @@ StringMorph.prototype.mouseClickLeft = function (pos) { } }; +StringMorph.prototype.mouseDoubleClick = function (pos) { + // selects the word at pos + // if there is no word, we select whatever is between + // the previous and next words + var slot = this.slotAt(pos); + + if (this.isEditable) { + this.edit(); + + if (slot === this.text.length) { + slot -= 1; + } + + if (isWordChar(this.text[slot])) { + this.selectWordAt(slot); + } else { + this.selectBetweenWordsAt(slot); + } + } else { + this.escalateEvent('mouseDoubleClick', pos); + } + +}; + +StringMorph.prototype.selectWordAt = function (slot) { + var cursor = this.root().cursor; + + if (slot === 0 || isWordChar(this.text[slot - 1])) { + cursor.gotoSlot(this.previousWordFrom(slot)); + this.startMark = cursor.slot; + this.endMark = this.nextWordFrom(cursor.slot); + } else { + cursor.gotoSlot(slot); + this.startMark = slot; + this.endMark = this.nextWordFrom(slot); + } + + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.selectBetweenWordsAt = function (slot) { + var cursor = this.root().cursor; + + cursor.gotoSlot(this.nextWordFrom(this.previousWordFrom(slot))); + this.startMark = cursor.slot; + this.endMark = cursor.slot; + + while (this.endMark < this.text.length + && !isWordChar(this.text[this.endMark])) { + this.endMark += 1; + } + + this.drawNew(); + this.changed(); +}; + StringMorph.prototype.enableSelecting = function () { this.mouseDownLeft = function (pos) { var crs = this.root().cursor, already = crs ? crs.target === this : false; - this.clearSelection(); - if (this.isEditable && (!this.isDraggable)) { - this.edit(); - this.root().cursor.gotoPos(pos); - this.startMark = this.slotAt(pos); - this.endMark = this.startMark; - this.currentlySelecting = true; - if (!already) {this.escalateEvent('mouseDownLeft', pos); } + if (this.world().currentKey === 16) { + this.shiftClick(pos); + } else { + this.clearSelection(); + if (this.isEditable && (!this.isDraggable)) { + this.edit(); + this.root().cursor.gotoPos(pos); + this.startMark = this.slotAt(pos); + this.endMark = this.startMark; + this.currentlySelecting = true; + if (!already) {this.escalateEvent('mouseDownLeft', pos); } + } } }; this.mouseMove = function (pos) { @@ -8410,8 +8574,10 @@ TextMorph.prototype.slotPosition = function (slot) { }; TextMorph.prototype.slotAt = function (aPoint) { - // answer the slot (index) closest to the given point + // answer the slot (index) closest to the given point taking + // in account how far from the middle of the character it is, // so the cursor can be moved accordingly + var charX = 0, row = 0, col = 0, @@ -8423,11 +8589,19 @@ TextMorph.prototype.slotAt = function (aPoint) { row += 1; } row = Math.max(row, 1); + while (aPoint.x - this.left() > charX) { charX += context.measureText(this.lines[row - 1][col]).width; col += 1; } - return this.lineSlots[Math.max(row - 1, 0)] + col - 1; + + // see where our click fell with respect to the middle of the char + if (aPoint.x - this.left() > + charX - context.measureText(this.lines[row - 1][col]).width / 2) { + return this.lineSlots[Math.max(row - 1, 0)] + col; + } else { + return this.lineSlots[Math.max(row - 1, 0)] + col - 1; + } }; TextMorph.prototype.upFrom = function (slot) { @@ -8469,6 +8643,10 @@ TextMorph.prototype.endOfLine = function (slot) { this.lines[this.columnRow(slot).y].length - 1; }; +TextMorph.prototype.previousWordFrom = StringMorph.prototype.previousWordFrom; + +TextMorph.prototype.nextWordFrom = StringMorph.prototype.nextWordFrom; + // TextMorph editing: TextMorph.prototype.edit = StringMorph.prototype.edit; @@ -8486,8 +8664,17 @@ TextMorph.prototype.selectAll = StringMorph.prototype.selectAll; TextMorph.prototype.mouseDownLeft = StringMorph.prototype.mouseDownLeft; +TextMorph.prototype.shiftClick = StringMorph.prototype.shiftClick; + TextMorph.prototype.mouseClickLeft = StringMorph.prototype.mouseClickLeft; +TextMorph.prototype.mouseDoubleClick = StringMorph.prototype.mouseDoubleClick; + +TextMorph.prototype.selectWordAt = StringMorph.prototype.selectWordAt; + +TextMorph.prototype.selectBetweenWordsAt + = StringMorph.prototype.selectBetweenWordsAt; + TextMorph.prototype.enableSelecting = StringMorph.prototype.enableSelecting; TextMorph.prototype.disableSelecting = StringMorph.prototype.disableSelecting; @@ -9255,7 +9442,8 @@ ScrollFrameMorph.prototype.init = function (scroller, size, sliderColor) { ScrollFrameMorph.uber.init.call(this); this.scrollBarSize = size || MorphicPreferences.scrollBarSize; this.autoScrollTrigger = null; - this.isScrollingByDragging = true; // change if desired + this.enableAutoScrolling = true; // change to suppress + this.isScrollingByDragging = true; // change to suppress this.hasVelocity = true; // dto. this.padding = 0; // around the scrollable area this.growth = 0; // pixels or Point to grow right/left when near edge @@ -10300,7 +10488,12 @@ HandMorph.prototype.processMouseMove = function (event) { // autoScrolling support: if (myself.children.length > 0) { - if (newMorph instanceof ScrollFrameMorph) { + if (newMorph instanceof ScrollFrameMorph && + newMorph.enableAutoScrolling && + newMorph.contents.allChildren().some(function (any) { + return any.wantsDropOf(myself.children[0]); + }) + ) { if (!newMorph.bounds.insetBy( MorphicPreferences.scrollBarSize * 3 ).containsPoint(myself.bounds.origin)) { @@ -10563,8 +10756,8 @@ WorldMorph.prototype.init = function (aCanvas, fillPage) { this.broken = []; this.hand = new HandMorph(this); this.keyboardReceiver = null; - this.lastEditedText = null; this.cursor = null; + this.lastEditedText = null; this.activeMenu = null; this.activeHandle = null; this.virtualKeyboard = null; @@ -11328,9 +11521,6 @@ WorldMorph.prototype.edit = function (aStringOrTextMorph) { if (this.cursor) { this.cursor.destroy(); } - if (this.lastEditedText) { - this.lastEditedText.clearSelection(); - } this.cursor = new CursorMorph(aStringOrTextMorph); aStringOrTextMorph.parent.add(this.cursor); this.keyboardReceiver = this.cursor; @@ -11348,6 +11538,11 @@ WorldMorph.prototype.edit = function (aStringOrTextMorph) { this.slide(aStringOrTextMorph); } } + + if (this.lastEditedText !== aStringOrTextMorph) { + aStringOrTextMorph.escalateEvent('freshTextEdit', aStringOrTextMorph); + } + this.lastEditedText = aStringOrTextMorph; }; WorldMorph.prototype.slide = function (aStringOrTextMorph) { @@ -11393,10 +11588,10 @@ WorldMorph.prototype.slide = function (aStringOrTextMorph) { WorldMorph.prototype.stopEditing = function () { if (this.cursor) { - this.lastEditedText = this.cursor.target; + this.cursor.target.escalateEvent('reactToEdit', this.cursor.target); + this.cursor.target.clearSelection(); this.cursor.destroy(); this.cursor = null; - this.lastEditedText.escalateEvent('reactToEdit', this.lastEditedText); } this.keyboardReceiver = null; if (this.virtualKeyboard) { @@ -11404,6 +11599,7 @@ WorldMorph.prototype.stopEditing = function () { document.body.removeChild(this.virtualKeyboard); this.virtualKeyboard = null; } + this.lastEditedText = null; this.worldCanvas.focus(); }; diff --git a/objects.js b/objects.js index 56a9e1bc..e4b68ce1 100644 --- a/objects.js +++ b/objects.js @@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph, BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize, TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph*/ -modules.objects = '2016-July-19'; +modules.objects = '2016-October-27'; var SpriteMorph; var StageMorph; @@ -1409,9 +1409,11 @@ SpriteMorph.prototype.fullCopy = function (forClone) { c.costumes = new List(arr); arr = []; this.sounds.asArray().forEach(function (sound) { - arr.push(sound); + var snd = forClone ? sound : sound.copy(); + arr.push(snd); }); c.sounds = new List(arr); + arr = []; c.nestingScale = 1; c.rotatesWithAnchor = true; c.anchor = null; @@ -2950,8 +2952,10 @@ SpriteMorph.prototype.setColor = function (aColor) { y = this.yPosition(); if (!this.color.eq(aColor)) { this.color = aColor.copy(); - this.drawNew(); - this.gotoXY(x, y); + if (!this.costume) { + this.drawNew(); + this.silentGotoXY(x, y); + } } }; @@ -3050,17 +3054,21 @@ SpriteMorph.prototype.overlappingImage = function (otherSprite) { SpriteMorph.prototype.doStamp = function () { var stage = this.parent, context = stage.penTrails().getContext('2d'), - isWarped = this.isWarped; + isWarped = this.isWarped, + originalAlpha = context.globalAlpha; + if (isWarped) { this.endWarp(); } context.save(); context.scale(1 / stage.scale, 1 / stage.scale); + context.globalAlpha = this.alpha; context.drawImage( this.image, (this.left() - stage.left()), (this.top() - stage.top()) ); + context.globalAlpha = originalAlpha; context.restore(); this.changed(); if (isWarped) { @@ -4000,14 +4008,33 @@ SpriteMorph.prototype.yCenter = function () { // SpriteMorph message broadcasting SpriteMorph.prototype.allMessageNames = function () { - var msgs = []; - this.scripts.allChildren().forEach(function (morph) { - var txt; - if (morph.selector) { - if (contains( - ['receiveMessage', 'doBroadcast', 'doBroadcastAndWait'], - morph.selector - )) { + var msgs = [], + all = this.scripts.children.slice(); + this.customBlocks.forEach(function (def) { + if (def.body) { + all.push(def.body.expression); + } + def.scripts.forEach(function (scr) { + all.push(scr); + }); + }); + if (this.globalBlocks) { + this.globalBlocks.forEach(function (def) { + if (def.body) { + all.push(def.body.expression); + } + def.scripts.forEach(function (scr) { + all.push(scr); + }); + }); + } + all.forEach(function (script) { + script.allChildren().forEach(function (morph) { + var txt; + if (morph.selector && contains( + ['receiveMessage', 'doBroadcast', 'doBroadcastAndWait'], + morph.selector + )) { txt = morph.inputs()[0].evaluate(); if (isString(txt) && txt !== '') { if (!contains(msgs, txt)) { @@ -4015,7 +4042,7 @@ SpriteMorph.prototype.allMessageNames = function () { } } } - } + }); }); return msgs; }; @@ -5435,7 +5462,7 @@ StageMorph.prototype.reactToDropOf = function (morph, hand) { // StageMorph stepping StageMorph.prototype.step = function () { - var current, elapsed, leftover, world = this.world(); + var current, elapsed, leftover, ide, world = this.world(); // handle keyboard events if (world.keyboardReceiver === null) { @@ -5471,6 +5498,14 @@ StageMorph.prototype.step = function () { this.changed(); } else { this.threads.step(); + + // single-stepping hook: + if (this.threads.wantsToPause) { + ide = this.parentThatIsA(IDE_Morph); + if (ide) { + ide.controlBar.pauseButton.refresh(); + } + } } // update watchers @@ -6938,13 +6973,13 @@ Costume.prototype.edit = function (aWorld, anIDE, isnew, oncancel, onsubmit) { function (img, rc) { myself.contents = img; myself.rotationCenter = rc; - if (anIDE.currentSprite instanceof SpriteMorph) { - // don't shrinkwrap stage costumes - myself.shrinkWrap(); - } myself.version = Date.now(); aWorld.changed(); if (anIDE) { + if (anIDE.currentSprite instanceof SpriteMorph) { + // don't shrinkwrap stage costumes + myself.shrinkWrap(); + } anIDE.currentSprite.wearCostume(myself); anIDE.hasChangedMedia = true; } diff --git a/store.js b/store.js index 22b0fcfc..344ed8cb 100644 --- a/store.js +++ b/store.js @@ -61,7 +61,7 @@ normalizeCanvas*/ // Global stuff //////////////////////////////////////////////////////// -modules.store = '2016-August-03'; +modules.store = '2016-October-27'; // XML_Serializer /////////////////////////////////////////////////////// @@ -630,8 +630,8 @@ SnapSerializer.prototype.loadSprites = function (xmlString, ide) { myself.objects[model.attributes.id] = sprite; } if (model.attributes.name) { - sprite.name = model.attributes.name; - project.sprites[model.attributes.name] = sprite; + sprite.name = ide.newSpriteName(model.attributes.name); + project.sprites[sprite.name] = sprite; } if (model.attributes.color) { sprite.color = myself.loadColor(model.attributes.color); @@ -1619,8 +1619,8 @@ Costume.prototype.toXML = function (serializer) { this.name, this.rotationCenter.x, this.rotationCenter.y, - this instanceof SVG_Costume ? - this.contents.src : this.contents.toDataURL('image/png') + this instanceof SVG_Costume ? this.contents.src + : normalizeCanvas(this.contents).toDataURL('image/png') ); }; diff --git a/threads.js b/threads.js index 013449e7..f19773f8 100644 --- a/threads.js +++ b/threads.js @@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy, isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph, TableFrameMorph, isSnapObject*/ -modules.threads = '2016-August-12'; +modules.threads = '2016-October-27'; var ThreadManager; var Process; @@ -175,6 +175,7 @@ function invoke( function ThreadManager() { this.processes = []; + this.wantsToPause = false; // single stepping support } ThreadManager.prototype.pauseCustomHatBlocks = false; @@ -275,6 +276,25 @@ ThreadManager.prototype.step = function () { // for sprites that are currently picked up, then filter out any // processes that have been terminated + var isInterrupted; + if (Process.prototype.enableSingleStepping) { + this.processes.forEach(function (proc) { + if (proc.isInterrupted) { + proc.runStep(); + isInterrupted = true; + } else { + proc.lastYield = Date.now(); + } + }); + this.wantsToPause = (Process.prototype.flashTime > 0.5); + if (isInterrupted) { + if (this.wantsToPause) { + this.pauseAll(); + } + return; + } + } + this.processes.forEach(function (proc) { if (!proc.homeContext.receiver.isPickedUp() && !proc.isDead) { proc.runStep(); @@ -290,6 +310,7 @@ ThreadManager.prototype.removeTerminatedProcesses = function () { var result; if ((!proc.isRunning() && !proc.errorFlag) || proc.isDead) { if (proc.topBlock instanceof BlockMorph) { + proc.unflash(); proc.topBlock.removeHighlight(); } if (proc.prompter) { @@ -371,6 +392,18 @@ ThreadManager.prototype.doWhen = function (block, stopIt) { } }; +ThreadManager.prototype.toggleSingleStepping = function () { + Process.prototype.enableSingleStepping = + !Process.prototype.enableSingleStepping; + if (!Process.prototype.enableSingleStepping) { + this.processes.forEach(function (proc) { + if (!proc.isPaused) { + proc.unflash(); + } + }); + } +}; + // Process ///////////////////////////////////////////////////////////// /* @@ -429,6 +462,8 @@ ThreadManager.prototype.doWhen = function (block, stopIt) { procedureCount number counting procedure call entries, used to tag custom block calls, so "stop block" invocations can catch them + flashingContext for single stepping + isInterrupted boolean, indicates intra-step flashing of blocks */ Process.prototype = {}; @@ -436,6 +471,8 @@ Process.prototype.constructor = Process; Process.prototype.timeout = 500; // msecs after which to force yield Process.prototype.isCatchingErrors = true; Process.prototype.enableLiveCoding = false; // experimental +Process.prototype.enableSingleStepping = false; // experimental +Process.prototype.flashTime = 0; // experimental function Process(topBlock, onComplete, rightAway) { this.topBlock = topBlock || null; @@ -459,6 +496,8 @@ function Process(topBlock, onComplete, rightAway) { this.exportResult = false; this.onComplete = onComplete || null; this.procedureCount = 0; + this.flashingContext = null; // experimental, for single-stepping + this.isInterrupted = false; // experimental, for single-stepping if (topBlock) { this.homeContext.receiver = topBlock.receiver(); @@ -490,9 +529,10 @@ Process.prototype.runStep = function (deadline) { if (this.isPaused) { // allow pausing in between atomic steps: return this.pauseStep(); } - this.readyToYield = false; - while (!this.readyToYield + this.isInterrupted = false; + + while (!this.readyToYield && !this.isInterrupted && this.context && (Date.now() - this.lastYield < this.timeout) ) { @@ -510,6 +550,7 @@ Process.prototype.runStep = function (deadline) { } this.evaluateContext(); } + this.lastYield = Date.now(); this.isFirstStep = false; @@ -544,13 +585,20 @@ Process.prototype.stop = function () { }; Process.prototype.pause = function () { + if (this.readyToTerminate) { + return; + } this.isPaused = true; + this.flashPausedContext(); if (this.context && this.context.startTime) { this.pauseOffset = Date.now() - this.context.startTime; } }; Process.prototype.resume = function () { + if (!this.enableSingleStepping) { + this.unflash(); + } this.isPaused = false; this.pauseOffset = null; }; @@ -586,7 +634,7 @@ Process.prototype.evaluateContext = function () { return this.evaluateBlock(exp, exp.inputs().length); } if (isString(exp)) { - return this[exp](); + return this[exp].apply(this, this.context.inputs); } this.popContext(); // default: just ignore it }; @@ -607,6 +655,7 @@ Process.prototype.evaluateBlock = function (block, argCount) { if (argCount > inputs.length) { this.evaluateNextInput(block); } else { + if (this.flashContext()) {return; } // yield to flash the block if (this[selector]) { rcvr = this; } @@ -636,11 +685,13 @@ Process.prototype.reportOr = function (block) { if (inputs.length < 1) { this.evaluateNextInput(block); } else if (inputs[0]) { + if (this.flashContext()) {return; } this.returnValueToParentContext(true); this.popContext(); } else if (inputs.length < 2) { this.evaluateNextInput(block); } else { + if (this.flashContext()) {return; } this.returnValueToParentContext(inputs[1] === true); this.popContext(); } @@ -652,11 +703,13 @@ Process.prototype.reportAnd = function (block) { if (inputs.length < 1) { this.evaluateNextInput(block); } else if (!inputs[0]) { + if (this.flashContext()) {return; } this.returnValueToParentContext(false); this.popContext(); } else if (inputs.length < 2) { this.evaluateNextInput(block); } else { + if (this.flashContext()) {return; } this.returnValueToParentContext(inputs[1] === true); this.popContext(); } @@ -664,6 +717,7 @@ Process.prototype.reportAnd = function (block) { Process.prototype.doReport = function (block) { var outer = this.context.outerContext; + if (this.flashContext()) {return; } // flash the block here, special form if (this.isClicked && (block.topBlock() === this.topBlock)) { this.isShowingResult = true; } @@ -736,6 +790,7 @@ Process.prototype.evaluateArgLabel = function (argLabel) { Process.prototype.evaluateInput = function (input) { // evaluate the input unless it is bound to an implicit parameter var ans; + if (this.flashContext()) {return; } // yield to flash the current argMorph if (input.bindingID) { if (this.isCatchingErrors) { try { @@ -879,7 +934,8 @@ Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) { i = 0; if (topBlock) { - context.expression = this.enableLiveCoding ? + context.expression = this.enableLiveCoding || + this.enableSingleStepping ? topBlock : topBlock.fullCopy(); context.expression.show(); // be sure to make visible if in app mode @@ -898,7 +954,8 @@ Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) { } } else { - context.expression = this.enableLiveCoding ? [this.context.expression] + context.expression = this.enableLiveCoding || + this.enableSingleStepping ? [this.context.expression] : [this.context.expression.fullCopy()]; } @@ -1182,6 +1239,15 @@ Process.prototype.reportCallCC = function (aContext) { Process.prototype.runContinuation = function (aContext, args) { var parms = args.asArray(); + + // determine whether the continuations is to show the result + // in a value-balloon becuse the user has directly clicked on a reporter + if (aContext.expression === 'expectReport' && parms.length) { + this.stop(); + this.homeContext.inputs[0] = parms[0]; + return; + } + this.context.parentContext = aContext.copyForContinuationCall(); // passing parameter if any was passed if (parms.length === 1) { @@ -1286,8 +1352,9 @@ Process.prototype.evaluateCustomBlock = function () { if (caller && !caller.tag) { caller.tag = this.procedureCount; } - // yield commands unless explicitly "warped" - if (!this.isAtomic) { + // yield commands unless explicitly "warped" or directly recursive + if (!this.isAtomic && + this.context.expression.definition.isDirectlyRecursive()) { this.readyToYield = true; } } @@ -1911,6 +1978,11 @@ Process.prototype.doWait = function (secs) { this.context.startTime = Date.now(); } if ((Date.now() - this.context.startTime) >= (secs * 1000)) { + if (!this.isAtomic && (secs === 0)) { + // "wait 0 secs" is a plain "yield" + // that can be overridden by "warp" + this.readyToYield = true; + } return null; } this.pushContext('doYield'); @@ -3276,6 +3348,67 @@ Process.prototype.reportFrameCount = function () { return this.frameCount; }; +// Process single-stepping + +Process.prototype.flashContext = function () { + var expr = this.context.expression; + if (this.enableSingleStepping && + !this.isAtomic && + expr instanceof SyntaxElementMorph && + !(expr instanceof CommandSlotMorph) && + !this.context.isFlashing && + expr.world()) { + this.unflash(); + expr.flash(); + this.context.isFlashing = true; + this.flashingContext = this.context; + if (this.flashTime > 0 && (this.flashTime <= 0.5)) { + this.pushContext('doIdle'); + this.context.addInput(this.flashTime); + } else { + this.pushContext('doInterrupt'); + } + return true; + } + return false; +}; + +Process.prototype.flashPausedContext = function () { + var flashable = this.context ? this.context.lastFlashable() : null; + if (flashable) { + this.unflash(); + flashable.expression.flash(); + flashable.isFlashing = true; + this.flashingContext = flashable; + } +}; + +Process.prototype.doInterrupt = function () { + this.popContext(); + if (!this.isAtomic) { + this.isInterrupted = true; + } +}; + +Process.prototype.doIdle = function (secs) { + if (!this.context.startTime) { + this.context.startTime = Date.now(); + } + if ((Date.now() - this.context.startTime) < (secs * 1000)) { + this.pushContext('doInterrupt'); + return; + } + this.popContext(); +}; + +Process.prototype.unflash = function () { + if (this.flashingContext) { + this.flashingContext.expression.unflash(); + this.flashingContext.isFlashing = false; + this.flashingContext = null; + } +}; + // Context ///////////////////////////////////////////////////////////// /* @@ -3298,6 +3431,7 @@ Process.prototype.reportFrameCount = function () { (if expression is a BlockMorph) pc the index of the next block to evaluate (if expression is an array) + isContinuation flag for marking a transient continuation context startTime time when the context was first evaluated startValue initial value for interpolated operations activeAudio audio buffer for interpolated operations, don't persist @@ -3306,6 +3440,7 @@ Process.prototype.reportFrameCount = function () { emptySlots caches the number of empty slots for reification tag string or number to optionally identify the Context, as a "return" target (for the "stop block" primitive) + isFlashing flag for single-stepping */ function Context( @@ -3325,12 +3460,14 @@ function Context( } this.inputs = []; this.pc = 0; + this.isContinuation = false; this.startTime = null; this.activeAudio = null; this.activeNote = null; this.isCustomBlock = false; // marks the end of a custom block's stack this.emptySlots = 0; // used for block reification this.tag = null; // lexical catch-tag for custom blocks + this.isFlashing = false; // for single-stepping } Context.prototype.toString = function () { @@ -3394,7 +3531,9 @@ Context.prototype.continuation = function () { } else if (this.parentContext) { cont = this.parentContext; } else { - return new Context(null, 'doStop'); + cont = new Context(null, 'expectReport'); + cont.isContinuation = true; + return cont; } cont = cont.copyForContinuation(); cont.tag = null; @@ -3464,6 +3603,19 @@ Context.prototype.stopMusic = function () { } }; +// Context single-stepping: + +Context.prototype.lastFlashable = function () { + // for experimental single-stepping when pausing + if (this.expression instanceof SyntaxElementMorph && + !(this.expression instanceof CommandSlotMorph)) { + return this; + } else if (this.parentContext) { + return this.parentContext.lastFlashable(); + } + return null; +}; + // Context debugging Context.prototype.stackSize = function () { diff --git a/tools.xml b/tools.xml index 5bb5e200..b19af1e7 100644 --- a/tools.xml +++ b/tools.xml @@ -1 +1 @@ -LABEL will stamp text on the stage at the given font size. The direction of the text is the direction the sprite is facing, and color will match the pen color.
Hello!12
1datamapmany1data lists
1
1
110i
1
cont
catchtag
cont
catchtag
Sprite
Sprite
\ No newline at end of file +LABEL will stamp text on the stage at the given font size. The direction of the text is the direction the sprite is facing, and color will match the pen color.
Hello!12
1datamapmany1data lists
1
1
110i
121
cont
catchtag
cont
catchtag
Sprite
Sprite
\ No newline at end of file