diff --git a/Backgrounds/BACKGROUNDS b/Backgrounds/BACKGROUNDS index 9492cffc..668eb4eb 100644 --- a/Backgrounds/BACKGROUNDS +++ b/Backgrounds/BACKGROUNDS @@ -1,12 +1,12 @@ -atom-playground.jpg Atom Playground +atom_playground.jpg Atom Playground bedroom1.gif Bedroom 1 bedroom2.gif Bedroom 2 -berkeley-mural.jpg Berkeley Mural +berkeley_mural.jpg Berkeley Mural brick-wall-and-stairs.jpg Brick Wall and Stairs brick-wall1.jpg Brick Wall 1 brick-wall2.jpg Brick Wall 2 desert.gif Desert -night-city-with-street.gif Night City with Street -party-room.jpg Party Room +night_city_with_street.gif Night City with Street +party_room.jpg Party Room pathway.jpg Pathway xy-grid.gif XY Grid diff --git a/Backgrounds/atom-playground.jpg b/Backgrounds/atom-playground.jpg deleted file mode 100644 index 94b953ea..00000000 Binary files a/Backgrounds/atom-playground.jpg and /dev/null differ diff --git a/Backgrounds/berkeley-mural.jpg b/Backgrounds/berkeley-mural.jpg deleted file mode 100644 index 2df7a430..00000000 Binary files a/Backgrounds/berkeley-mural.jpg and /dev/null differ diff --git a/Backgrounds/brick-wall-and-stairs.jpg b/Backgrounds/brick-wall-and-stairs.jpg deleted file mode 100644 index 78e412d4..00000000 Binary files a/Backgrounds/brick-wall-and-stairs.jpg and /dev/null differ diff --git a/Backgrounds/night-city-with-street.gif b/Backgrounds/night-city-with-street.gif deleted file mode 100644 index 91e61851..00000000 Binary files a/Backgrounds/night-city-with-street.gif and /dev/null differ diff --git a/Backgrounds/party-room.jpg b/Backgrounds/party-room.jpg deleted file mode 100644 index f253c9f3..00000000 Binary files a/Backgrounds/party-room.jpg and /dev/null differ diff --git a/blocks.js b/blocks.js index 6ad59843..8acfed93 100644 --- a/blocks.js +++ b/blocks.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2016 by Jens Mönig + Copyright (C) 2017 by Jens Mönig This file is part of Snap!. @@ -149,7 +149,7 @@ isSnapObject, copy, PushButtonMorph, SpriteIconMorph, Process, AlignmentMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2016-December-27'; +modules.blocks = '2017-January-03'; var SyntaxElementMorph; var BlockMorph; @@ -5914,7 +5914,7 @@ ScriptsMorph.prototype.userMenu = function () { if (this.dropRecord) { if (this.dropRecord.lastRecord) { hasUndropQueue = true; - menu.addItem( + menu.addPair( [ new SymbolMorph( 'turnBack', @@ -5923,12 +5923,13 @@ ScriptsMorph.prototype.userMenu = function () { localize('undrop') ], 'undrop', + '⌘Z', 'undo the last\nblock drop\nin this pane' ); } if (this.dropRecord.nextRecord) { hasUndropQueue = true; - menu.addItem( + menu.addPair( [ new SymbolMorph( 'turnForward', @@ -5937,6 +5938,7 @@ ScriptsMorph.prototype.userMenu = function () { localize('redrop') ], 'redrop', + '⌘Y', 'redo the last undone\nblock drop\nin this pane' ); } @@ -7722,8 +7724,19 @@ InputSlotMorph.prototype.setContents = function (aStringOrFloat) { // InputSlotMorph drop-down menu: InputSlotMorph.prototype.dropDownMenu = function (enableKeyboard) { - var choices = this.choices, - key, + var menu = this.menuFromDict(this.choices); + if (menu.items.length > 0) { + if (enableKeyboard) { + menu.popup(this.world(), this.bottomLeft()); + menu.getFocus(); + } else { + menu.popUpAtHand(this.world()); + } + } +}; + +InputSlotMorph.prototype.menuFromDict = function (choices, noEmptyOption) { + var key, menu = new MenuMorph( this.setContents, null, @@ -7736,31 +7749,24 @@ InputSlotMorph.prototype.dropDownMenu = function (enableKeyboard) { } else if (isString(choices)) { choices = this[choices](); } - if (!choices) { - return null; + if (!noEmptyOption) { + menu.addItem(' ', null); } - menu.addItem(' ', null); for (key in choices) { if (Object.prototype.hasOwnProperty.call(choices, key)) { if (key[0] === '~') { menu.addLine(); // } else if (key.indexOf('§_def') === 0) { // menu.addItem(choices[key].blockInstance(), choices[key]); + } else if (choices[key] instanceof Object && + !(choices[key] instanceof Array)) { + menu.addMenu(key, this.menuFromDict(choices[key], true)); } else { menu.addItem(key, choices[key]); } } } - if (menu.items.length > 0) { - if (enableKeyboard) { - menu.popup(this.world(), this.bottomLeft()); - menu.getFocus(); - } else { - menu.popUpAtHand(this.world()); - } - } else { - return null; - } + return menu; }; InputSlotMorph.prototype.messagesMenu = function () { @@ -7793,7 +7799,6 @@ InputSlotMorph.prototype.messagesMenu = function () { myself.world() ); }; - return dict; }; @@ -7825,7 +7830,6 @@ InputSlotMorph.prototype.messagesReceivedMenu = function () { myself.world() ); }; - return dict; }; diff --git a/byob.js b/byob.js index d976917b..8652e53e 100644 --- a/byob.js +++ b/byob.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2016 by Jens Mönig + Copyright (C) 2017 by Jens Mönig This file is part of Snap!. @@ -107,7 +107,7 @@ SymbolMorph, isNil, CursorMorph, VariableFrame, WatcherMorph, Variable*/ // Global stuff //////////////////////////////////////////////////////// -modules.byob = '2016-December-29'; +modules.byob = '2017-January-03'; // Declarations @@ -291,17 +291,31 @@ CustomBlockDefinition.prototype.inputOptionsOfIdx = function (idx) { }; CustomBlockDefinition.prototype.dropDownMenuOf = function (inputName) { - var dict = {}; if (this.declarations[inputName] && this.declarations[inputName][2]) { - this.declarations[inputName][2].split('\n').forEach(function (line) { - var pair = line.split('='); - dict[pair[0]] = isNil(pair[1]) ? pair[0] : pair[1]; - }); - return dict; + return this.parseChoices(this.declarations[inputName][2]); } return null; }; +CustomBlockDefinition.prototype.parseChoices = function (string) { + var dict = {}, + stack = [dict]; + string.split('\n').forEach(function (line) { + var pair = line.split('='); + if (pair[0] === '}') { + stack.pop(); + dict = stack[stack.length - 1]; + } else if (pair[1] === '{') { + dict = {}; + stack[stack.length - 1][pair[0]] = dict; + stack.push(dict); + } else { + dict[pair[0]] = isNil(pair[1]) ? pair[0] : pair[1]; + } + }); + return dict; +}; + CustomBlockDefinition.prototype.isReadOnlyInput = function (inputName) { return this.declarations[inputName] && this.declarations[inputName][3] === true; @@ -945,47 +959,6 @@ CustomCommandBlockMorph.prototype.deleteBlockDefinition = function () { ); }; -// CustomCommandBlockMorph events: - -// hover help - commented out for now -/* - -CustomCommandBlockMorph.prototype.mouseEnter = function () { - var comment, help; - if (this.isTemplate && this.definition.comment) { - comment = this.definition.comment.fullCopy(); - comment.contents.parse(); - help = ''; - comment.contents.lines.forEach(function (line) { - help = help + '\n' + line; - }); - this.popUpbubbleHelp( - help.substr(1), - this.definition.comment.color - ); - } -}; - -CustomCommandBlockMorph.prototype.mouseLeave = function () { - if (this.isTemplate && this.definition.comment) { - this.world().hand.destroyTemporaries(); - } -}; - -CustomCommandBlockMorph.prototype.popUpbubbleHelp = function ( - contents, - color -) { - new SpeechBubbleMorph( - contents, - color, - null, - 1 - ).popUp(this.world(), this.rightCenter().add(new Point(-8, 0))); -}; - -*/ - // CustomCommandBlockMorph relabelling CustomCommandBlockMorph.prototype.relabel = function (alternatives) { @@ -3254,8 +3227,9 @@ InputSlotDialogMorph.prototype.editSlotOptions = function () { myself.fragment.options, myself.world(), null, - localize('Enter one option per line.' + - 'Optionally use "=" as key/value delimiter\n' + + localize('Enter one option per line.\n' + + 'Optionally use "=" as key/value delimiter ' + + 'and {} for submenus. ' + 'e.g.\n the answer=42') ); }; diff --git a/gui.js b/gui.js index a102572d..e7387ea9 100644 --- a/gui.js +++ b/gui.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2016 by Jens Mönig + Copyright (C) 2017 by Jens Mönig This file is part of Snap!. @@ -72,7 +72,7 @@ isRetinaSupported, SliderMorph, Animation*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2016-December-29'; +modules.gui = '2017-January-03'; // Declarations @@ -2798,9 +2798,9 @@ IDE_Morph.prototype.projectMenu = function () { menu = new MenuMorph(this); menu.addItem('Project notes...', 'editProjectNotes'); menu.addLine(); - menu.addItem('New', 'createNewProject'); - menu.addItem('Open...', 'openProjectsBrowser'); - menu.addItem('Save', "save"); + menu.addPair('New', 'createNewProject', '⌘N'); + menu.addPair('Open...', 'openProjectsBrowser', '⌘O'); + menu.addPair('Save', "save", '⌘S'); menu.addItem('Save As...', 'saveProjectsBrowser'); menu.addLine(); menu.addItem( @@ -3209,7 +3209,7 @@ IDE_Morph.prototype.aboutSnap = function () { world = this.world(); aboutTxt = 'Snap! 4.0.10 - dev -\nBuild Your Own Blocks\n\n' - + 'Copyright \u24B8 2016 Jens M\u00F6nig and ' + + 'Copyright \u24B8 2017 Jens M\u00F6nig and ' + 'Brian Harvey\n' + 'jens@moenig.org, bh@cs.berkeley.edu\n\n' diff --git a/history.txt b/history.txt index e86ebac6..e34d5dca 100755 --- a/history.txt +++ b/history.txt @@ -3228,6 +3228,20 @@ http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=rotation * BYOB: Disabled hover-help for custom blocks (some people find it annoying) * GUI: Hide setting for “prefer smooth animations” (now - mostly - redundant) +161231 +------ +* Morphic: support for menu shortcuts (ongoing) +* GUI, Blocks: menu shortcuts (experimental) + +170102 +------ +* Morphic: use animations to schedule tool tips + +170103 +------ +* Hierarchical menus, thanks, Brian! +* Tweaks to hierarchical menus + == v4.10 === (in development) @@ -3242,6 +3256,7 @@ Features: * svg support for images from the web (svg files have been supported for a long time) * use media dialog for browsing and importing sounds * highly experimental infix-expression-to-reporter parser. Thanks, Bernat, for the brilliant idea to add it to the search-blocks field! +* hierarchical menus, also for custom blocks, thanks, Brian! Fixes: * Music (play note) to work again in new and recent browser versions (Chrome 55) diff --git a/morphic.js b/morphic.js index 60561149..0df7593c 100644 --- a/morphic.js +++ b/morphic.js @@ -8,7 +8,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2016 by Jens Mönig + Copyright (C) 2017 by Jens Mönig This file is part of Snap!. @@ -1136,7 +1136,7 @@ /*global window, HTMLCanvasElement, FileReader, Audio, FileList*/ -var morphicVersion = '2016-December-27'; +var morphicVersion = '2017-January-03'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug @@ -3347,7 +3347,8 @@ Morph.prototype.toggleVisibility = function () { // Morph full image: Morph.prototype.fullImageClassic = function () { - var fb = this.cachedFullBounds || this.fullBounds(), // use the cache since fullDrawOn() will + // use the cache since fullDrawOn() will + var fb = this.cachedFullBounds || this.fullBounds(), img = newCanvas(fb.extent()), ctx = img.getContext('2d'); ctx.translate(-fb.origin.x, -fb.origin.y); @@ -4051,9 +4052,15 @@ Morph.prototype.hierarchyMenu = function () { parents.forEach(function (each) { if (each.developersMenu && (each !== world)) { + menu.addMenu( + each.toString().slice(0, 50), + each.developersMenu() + ); + /* menu.addItem(each.toString().slice(0, 50), function () { each.developersMenu().popUpAtHand(world); }); + */ } }); return menu; @@ -4067,12 +4074,7 @@ Morph.prototype.developersMenu = function () { menu = new MenuMorph(this, this.constructor.name || this.constructor.toString().split(' ')[1].split('(')[0]); if (userMenu) { - menu.addItem( - 'user features...', - function () { - userMenu.popUpAtHand(world); - } - ); + menu.addMenu('user features', userMenu); menu.addLine(); } menu.addItem( @@ -7470,6 +7472,7 @@ MenuMorph.prototype.init = function (target, title, environment, fontSize) { this.isListContents = false; this.hasFocus = false; this.selection = null; + this.submenu = null; // initialize inherited properties: MenuMorph.uber.init.call(this); @@ -7489,7 +7492,8 @@ MenuMorph.prototype.addItem = function ( color, bold, // bool italic, // bool - doubleClickAction // optional, when used as list contents + doubleClickAction, // optional, when used as list contents + shortcut // optional string, icon (Morph or Canvas) or tuple [icon, string] ) { /* labelString is normally a single-line string. But it can also be one @@ -7506,7 +7510,16 @@ MenuMorph.prototype.addItem = function ( color, bold || false, italic || false, - doubleClickAction]); + doubleClickAction, + shortcut]); +}; + +MenuMorph.prototype.addMenu = function (label, aMenu, indicator) { + this.addPair(label, aMenu, isNil(indicator) ? '\u25ba' : indicator); +}; + +MenuMorph.prototype.addPair = function (label, action, shortcut, hint) { + this.addItem(label, action, hint, null, null, null, null, shortcut); }; MenuMorph.prototype.addLine = function (width) { @@ -7598,7 +7611,8 @@ MenuMorph.prototype.drawNew = function () { tuple[3], // color tuple[4], // bold tuple[5], // italic - tuple[6] // doubleclick action + tuple[6], // doubleclick action + tuple[7] // shortcut ); } if (isLine) { @@ -7627,9 +7641,12 @@ MenuMorph.prototype.maxWidth = function () { } } this.children.forEach(function (item) { - if (item instanceof MenuItemMorph) { - w = Math.max(w, item.children[0].width() + 8); + w = Math.max( + w, + item.label.width() + 8 + + (item.shortcut ? item.shortcut.width() + 4 : 0) + ); } else if ((item instanceof StringFieldMorph) || (item instanceof ColorPickerMorph) || (item instanceof SliderMorph)) { @@ -7649,6 +7666,7 @@ MenuMorph.prototype.adjustWidths = function () { this.children.forEach(function (item) { item.silentSetWidth(w); if (item instanceof MenuItemMorph) { + item.fixLayout(); isSelected = (item.image === item.pressImage); item.createBackgrounds(); if (isSelected) { @@ -7720,6 +7738,24 @@ MenuMorph.prototype.popUpCenteredInWorld = function (world) { ); }; +// MenuMorph submenus + +MenuMorph.prototype.closeRootMenu = function () { + if (this.parent instanceof MenuMorph) { + this.parent.closeRootMenu(); + } else { + this.destroy(); + } +}; + +MenuMorph.prototype.closeSubmenu = function () { + if (this.submenu) { + this.submenu.destroy(); + this.submenu = null; + this.unselectAllItems(); + } +}; + // MenuMorph keyboard accessibility MenuMorph.prototype.getFocus = function () { @@ -7730,18 +7766,25 @@ MenuMorph.prototype.getFocus = function () { }; MenuMorph.prototype.processKeyDown = function (event) { - //console.log(event.keyCode); + // console.log(event.keyCode); switch (event.keyCode) { case 13: // 'enter' case 32: // 'space' if (this.selection) { this.selection.mouseClickLeft(); + if (this.submenu) { + this.submenu.getFocus(); + } } return; case 27: // 'esc' return this.destroy(); + case 37: // 'left arrow' + return this.leaveSubmenu(); case 38: // 'up arrow' return this.selectUp(); + case 39: // 'right arrow' + return this.enterSubmenu(); case 40: // 'down arrow' return this.selectDown(); default: @@ -7805,6 +7848,25 @@ MenuMorph.prototype.selectDown = function () { this.select(triggers[idx]); }; +MenuMorph.prototype.enterSubmenu = function () { + if (this.selection && this.selection.action instanceof MenuMorph) { + this.selection.popUpSubmenu(); + if (this.submenu) { + this.submenu.getFocus(); + } + } +}; + +MenuMorph.prototype.leaveSubmenu = function () { + var menu = this.parent; + if (this.parent instanceof MenuMorph) { + menu.submenu = null; + menu.hasFocus = true; + this.destroy(); + menu.world.keyboardReceiver = menu; + } +}; + MenuMorph.prototype.select = function (aMenuItem) { this.unselectAllItems(); aMenuItem.image = aMenuItem.highlightImage; @@ -9096,6 +9158,7 @@ TriggerMorph.prototype.init = function ( this.labelString = labelString || null; this.label = null; this.hint = hint || null; // null, String, or Function + this.schedule = null; // animation slot for displaying hints this.fontSize = fontSize || MorphicPreferences.menuFontSize; this.fontStyle = fontStyle || 'sans-serif'; this.highlightColor = new Color(192, 192, 192); @@ -9189,6 +9252,9 @@ TriggerMorph.prototype.trigger = function () { treat it as function property of target and execute it for selector-like actions */ + if (this.schedule) { + this.schedule.isActive = false; + } if (typeof this.target === 'function') { if (typeof this.action === 'function') { this.target.call(this.environment, this.action.call(), this); @@ -9208,6 +9274,9 @@ TriggerMorph.prototype.triggerDoubleClick = function () { // same as trigger() but use doubleClickAction instead of action property // note that specifying a doubleClickAction is optional if (!this.doubleClickAction) {return; } + if (this.schedule) { + this.schedule.isActive = false; + } if (typeof this.target === 'function') { if (typeof this.doubleClickAction === 'function') { this.target.call( @@ -9241,6 +9310,9 @@ TriggerMorph.prototype.mouseEnter = function () { TriggerMorph.prototype.mouseLeave = function () { this.image = this.normalImage; this.changed(); + if (this.schedule) { + this.schedule.isActive = false; + } if (this.hint) { this.world().hand.destroyTemporaries(); } @@ -9268,15 +9340,17 @@ TriggerMorph.prototype.rootForGrab = function () { // TriggerMorph bubble help: TriggerMorph.prototype.bubbleHelp = function (contents) { - var myself = this; - this.fps = 2; - this.step = function () { - if (this.bounds.containsPoint(this.world().hand.position())) { - myself.popUpbubbleHelp(contents); - } - myself.fps = 0; - delete myself.step; - }; + var world = this.world(), + myself = this; + this.schedule = new Animation( + nop, + nop, + 0, + 500, + nop, + function () {myself.popUpbubbleHelp(contents); } + ); + world.animations.push(this.schedule); }; TriggerMorph.prototype.popUpbubbleHelp = function (contents) { @@ -9313,8 +9387,14 @@ function MenuItemMorph( color, bold, italic, - doubleClickAction // optional when used as list morph item + doubleClickAction, // optional when used as list morph item + shortcut // optional string, Morph, Canvas or tuple: [icon, string] ) { + // additional properties: + this.shortcutString = shortcut || null; + this.shortcut = null; + + // initialize inherited properties: this.init( target, action, @@ -9331,31 +9411,58 @@ function MenuItemMorph( } MenuItemMorph.prototype.createLabel = function () { - var icon, lbl, np; - if (this.label !== null) { + var w, h; + if (this.label) { this.label.destroy(); } - if (isString(this.labelString)) { - this.label = this.createLabelString(this.labelString); - } else if (this.labelString instanceof Array) { + this.label = this.createLabelPart(this.labelString); + this.add(this.label); + w = this.label.width(); + h = this.label.height(); + if (this.shortcut) { + this.shortcut.destroy(); + } + if (this.shortcutString) { + this.shortcut = this.createLabelPart(this.shortcutString); + w += this.shortcut.width() + 4; + h = Math.max(h, this.shortcut.height()); + this.add(this.shortcut); + } + this.silentSetExtent(new Point(w + 8, h)); + this.fixLayout(); +}; + +MenuItemMorph.prototype.fixLayout = function () { + var cntr = this.center(); + this.label.setCenter(cntr); + this.label.setLeft(this.left() + 4); + if (this.shortcut) { + this.shortcut.setCenter(cntr); + this.shortcut.setRight(this.right() - 4); + } +}; + +MenuItemMorph.prototype.createLabelPart = function (source) { + var part, icon, lbl; + if (isString(source)) { + return this.createLabelString(source); + } + if (source instanceof Array) { // assume its pattern is: [icon, string] - this.label = new Morph(); - this.label.alpha = 0; // transparent - icon = this.createIcon(this.labelString[0]); - this.label.add(icon); - lbl = this.createLabelString(this.labelString[1]); - this.label.add(lbl); + part = new Morph(); + part.alpha = 0; // transparent + icon = this.createIcon(source[0]); + part.add(icon); + lbl = this.createLabelString(source[1]); + part.add(lbl); lbl.setCenter(icon.center()); lbl.setLeft(icon.right() + 4); - this.label.bounds = (icon.bounds.merge(lbl.bounds)); - this.label.drawNew(); - } else { // assume it's either a Morph or a Canvas - this.label = this.createIcon(this.labelString); + part.bounds = (icon.bounds.merge(lbl.bounds)); + part.drawNew(); + return part; } - this.silentSetExtent(this.label.extent().add(new Point(8, 0))); - np = this.position().add(new Point(4, 0)); - this.label.bounds = np.extent(this.label.extent()); - this.add(this.label); + // assume it's either a Morph or a Canvas + return this.createIcon(source); }; MenuItemMorph.prototype.createIcon = function (source) { @@ -9393,20 +9500,36 @@ MenuItemMorph.prototype.createLabelString = function (string) { // MenuItemMorph events: MenuItemMorph.prototype.mouseEnter = function () { + var menu = this.parentThatIsA(MenuMorph); + if (this.isShowingSubmenu()) { + return; + } + if (menu) { + menu.closeSubmenu(); + } if (!this.isListItem()) { this.image = this.highlightImage; this.changed(); } - if (this.hint) { + if (this.action instanceof MenuMorph) { + this.delaySubmenu(); + } else if (this.hint) { this.bubbleHelp(this.hint); } }; MenuItemMorph.prototype.mouseLeave = function () { if (!this.isListItem()) { - this.image = this.normalImage; + if (this.isShowingSubmenu()) { + this.image = this.highlightImage; + } else { + this.image = this.normalImage; + } this.changed(); } + if (this.schedule) { + this.schedule.isActive = false; + } if (this.hint) { this.world().hand.destroyTemporaries(); } @@ -9428,11 +9551,15 @@ MenuItemMorph.prototype.mouseMove = function () { }; MenuItemMorph.prototype.mouseClickLeft = function () { - if (!this.isListItem()) { - this.parent.destroy(); - this.root().activeMenu = null; + if (this.action instanceof MenuMorph) { + this.popUpSubmenu(); + } else { + if (!this.isListItem()) { + this.parent.closeRootMenu(); + this.world().activeMenu = null; + } + this.trigger(); } - this.trigger(); }; MenuItemMorph.prototype.isListItem = function () { @@ -9449,6 +9576,44 @@ MenuItemMorph.prototype.isSelectedListItem = function () { return false; }; +MenuItemMorph.prototype.isShowingSubmenu = function () { + var menu = this.parentThatIsA(MenuMorph); + if (menu && (this.action instanceof MenuMorph)) { + return menu.submenu === this.action; + } + return false; +}; + +// MenuItemMorph submenus: + +MenuItemMorph.prototype.delaySubmenu = function () { + var world = this.world(), + myself = this; + this.schedule = new Animation( + nop, + nop, + 0, + 500, + nop, + function () {myself.popUpSubmenu(); } + ); + world.animations.push(this.schedule); +}; + +MenuItemMorph.prototype.popUpSubmenu = function () { + var menu = this.parentThatIsA(MenuMorph); + if (!(this.action instanceof MenuMorph)) {return; } + this.action.drawNew(); + this.action.setPosition(this.topRight().subtract(new Point(0, 5))); + this.action.addShadow(new Point(2, 2), 80); + this.action.keepWithin(this.world()); + if (this.action.items.length < 1 && !this.action.title) {return; } + menu.add(this.action); + menu.submenu = this.action; + menu.submenu.world = menu.world; // keyboard control + this.action.fullChanged(); +}; + // FrameMorph ////////////////////////////////////////////////////////// // I clip my submorphs at my bounds diff --git a/objects.js b/objects.js index 10c4855a..24858a66 100644 --- a/objects.js +++ b/objects.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2016 by Jens Mönig + Copyright (C) 2017 by Jens Mönig This file is part of Snap!. @@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph, BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize, TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph*/ -modules.objects = '2016-December-27'; +modules.objects = '2017-January-03'; var SpriteMorph; var StageMorph; @@ -2272,7 +2272,11 @@ SpriteMorph.prototype.freshPalette = function (category) { }); } - menu.addItem('find blocks...', function () {myself.searchBlocks(); }); + menu.addPair( + 'find blocks...', + function () {myself.searchBlocks(); }, + '⌘F' + ); if (canHidePrimitives()) { menu.addItem( 'hide primitives', diff --git a/widgets.js b/widgets.js index 57574ea7..34c8c8e1 100644 --- a/widgets.js +++ b/widgets.js @@ -7,7 +7,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2015 by Jens Mönig + Copyright (C) 2017 by Jens Mönig This file is part of Snap!. @@ -74,7 +74,7 @@ HTMLCanvasElement, fontHeight, SymbolMorph, localize, SpeechBubbleMorph, ArrowMorph, MenuMorph, isString, isNil, SliderMorph, MorphicPreferences, ScrollFrameMorph*/ -modules.widgets = '2016-July-19'; +modules.widgets = '2017-January-03'; var PushButtonMorph; var ToggleButtonMorph; @@ -575,6 +575,9 @@ ToggleButtonMorph.prototype.mouseLeave = function () { this.image = this.normalImage; this.changed(); } + if (this.schedule) { + this.schedule.isActive = false; + } if (this.hint) { this.world().hand.destroyTemporaries(); }