From f829635106ffca4f9933bf4b19f64c7a7352d4c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20M=C3=B6nig?= Date: Sun, 26 Jul 2015 22:52:46 +0200 Subject: [PATCH] Morphic enhancements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enable exporting a screenshot of the World * enable more fine-grained control over dragging position correction * enable all Morphs to “scrollIntoView()” * keyboard accessibility for menus --- morphic.js | 205 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 177 insertions(+), 28 deletions(-) diff --git a/morphic.js b/morphic.js index f26c7bf7..bf4c1ab2 100644 --- a/morphic.js +++ b/morphic.js @@ -2501,6 +2501,32 @@ Morph.prototype.keepWithin = function (aMorph) { } }; +Morph.prototype.scrollIntoView = function () { + var leftOff, rightOff, topOff, bottomOff, + sf = this.parentThatIsA(ScrollFrameMorph); + if (!sf) {return; } + rightOff = Math.min( + this.fullBounds().right() - sf.right(), + sf.contents.right() - sf.right() + ); + if (rightOff > 0) { + sf.contents.moveBy(new Point(-rightOff, 0)); + } + leftOff = this.fullBounds().left() - sf.left(); + if (leftOff < 0) { + sf.contents.moveBy(new Point(-leftOff, 0)); + } + topOff = this.fullBounds().top() - sf.top(); + if (topOff < 0) { + sf.contents.moveBy(new Point(0, -topOff)); + } + bottomOff = this.fullBounds().bottom() - sf.bottom(); + if (bottomOff > 0) { + sf.contents.moveBy(new Point(0, -bottomOff)); + } + sf.adjustScrollBars(); +}; + // Morph accessing - dimensional changes requiring a complete redraw Morph.prototype.setExtent = function (aPoint) { @@ -3092,6 +3118,13 @@ Morph.prototype.rootForGrab = function () { return this.parent.rootForGrab(); }; +Morph.prototype.isCorrectingOutsideDrag = function () { + // make sure I don't "trail behind" the hand when dragged + // override for morphs that you want to be dragged outside + // their full bounds + return true; +}; + Morph.prototype.wantsDropOf = function (aMorph) { // default is to answer the general flag - change for my heirs if ((aMorph instanceof HandleMorph) || @@ -3174,6 +3207,17 @@ Morph.prototype.move = function () { ); }; +Morph.prototype.moveCenter = function () { + this.world().activeHandle = new HandleMorph( + this, + null, + null, + null, + null, + 'moveCenter' + ); +}; + Morph.prototype.hint = function (msg) { var m, text; text = msg; @@ -3722,7 +3766,7 @@ HandleMorph.prototype.init = function ( this.target = target || null; this.minExtent = new Point(minX || 0, minY || 0); this.inset = new Point(insetX || 0, insetY || insetX || 0); - this.type = type || 'resize'; // can also be 'move' + this.type = type || 'resize'; // can also be 'move', 'moveCenter' HandleMorph.uber.init.call(this); this.color = new Color(255, 255, 255); this.isDraggable = false; @@ -3747,11 +3791,15 @@ HandleMorph.prototype.drawNew = function () { ); this.image = this.normalImage; if (this.target) { - this.setPosition( - this.target.bottomRight().subtract( - this.extent().add(this.inset) - ) - ); + if (this.type === 'moveCenter') { + this.setCenter(this.target.center()); + } else { // 'resize', 'move' + this.setPosition( + this.target.bottomRight().subtract( + this.extent().add(this.inset) + ) + ); + } this.target.add(this); this.target.changed(); } @@ -3763,6 +3811,7 @@ HandleMorph.prototype.drawOnCanvas = function ( shadowColor ) { var context = aCanvas.getContext('2d'), + isSquare = (this.type.indexOf('move') === 0), p1, p11, p2, @@ -3774,7 +3823,7 @@ HandleMorph.prototype.drawOnCanvas = function ( context.strokeStyle = color.toString(); - if (this.type === 'move') { + if (isSquare) { p1 = this.bottomLeft().subtract(this.position()); p11 = p1.copy(); @@ -3811,7 +3860,7 @@ HandleMorph.prototype.drawOnCanvas = function ( context.strokeStyle = shadowColor.toString(); - if (this.type === 'move') { + if (isSquare) { p1 = this.bottomLeft().subtract(this.position()); p11 = p1.copy(); @@ -3853,12 +3902,17 @@ HandleMorph.prototype.step = null; HandleMorph.prototype.mouseDownLeft = function (pos) { var world = this.root(), - offset = pos.subtract(this.bounds.origin), + offset, myself = this; if (!this.target) { return null; } + if (this.type === 'moveCenter') { + offset = pos.subtract(this.center()); + } else { + offset = pos.subtract(this.bounds.origin); + } this.step = function () { var newPos, newExt; if (world.hand.mouseButton) { @@ -3875,6 +3929,8 @@ HandleMorph.prototype.mouseDownLeft = function (pos) { myself.extent().add(myself.inset) ) ); + } else if (this.type === 'moveCenter') { + myself.target.setCenter(newPos); } else { // type === 'move' myself.target.setPosition( newPos.subtract(this.target.extent()) @@ -6652,6 +6708,8 @@ MenuMorph.prototype.init = function (target, title, environment, fontSize) { this.label = null; this.world = null; this.isListContents = false; + this.hasFocus = false; + this.selection = null; // initialize inherited properties: MenuMorph.uber.init.call(this); @@ -6871,6 +6929,7 @@ MenuMorph.prototype.popup = function (world, pos) { } world.add(this); world.activeMenu = this; + this.world = world; // optionally enable keyboard support this.fullChanged(); }; @@ -6901,6 +6960,105 @@ MenuMorph.prototype.popUpCenteredInWorld = function (world) { ); }; +// MenuMorph keyboard accessibility + +MenuMorph.prototype.getFocus = function () { + this.world.keyboardReceiver = this; + this.selection = null; + this.selectFirst(); + this.hasFocus = true; +}; + +MenuMorph.prototype.processKeyDown = function (event) { + //console.log(event.keyCode); + switch (event.keyCode) { + case 13: // 'enter' + case 32: // 'space' + if (this.selection) { + this.selection.mouseClickLeft(); + } + return; + case 27: // 'esc' + return this.destroy(); + case 38: // 'up arrow' + return this.selectUp(); + case 40: // 'down arrow' + return this.selectDown(); + default: + nop(); + } +}; + +MenuMorph.prototype.processKeyUp = function (event) { + nop(event); +}; + +MenuMorph.prototype.processKeyPress = function (event) { + nop(event); +}; + +MenuMorph.prototype.selectFirst = function () { + var i; + for (i = 0; i < this.children.length; i += 1) { + if (this.children[i] instanceof MenuItemMorph) { + this.select(this.children[i]); + return; + } + } +}; + +MenuMorph.prototype.selectUp = function () { + var triggers, idx; + + triggers = this.children.filter(function (each) { + return each instanceof MenuItemMorph; + }); + if (!this.selection) { + if (triggers.length) { + this.select(triggers[0]); + } + return; + } + idx = triggers.indexOf(this.selection) - 1; + if (idx < 0) { + idx = triggers.length - 1; + } + this.select(triggers[idx]); +}; + +MenuMorph.prototype.selectDown = function () { + var triggers, idx; + + triggers = this.children.filter(function (each) { + return each instanceof MenuItemMorph; + }); + if (!this.selection) { + if (triggers.length) { + this.select(triggers[0]); + } + return; + } + idx = triggers.indexOf(this.selection) + 1; + if (idx >= triggers.length) { + idx = 0; + } + this.select(triggers[idx]); +}; + +MenuMorph.prototype.select = function (aMenuItem) { + this.unselectAllItems(); + aMenuItem.image = aMenuItem.highlightImage; + aMenuItem.changed(); + this.selection = aMenuItem; +}; + +MenuMorph.prototype.destroy = function () { + if (this.hasFocus) { + this.world.keyboardReceiver = null; + } + MenuMorph.uber.destroy.call(this); +}; + // StringMorph ///////////////////////////////////////////////////////// // I am a single line of text @@ -9586,32 +9744,16 @@ HandMorph.prototype.processMouseMove = function (event) { this.grabOrigin = this.morphToGrab.situation(); } if (morph) { - // if the mouse has left its fullBounds, center it + // if the mouse has left its fullBounds, allow to center it fb = morph.fullBounds(); - if (!fb.containsPoint(pos)) { + if (!fb.containsPoint(pos) && + morph.isCorrectingOutsideDrag()) { this.bounds.origin = fb.center(); this.grab(morph); this.setPosition(pos); } } } - -/* - original, more cautious code for grabbing Morphs, - retained in case of needing to fall back: - - if (morph === this.morphToGrab) { - if (morph.isDraggable) { - this.grab(morph); - } else if (morph.isTemplate) { - morph = morph.fullCopy(); - morph.isTemplate = false; - morph.isDraggable = true; - this.grab(morph); - } - } -*/ - } this.mouseOverList.forEach(function (old) { @@ -10399,6 +10541,13 @@ WorldMorph.prototype.contextMenu = function () { 'inspect', 'open a window on\nall properties' ); + menu.addItem( + "screenshot...", + function () { + window.open(this.fullImageClassic().toDataURL()); + }, + 'open a new window\nwith a picture of this morph' + ); menu.addLine(); menu.addItem( "restore display",