diff --git a/src/morphic.js b/src/morphic.js index d57ae07d..2b6402a1 100644 --- a/src/morphic.js +++ b/src/morphic.js @@ -26,6 +26,49 @@ along with this program. If not, see . + *** UNDER CONSTRUCTIOM *** + + Morphic changes to v1: + + * noticesTransparentClick => !isFreeForm (reversed default) + * drawOn() / fullDrawOn() takes context instead of Canvas as first arg + * drawNew() is deprecated => render(), also takes context as arg + * rerender() to earmark for rerendering + * image has a getter method: getImage() + * image has been renamed to cachedImage + * cachesImage flag (default: false) + * shouldRerender flag (default: false) + * fixLayout() determines extent and arranges submorphs, if any, gets called + from setExtent() + + "silent" - functions are no longer needed: + + * silentSetExtent + * silentMoveBy + * silentSetPosition + * silentSetWidth + * silentSetHeight + + likewise "silent" parameters are no longer needed and supported + + * cachedFullImage + * cachedFullBounds + + are deprecated + + "trackChanges" and other damage-list housekeeping tweaks are no longer + needed and no longer supported + + holes: + Morphs have a list of rectangles representing "untouchable" areas + + * virtualKeyboard property and Morphic preference has been deprecated + * fullImageClassic() => is always just fullImage() + * keyboardReceiver => keyboardFocus + + * keyboard navigation can be activated for any visible menu by pressing an arbitrary key + + documentation contents ---------------------- I. inheritance hierarchy @@ -53,10 +96,9 @@ (5) creating new kinds of morphs (6) development and user modes (7) turtle graphics - (8) damage list housekeeping - (9) supporting high-resolution "retina" screens - (10 animations - (11) minifying morphic.js + (8) supporting high-resolution "retina" screens + (9 animations + (10) minifying morphic.js VIII. acknowledgements IX. contributors @@ -267,7 +309,7 @@ window.onload = function () { world = new WorldMorph(document.getElementById('world')); - world.worldCanvas.focus(); + // +++ world.worldCanvas.focus(); world.isDevMode = true; loop(); }; @@ -363,7 +405,7 @@ worldCanvas = document.getElementById('world'); world = new WorldMorph(worldCanvas); - world.worldCanvas.focus(); + // +++ world.worldCanvas.focus(); world.isDevMode = false; world.setColor(new Color()); @@ -729,8 +771,7 @@ MyMorph.prototype.reactToWorldResize = function (rect) { this.changed(); this.bounds = rect.insetBy(10); - this.drawNew(); - this.changed(); + this.rerender(); }; @@ -808,8 +849,8 @@ applications. In addition to user-initiated events text elements also emit - change notifications to their direct parents whenever their drawNew() - method is invoked. That way complex Morphs containing text elements + change notifications to their direct parents whenever their contents + changes. That way complex Morphs containing text elements get a chance to react if something about the embedded text has been modified programmatically. These events are: @@ -855,24 +896,21 @@ with customized shapes. Imagine, e.g. jigsaw puzzle pieces or musical notes. For this you have to override the default - drawNew() + render(ctx) method. - This method creates a new offscreen Canvas and stores it in - the morph's + This method draws the morph's shape with a 2d graphics context. - image + explain - property. + * isCachingImage + * isFreeForm = bool Use the following template for a start: - MyMorph.prototype.drawNew = function() { - var context; - this.image = newCanvas(this.extent()); - context = this.image.getContext('2d'); - // use context to paint stuff here + MyMorph.prototype.render = function(ctx) { + // use ctx to paint stuff here }; If your new morph stores or references to other morphs outside of @@ -940,7 +978,7 @@ which you can use to draw onto its parent Morph. By default every Morph in the system (including the World) is able to act as turtle canvas and can display pen trails. Pen trails will be lost whenever - the trails morph (the pen's parent) performs a "drawNew()" + the trails morph (the pen's parent) performs a "render()" operation. If you want to create your own pen trails canvas, you may wish to modify its @@ -989,47 +1027,7 @@ segment and instead redraws the outcome in a single pass. - (8) damage list housekeeping - ---------------------------- - Morphic's progressive display update comes at the cost of having to - cycle through a list of "broken rectangles" every display cycle. If - this list gets very long working this damage list can lead to a - seemingly dramatic slow-down of the Morphic system. Typically this - occurs when updating the layout of complex Morphs with very many - submorphs, e.g. when resizing an inspector window. - - An effective strategy to cope with this is to use the inherited - - trackChanges - - property of the Morph prototype for damage list housekeeping. - - The trackChanges property of the Morph prototype is a Boolean switch - that determines whether the World's damage list ('broken' rectangles) - tracks changes. By default the switch is always on. If set to false - changes are not stored. This can be very useful for housekeeping of - the damage list in situations where a large number of (sub-) morphs - are changed more or less at once. Instead of keeping track of every - single submorph's changes tremendous performance improvements can be - achieved by setting the trackChanges flag to false before propagating - the layout changes, setting it to true again and then storing the full - bounds of the surrounding morph. As an example refer to the - - moveBy() - - method of HandMorph, and to the - - fixLayout() - - method of InspectorMorph, or the - - startLayout() - endLayout() - - methods of SyntaxElementMorph in the Snap application. - - - (9) supporting high-resolution "retina" screens + (8) supporting high-resolution "retina" screens ----------------------------------------------- By default retina support gets installed when Morphic.js loads. There are two global functions that let you test for retina availability: @@ -1075,7 +1073,7 @@ stage (high-resolution) into a sprite-costume (normal resolution). - (10) animations + (9) animations --------------- Animations handle gradual transitions between one state and another over a period of time. Transition effects can be specified using easing functions. @@ -1105,7 +1103,7 @@ are implemented. - (11) minifying morphic.js + (10) minifying morphic.js ------------------------- Coming from Smalltalk and being a Squeaker at heart I am a huge fan of browsing the code itself to make sense of it. Therefore I have @@ -1178,10 +1176,13 @@ /*global window, HTMLCanvasElement, FileReader, Audio, FileList, Map*/ -var morphicVersion = '2020-January-04'; +var morphicVersion = '2020-February-10'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug +const ZERO = new Point(); +Object.freeze(ZERO); + var standardSettings = { minimumFontHeight: getMinimumFontHeight(), // browser settings globalFontFamily: '', @@ -1195,11 +1196,11 @@ var standardSettings = { scrollBarSize: 12, mouseScrollAmount: 40, useSliderForInput: false, - useVirtualKeyboard: true, isTouchDevice: false, // turned on by touch events, don't set rasterizeSVGs: false, isFlat: false, - grabThreshold: 5 + grabThreshold: 5, + showHoles: false }; var touchScreenSettings = { @@ -1215,11 +1216,11 @@ var touchScreenSettings = { scrollBarSize: 24, mouseScrollAmount: 40, useSliderForInput: true, - useVirtualKeyboard: true, isTouchDevice: false, rasterizeSVGs: false, isFlat: false, - grabThreshold: 5 + grabThreshold: 5, + showHoles: false }; var MorphicPreferences = standardSettings; @@ -1605,7 +1606,7 @@ function enableRetinaSupport() { // [Jens]: only install retina utilities if the display supports them if (backingStorePixelRatio === originalDevicePixelRatio) {return; } // [Jens]: check whether properties can be overridden, needed for Safari - if (Object.keys(uber).some(function (any) { + if (Object.keys(uber).some(any => { var prop = uber[any]; return prop.hasOwnProperty('configurable') && (!prop.configurable); })) {return; } @@ -1649,8 +1650,10 @@ function enableRetinaSupport() { context; uber.width.set.call(this, width * pixelRatio); context = this.getContext('2d'); + /* context.restore(); context.save(); + */ context.scale(pixelRatio, pixelRatio); } catch (err) { console.log('Retina Display Support Problem', err); @@ -1668,8 +1671,10 @@ function enableRetinaSupport() { context; uber.height.set.call(this, height * pixelRatio); context = this.getContext('2d'); + /* context.restore(); context.save(); + */ context.scale(pixelRatio, pixelRatio); } }); @@ -1803,7 +1808,7 @@ function isRetinaSupported () { ) }; return backingStorePixelRatio !== window.devicePixelRatio && - !(Object.keys(uber).some(function (any) { + !(Object.keys(uber).some(any => { var prop = uber[any]; return prop.hasOwnProperty('configurable') && (!prop.configurable); }) @@ -1902,36 +1907,30 @@ Animation.prototype.easings = { // two states // ease both in and out: - linear: function (t) {return t; }, - sinusoidal: function (t) {return 1 - Math.cos(radians(t * 90)); }, - quadratic: function (t) { + linear: t => t, + sinusoidal: t => 1 - Math.cos(radians(t * 90)), + quadratic: t => t < 0.5 ? 2 * t * t : ((4 - (2 * t)) * t) - 1, + cubic: t => { return t < 0.5 ? - 2 * t * t - : ((4 - (2 * t)) * t) - 1; + 4 * t * t * t + : ((t - 1) * ((2 * t) - 2) * ((2 * t) - 2)) + 1; }, - cubic: function (t) { - return t < 0.5 ? - 4 * t * t * t - : ((t - 1) * ((2 * t) - 2) * ((2 * t) - 2)) + 1; - }, - elastic: function (t) { + elastic: t => { return (t -= 0.5) < 0 ? (0.01 + 0.01 / t) * Math.sin(50 * t) : (0.02 - 0.01 / t) * Math.sin(50 * t) + 1; }, // ease in only: - sine_in: function (t) {return 1 - Math.sin(radians(90 + (t * 90))); }, - quad_in: function (t) {return t * t; }, - cubic_in: function (t) {return t * t * t; }, - elastic_in: function (t) { - return (0.04 - 0.04 / t) * Math.sin(25 * t) + 1; - }, + sine_in: t => 1 - Math.sin(radians(90 + (t * 90))), + quad_in: t => t * t, + cubic_in: t => t * t * t, + elastic_in: t => (0.04 - 0.04 / t) * Math.sin(25 * t) + 1, // ease out only: - sine_out: function (t) {return Math.sin(radians(t * 90)); }, - quad_out: function (t) {return t * (2 - t); }, - elastic_out: function (t) {return 0.04 * t / (--t) * Math.sin(25 * t); } + sine_out: t => Math.sin(radians(t * 90)), + quad_out: t => t * (2 - t), + elastic_out: t => 0.04 * t / (--t) * Math.sin(25 * t) }; Animation.prototype.start = function () { @@ -2568,6 +2567,16 @@ Rectangle.prototype.setTo = function (left, top, right, bottom) { ); }; +// Rectangle mutating + +Rectangle.prototype.setWidth = function (width) { + this.corner.x += width; +}; + +Rectangle.prototype.setHeight = function (height) { + this.corner.y += height; +}; + // Rectangle accessing - getting: Rectangle.prototype.area = function () { @@ -2727,8 +2736,8 @@ Rectangle.prototype.round = function () { Rectangle.prototype.spread = function () { // round me by applying floor() to my origin and ceil() to my corner - // expand by 1 to be on the safe side, this eliminates rounding - // artifacts caused by Safari's auto-scaling on retina displays + // and expand by 1, + // avoids artefacts on retina displays return this.origin.floor().corner(this.corner.ceil()).expandBy(1); }; @@ -2755,6 +2764,60 @@ Rectangle.prototype.amountToTranslateWithin = function (aRect) { return new Point(dx, dy); }; +Rectangle.prototype.regionsAround = function (aRect) { + // answer a list of rectangles surrounding another one, + // use this to clip "holes" + var regions = []; + if (!this.intersects(aRect)) { + return regions; + } + // left + if (aRect.left() > this.left()) { + regions.push( + new Rectangle( + this.left(), + this.top(), + aRect.left(), + this.bottom() + ) + ); + } + // above: + if (aRect.top() > this.top()) { + regions.push( + new Rectangle( + this.left(), + this.top(), + this.right(), + aRect.top() + ) + ); + } + // right: + if (aRect.right() < this.right()) { + regions.push( + new Rectangle( + aRect.right(), + this.top(), + this.right(), + this.bottom() + ) + ); + } + // below: + if (aRect.bottom() < this.bottom()) { + regions.push( + new Rectangle( + this.left(), + aRect.bottom(), + this.right(), + this.bottom() + ) + ); + } + return regions; +}; + // Rectangle testing: Rectangle.prototype.containsPoint = function (aPoint) { @@ -2791,10 +2854,10 @@ Rectangle.prototype.scaleBy = function (scale) { return new Rectangle(o.x, o.y, c.x, c.y); }; -Rectangle.prototype.translateBy = function (factor) { - // factor can be either a Point or a scalar - var o = this.origin.add(factor), - c = this.corner.add(factor); +Rectangle.prototype.translateBy = function (delta) { + // delta can be either a Point or a number + var o = this.origin.add(delta), + c = this.corner.add(delta); return new Rectangle(o.x, o.y, c.x, c.y); }; @@ -2865,7 +2928,7 @@ Node.prototype.depth = function () { Node.prototype.allChildren = function () { // includes myself var result = [this]; - this.children.forEach(function (child) { + this.children.forEach(child => { result = result.concat(child.allChildren()); }); return result; @@ -2873,9 +2936,7 @@ Node.prototype.allChildren = function () { Node.prototype.forAllChildren = function (aFunction) { if (this.children.length > 0) { - this.children.forEach(function (child) { - child.forAllChildren(aFunction); - }); + this.children.forEach(child => child.forAllChildren(aFunction)); } aFunction.call(null, this); }; @@ -2896,7 +2957,7 @@ Node.prototype.anyChild = function (aPredicate) { Node.prototype.allLeafs = function () { var result = []; - this.allChildren().forEach(function (element) { + this.allChildren().forEach(element => { if (element.children.length === 0) { result.push(element); } @@ -2914,13 +2975,10 @@ Node.prototype.allParents = function () { }; Node.prototype.siblings = function () { - var myself = this; if (this.parent === null) { return []; } - return this.parent.children.filter(function (child) { - return child !== myself; - }); + return this.parent.children.filter(child => child !== this); }; Node.prototype.parentThatIsA = function () { @@ -2970,31 +3028,6 @@ Morph.uber = Node.prototype; // Morph settings: -/* - damage list housekeeping - - the trackChanges property of the Morph prototype is a Boolean switch - that determines whether the World's damage list ('broken' rectangles) - tracks changes. By default the switch is always on. If set to false - changes are not stored. This can be very useful for housekeeping of - the damage list in situations where a large number of (sub-) morphs - are changed more or less at once. Instead of keeping track of every - single submorph's changes tremendous performance improvements can be - achieved by setting the trackChanges flag to false before propagating - the layout changes, setting it to true again and then storing the full - bounds of the surrounding morph. As an example refer to the - - fixLayout() - - method of InspectorMorph, or the - - startLayout() - endLayout() - - methods of SyntaxElementMorph in the Snap application. -*/ - -Morph.prototype.trackChanges = true; Morph.prototype.shadowBlur = 4; // Morph instance creation: @@ -3005,13 +3038,14 @@ function Morph() { // Morph initialization: -Morph.prototype.init = function (noDraw) { +Morph.prototype.init = function () { Morph.uber.init.call(this); - this.isMorph = true; - this.image = null; + this.isMorph = true; // used to optimize deep copying + this.cachedImage = null; + this.isCachingImage = false; + this.shouldRerender = false; this.bounds = new Rectangle(0, 0, 50, 40); - this.cachedFullImage = null; - this.cachedFullBounds = null; + this.holes = []; // list of "untouchable" regions (rectangles) this.color = new Color(80, 80, 80); this.texture = null; // optional url of a fill-image this.cachedTexture = null; // internal cache of actual bg image @@ -3020,8 +3054,7 @@ Morph.prototype.init = function (noDraw) { this.isDraggable = false; this.isTemplate = false; this.acceptsDrops = false; - this.noticesTransparentClick = false; - if (!noDraw) {this.drawNew(); } + this.isFreeForm = false; this.fps = 0; this.customContextMenu = null; this.lastTime = Date.now(); @@ -3070,20 +3103,17 @@ Morph.prototype.stepFrame = function () { nxt.call(this); } this.step(); - this.children.forEach(function (child) { - child.stepFrame(); - }); + this.children.forEach(child => child.stepFrame()); } }; Morph.prototype.nextSteps = function (arrayOfFunctions) { var lst = arrayOfFunctions || [], - nxt = lst.shift(), - myself = this; + nxt = lst.shift(); if (nxt) { - this.onNextStep = function () { - nxt.call(myself); - myself.nextSteps(lst); + this.onNextStep = () => { + nxt.call(this); + this.nextSteps(lst); }; } }; @@ -3170,7 +3200,7 @@ Morph.prototype.height = function () { Morph.prototype.fullBounds = function () { var result; result = this.bounds; - this.children.forEach(function (child) { + this.children.forEach(child => { if (child.isVisible) { result = result.merge(child.fullBounds()); } @@ -3182,7 +3212,7 @@ Morph.prototype.fullBoundsNoShadow = function () { // answer my full bounds but ignore any shadow var result; result = this.bounds; - this.children.forEach(function (child) { + this.children.forEach(child => { if (!(child instanceof ShadowMorph) && (child.isVisible)) { result = result.merge(child.fullBounds()); } @@ -3193,50 +3223,31 @@ Morph.prototype.fullBoundsNoShadow = function () { Morph.prototype.visibleBounds = function () { // answer which part of me is not clipped by a Frame var visible = this.bounds, - frames = this.allParents().filter(function (p) { - return p instanceof FrameMorph; - }); - frames.forEach(function (f) { - visible = visible.intersect(f.bounds); - }); + frames = this.allParents().filter(p => p instanceof FrameMorph); + frames.forEach(f => visible = visible.intersect(f.bounds)); return visible; }; // Morph accessing - simple changes: Morph.prototype.moveBy = function (delta) { - this.fullChanged(); - this.silentMoveBy(delta); - this.fullChanged(); -}; - -Morph.prototype.silentMoveBy = function (delta) { var children = this.children, i = children.length; + this.changed(); this.bounds = this.bounds.translateBy(delta); - if (this.cachedFullBounds) { - this.cachedFullBounds = this.cachedFullBounds.translateBy(delta); - } - // ugly optimization avoiding forEach() + this.changed(); for (i; i > 0; i -= 1) { - children[i - 1].silentMoveBy(delta); + children[i - 1].moveBy(delta); } }; Morph.prototype.setPosition = function (aPoint) { var delta = aPoint.subtract(this.topLeft()); - if ((delta.x !== 0) || (delta.y !== 0)) { + if (!(delta.eq(ZERO))) { this.moveBy(delta); } }; -Morph.prototype.silentSetPosition = function (aPoint) { - var delta = aPoint.subtract(this.topLeft()); - if ((delta.x !== 0) || (delta.y !== 0)) { - this.silentMoveBy(delta); - } -}; - Morph.prototype.setLeft = function (x) { this.setPosition( new Point( @@ -3338,222 +3349,175 @@ Morph.prototype.scrollIntoView = function () { // Morph accessing - dimensional changes requiring a complete redraw -Morph.prototype.setExtent = function (aPoint, silently) { - // silently avoids redrawing the receiver - if (silently) { - this.silentSetExtent(aPoint); - return; - } - if (!aPoint.eq(this.extent())) { - this.changed(); - this.silentSetExtent(aPoint); - this.changed(); - this.drawNew(); - } -}; - -Morph.prototype.silentSetExtent = function (aPoint) { - var ext, newWidth, newHeight; - ext = aPoint.round(); - newWidth = Math.max(ext.x, 0); - newHeight = Math.max(ext.y, 0); +Morph.prototype.setExtent = function (aPoint) { + if (aPoint.eq(this.extent())) {return; } + this.changed(); this.bounds.corner = new Point( - this.bounds.origin.x + newWidth, - this.bounds.origin.y + newHeight + this.bounds.origin.x + Math.max(aPoint.x, 0), + this.bounds.origin.y + Math.max(aPoint.y, 0) ); + this.fixLayout(); + this.rerender(); }; Morph.prototype.setWidth = function (width) { this.setExtent(new Point(width || 0, this.height())); }; -Morph.prototype.silentSetWidth = function (width) { - // do not drawNew() just yet - var w = Math.max(Math.round(width || 0), 0); - this.bounds.corner = new Point( - this.bounds.origin.x + w, - this.bounds.corner.y - ); -}; - Morph.prototype.setHeight = function (height) { this.setExtent(new Point(this.width(), height || 0)); }; -Morph.prototype.silentSetHeight = function (height) { - // do not drawNew() just yet - var h = Math.max(Math.round(height || 0), 0); - this.bounds.corner = new Point( - this.bounds.corner.x, - this.bounds.origin.y + h - ); -}; - Morph.prototype.setColor = function (aColor) { if (aColor) { if (!this.color.eq(aColor)) { this.color = aColor; - this.changed(); - this.drawNew(); + this.rerender(); } } }; +// Morph rendering: + +Morph.prototype.getImage = function () { + var img; + if (this.cachedImage && !this.shouldRerender) { + return this.cachedImage; + } + img = newCanvas(this.extent(), false, this.cachedImage); + if (this.isCachingImage) { + this.cachedImage = img; + } + this.render(img.getContext('2d')); + this.shouldRerender = false; + return img; +}; + +Morph.prototype.render = function (aContext) { + aContext.fillStyle = this.color.toString(); + aContext.fillRect(0, 0, this.width(), this.height()); + if (this.cachedTexture) { + this.renderCachedTexture(aContext); + } else if (this.texture) { + this.renderTexture(this.texture, aContext); + } +}; + +Morph.prototype.fixLayout = function () { + // implemented by my heirs + // determine my extent and arrange my submorphs, if any + // default is to do nothing + return; +}; + // Morph displaying: -Morph.prototype.drawNew = function () { - // initialize my surface property - this.image = newCanvas(this.extent(), false, this.image); - var context = this.image.getContext('2d'); - context.fillStyle = this.color.toString(); - context.fillRect(0, 0, this.width(), this.height()); - if (this.cachedTexture) { - this.drawCachedTexture(); - } else if (this.texture) { - this.drawTexture(this.texture); - } -}; - -Morph.prototype.drawTexture = function (url) { - var myself = this; +Morph.prototype.renderTexture = function (url, ctx) { this.cachedTexture = new Image(); - this.cachedTexture.onload = function () { - myself.drawCachedTexture(); - }; - this.cachedTexture.src = this.texture = url; // make absolute + this.cachedTexture.onload = () => this.changed(); + this.cachedTexture.src = this.texture = url; }; -Morph.prototype.drawCachedTexture = function () { +Morph.prototype.renderCachedTexture = function (ctx) { var bg = this.cachedTexture, - cols = Math.floor(this.image.width / bg.width), - lines = Math.floor(this.image.height / bg.height), + cols = Math.floor(this.width() / bg.width), + lines = Math.floor(this.height() / bg.height), x, - y, - context = this.image.getContext('2d'); + y; + ctx.save(); + ctx.globalAlpha = this.alpha; + ctx.beginPath(); + ctx.rect(0, 0, this.width(), this.height()); + ctx.clip(); for (y = 0; y <= lines; y += 1) { for (x = 0; x <= cols; x += 1) { - context.drawImage(bg, x * bg.width, y * bg.height); + ctx.drawImage(bg, x * bg.width, y * bg.height); } } - this.changed(); + ctx.restore(); }; -/* -Morph.prototype.drawCachedTexture = function () { - var context = this.image.getContext('2d'), - pattern = context.createPattern(this.cachedTexture, 'repeat'); - context.fillStyle = pattern; - context.fillRect(0, 0, this.image.width, this.image.height); - this.changed(); -}; -*/ - -Morph.prototype.drawOn = function (aCanvas, aRect) { - var rectangle, area, delta, src, context, w, h, sl, st, - pic = this.cachedFullImage || this.image, - bounds = this.cachedFullBounds || this.bounds; - if (!this.isVisible) { - return null; - } - rectangle = aRect || bounds; - area = rectangle.intersect(bounds); - if (area.extent().gt(new Point(0, 0))) { - delta = bounds.position().neg(); - src = area.copy().translateBy(delta); - context = aCanvas.getContext('2d'); - context.globalAlpha = this.alpha; +Morph.prototype.drawOn = function (ctx, rect) { + var clipped = rect.intersect(this.bounds), + pos = this.position(), + pic, src, w, h, sl, st; + if (!clipped.extent().gt(ZERO)) {return; } + ctx.save(); + ctx.globalAlpha = this.alpha; + if (this.isCachingImage) { + pic = this.getImage(); + src = clipped.translateBy(pos.neg()); sl = src.left(); st = src.top(); w = Math.min(src.width(), pic.width - sl); h = Math.min(src.height(), pic.height - st); - - if (w < 1 || h < 1) { - return null; - } - - context.drawImage( + if (w < 1 || h < 1) {return; } + ctx.drawImage( pic, sl, st, w, h, - area.left(), - area.top(), + clipped.left(), + clipped.top(), w, h ); + } else { // render directly on target canvas + ctx.beginPath(); + ctx.rect(clipped.left(), clipped.top(), clipped.width(), clipped.height()); + ctx.clip(); + ctx.translate(pos.x, pos.y); + this.render(ctx); + if (MorphicPreferences.showHoles) { // debug hole rendering + ctx.translate(-pos.x, -pos.y); + ctx.globalAlpha = 0.5; + ctx.fillStyle = 'white'; + this.holes.forEach(hole => { + var sect = hole.translateBy(pos).intersect(clipped); + ctx.fillRect( + sect.left(), + sect.top(), + sect.width(), + sect.height() + ); + }); + } } + ctx.restore(); }; -Morph.prototype.fullDrawOn = function (aCanvas, aRect) { - var rectangle; - if (!this.isVisible) { - return null; - } - rectangle = aRect || this.cachedFullBounds || this.fullBounds(); - this.drawOn(aCanvas, rectangle); - if (this.cachedFullImage) {return; } - this.children.forEach(function (child) { - child.fullDrawOn(aCanvas, rectangle); - }); +Morph.prototype.fullDrawOn = function (aContext, aRect) { + if (!this.isVisible) {return; } + this.drawOn(aContext, aRect); + this.children.forEach(child => child.fullDrawOn(aContext, aRect)); }; Morph.prototype.hide = function () { this.isVisible = false; this.changed(); - this.children.forEach(function (child) { - child.hide(); - }); }; Morph.prototype.show = function () { this.isVisible = true; this.changed(); - this.children.forEach(function (child) { - child.show(); - }); }; Morph.prototype.toggleVisibility = function () { - this.isVisible = (!this.isVisible); + this.isVisible = !this.isVisible; this.changed(); - this.children.forEach(function (child) { - child.toggleVisibility(); - }); }; // Morph full image: -Morph.prototype.fullImageClassic = function () { - // use the cache since fullDrawOn() will - var fb = this.cachedFullBounds || this.fullBounds(), +Morph.prototype.fullImage = function () { + var fb = this.fullBounds(), img = newCanvas(fb.extent()), ctx = img.getContext('2d'); ctx.translate(-fb.origin.x, -fb.origin.y); - this.fullDrawOn(img, fb); - img.globalAlpha = this.alpha; - return img; -}; - -Morph.prototype.fullImage = function () { - var img, ctx, fb; - img = newCanvas(this.fullBounds().extent()); - ctx = img.getContext('2d'); - fb = this.fullBounds(); - this.allChildren().forEach(function (morph) { - if (morph.isVisible) { - ctx.globalAlpha = morph.alpha; - if (morph.image.width && morph.image.height) { - ctx.drawImage( - morph.image, - morph.bounds.origin.x - fb.origin.x, - morph.bounds.origin.y - fb.origin.y - ); - } - } - }); + this.fullDrawOn(ctx, fb); return img; }; @@ -3621,14 +3585,15 @@ Morph.prototype.shadow = function (off, a, color) { fb = this.fullBounds(); shadow.setExtent(fb.extent().add(this.shadowBlur * 2)); if (useBlurredShadows && !MorphicPreferences.isFlat) { - shadow.image = this.shadowImageBlurred(offset, color); + shadow.cachedImage = this.shadowImageBlurred(offset, color); shadow.alpha = alpha; shadow.setPosition(fb.origin.add(offset).subtract(this.shadowBlur)); } else { - shadow.image = this.shadowImage(offset, color); + shadow.cachedImage = this.shadowImage(offset, color); shadow.alpha = alpha; shadow.setPosition(fb.origin.add(offset)); } + shadow.shouldRerender = false; return shadow; }; @@ -3645,9 +3610,7 @@ Morph.prototype.addShadow = function (off, a, color) { Morph.prototype.getShadow = function () { var shadows; shadows = this.children.slice(0).reverse().filter( - function (child) { - return child instanceof ShadowMorph; - } + child => child instanceof ShadowMorph ); if (shadows.length !== 0) { return shadows[0]; @@ -3667,17 +3630,20 @@ Morph.prototype.removeShadow = function () { Morph.prototype.penTrails = function () { // answer my pen trails canvas. default is to answer my image - return this.image; + return this.getImage(); // +++ review this }; // Morph updating: +Morph.prototype.rerender = function () { + this.shouldRerender = true; + this.changed(); +}; + Morph.prototype.changed = function () { - if (this.trackChanges) { - var w = this.root(); - if (w instanceof WorldMorph) { - w.broken.push(this.visibleBounds().spread()); - } + var w = this.root(); + if (w instanceof WorldMorph) { + w.broken.push(this.visibleBounds().spread()); } if (this.parent) { this.parent.childChanged(this); @@ -3685,13 +3651,11 @@ Morph.prototype.changed = function () { }; Morph.prototype.fullChanged = function () { - if (this.trackChanges) { - var w = this.root(); - if (w instanceof WorldMorph) { - w.broken.push( - (this.cachedFullBounds || this.fullBounds()).spread() - ); - } + var w = this.root(); + if (w instanceof WorldMorph) { + w.broken.push( + this.fullBounds().spread() + ); } }; @@ -3740,9 +3704,21 @@ Morph.prototype.topMorphAt = function (point) { result = this.children[i].topMorphAt(point); if (result) {return result; } } - return this.bounds.containsPoint(point) && - (this.noticesTransparentClick || !this.isTransparentAt(point)) ? this - : null; + if (this.bounds.containsPoint(point)) { + if (this.holes.some( + any => any.translateBy(this.position()).containsPoint(point)) + ) { + return null; + } + if (this.isFreeForm) { + if (!this.isTransparentAt(point)) { + return this; + } + } else { + return this; + } + } + return null; }; Morph.prototype.topMorphSuchThat = function (predicate) { @@ -3764,15 +3740,14 @@ Morph.prototype.overlappedMorphs = function () { //exclude the World var world = this.world(), fb = this.fullBounds(), - myself = this, allParents = this.allParents(), allChildren = this.allChildren(), morphs; morphs = world.allChildren(); - return morphs.filter(function (m) { + return morphs.filter(m => { return m.isVisible && - m !== myself && + m !== this && m !== world && !contains(allParents, m) && !contains(allChildren, m) && @@ -3785,7 +3760,7 @@ Morph.prototype.overlappedMorphs = function () { Morph.prototype.getPixelColor = function (aPoint) { var point, context, data; point = aPoint.subtract(this.bounds.origin); - context = this.image.getContext('2d'); + context = this.cachedImage.getContext('2d'); data = context.getImageData(point.x, point.y, 1, 1); return new Color( data.data[0], @@ -3802,7 +3777,7 @@ Morph.prototype.isTransparentAt = function (aPoint) { return false; } point = aPoint.subtract(this.bounds.origin); - context = this.image.getContext('2d'); + context = this.getImage().getContext('2d'); data = context.getImageData( Math.floor(point.x), Math.floor(point.y), @@ -3833,9 +3808,7 @@ Morph.prototype.fullCopy = function () { */ var map = new Map(), c; c = this.copyRecordingReferences(map); - c.forAllChildren(function (m) { - m.updateReferences(map); - }); + c.forAllChildren(m => m.updateReferences(map)); return c; }; @@ -3854,9 +3827,7 @@ Morph.prototype.copyRecordingReferences = function (map) { */ var c = this.copy(); map.set(this, c); - this.children.forEach(function (m) { - c.add(m.copyRecordingReferences(map)); - }); + this.children.forEach(m => c.add(m.copyRecordingReferences(map))); return c; }; @@ -3950,18 +3921,17 @@ Morph.prototype.slideBackTo = function ( onBeforeDrop, onComplete ) { - var pos = situation.origin.position().add(situation.position), - myself = this; + var pos = situation.origin.position().add(situation.position); this.glideTo( pos, msecs, null, // easing - function () { - situation.origin.add(myself); + () => { + situation.origin.add(this); if (onBeforeDrop) {onBeforeDrop(); } - if (myself.justDropped) {myself.justDropped(); } + if (this.justDropped) {this.justDropped(); } if (situation.origin.reactToDropOf) { - situation.origin.reactToDropOf(myself); + situation.origin.reactToDropOf(this); } if (onComplete) {onComplete(); } } @@ -3972,22 +3942,21 @@ Morph.prototype.slideBackTo = function ( Morph.prototype.glideTo = function (endPoint, msecs, easing, onComplete) { var world = this.world(), - myself = this, horizontal = new Animation( - function (x) {myself.setLeft(x); }, - function () {return myself.left(); }, + x => this.setLeft(x), + () => this.left(), -(this.left() - endPoint.x), msecs || 100, easing ); world.animations.push(horizontal); world.animations.push(new Animation( - function (y) {myself.setTop(y); }, - function () {return myself.top(); }, + y => this.setTop(y), + () => this.top(), -(this.top() - endPoint.y), msecs || 100, easing, - function () { + () => { horizontal.setter(horizontal.destination); horizontal.isActive = false; onComplete(); @@ -4000,35 +3969,31 @@ Morph.prototype.fadeTo = function (endAlpha, msecs, easing, onComplete) { // include all my children, restore all original transparencies // on completion, so I can be recovered var world = this.world(), - myself = this, oldAlpha = this.alpha; - this.children.forEach(function (child) { - child.fadeTo(endAlpha, msecs, easing); - }); + this.children.forEach(child => child.fadeTo(endAlpha, msecs, easing)); world.animations.push(new Animation( - function (n) { - myself.alpha = n; - myself.changed(); + n => { + this.alpha = n; + this.changed(); }, - function () {return myself.alpha; }, + () => this.alpha, endAlpha - this.alpha, msecs || 200, easing, - function () { - myself.alpha = oldAlpha; + () => { + this.alpha = oldAlpha; if (onComplete) {onComplete(); } } )); }; Morph.prototype.perish = function (msecs, onComplete) { - var myself = this; this.fadeTo( 0, msecs || 100, null, - function () { - myself.destroy(); + () => { + this.destroy(); if (onComplete) {onComplete(); } } ); @@ -4141,18 +4106,18 @@ Morph.prototype.prompt = function ( slider.button.pressColor.b += 150; slider.setHeight(MorphicPreferences.prompterSliderSize); if (isRounded) { - slider.action = function (num) { + slider.action = (num) => { entryField.changed(); entryField.text.text = Math.round(num).toString(); - entryField.text.drawNew(); + entryField.text.fixLayout(); entryField.text.changed(); entryField.text.edit(); }; } else { - slider.action = function (num) { + slider.action = (num) => { entryField.changed(); entryField.text.text = num.toString(); - entryField.text.drawNew(); + entryField.text.fixLayout(); entryField.text.changed(); }; } @@ -4160,12 +4125,8 @@ Morph.prototype.prompt = function ( } menu.addLine(2); - menu.addItem('Ok', function () { - return entryField.string(); - }); - menu.addItem('Cancel', function () { - return null; - }); + menu.addItem('Ok', () => entryField.string()); + menu.addItem('Cancel', () => null); menu.isDraggable = true; menu.popUpAtHand(this.world()); entryField.text.edit(); @@ -4186,12 +4147,8 @@ Morph.prototype.pickColor = function ( colorPicker = new ColorPickerMorph(defaultContents); menu.items.push(colorPicker); menu.addLine(2); - menu.addItem('Ok', function () { - return colorPicker.getChoice(); - }); - menu.addItem('Cancel', function () { - return null; - }); + menu.addItem('Ok', () => colorPicker.getChoice()); + menu.addItem('Cancel', () => null); menu.isDraggable = true; menu.popUpAtHand(this.world()); }; @@ -4212,6 +4169,28 @@ Morph.prototype.inspect = function (anotherObject) { inspector.changed(); }; +Morph.prototype.inspectKeyEvent = function (event) { + this.inform( + 'Key pressed: ' + + String.fromCharCode(event.charCode) + + '\n------------------------' + + '\ncharCode: ' + + event.charCode.toString() + + '\nkeyCode: ' + + event.keyCode.toString() + + '\nkey: ' + + event.key.toString() + + '\nshiftKey: ' + + event.shiftKey.toString() + + '\naltKey: ' + + event.altKey.toString() + + '\nctrlKey: ' + + event.ctrlKey.toString() + + '\ncmdKey: ' + + event.metaKey.toString() + ); +}; + // Morph menus: Morph.prototype.contextMenu = function () { @@ -4236,17 +4215,12 @@ Morph.prototype.hierarchyMenu = function () { world = this.world instanceof Function ? this.world() : this.world, menu = new MenuMorph(this, null); - parents.forEach(function (each) { + parents.forEach(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; @@ -4265,7 +4239,7 @@ Morph.prototype.developersMenu = function () { } menu.addItem( "color...", - function () { + () => { this.pickColor( menu.title + localize('\ncolor:'), this.setColor, @@ -4277,7 +4251,7 @@ Morph.prototype.developersMenu = function () { ); menu.addItem( "transparency...", - function () { + () => { this.prompt( menu.title + localize('\nalpha\nvalue:'), this.setAlphaScaled, @@ -4300,9 +4274,7 @@ Morph.prototype.developersMenu = function () { menu.addLine(); menu.addItem( "duplicate", - function () { - this.fullCopy().pickUp(this.world()); - }, + () => this.fullCopy().pickUp(this.world()), 'make a copy\nand pick it up' ); menu.addItem( @@ -4327,9 +4299,7 @@ Morph.prototype.developersMenu = function () { ); menu.addItem( "pic...", - function () { - window.open(this.fullImageClassic().toDataURL()); - }, + () => window.open(this.fullImage().toDataURL()), 'open a new window\nwith a picture of this morph' ); menu.addLine(); @@ -4352,9 +4322,7 @@ Morph.prototype.developersMenu = function () { menu.addLine(); menu.addItem( "World...", - function () { - world.contextMenu().popUpAtHand(world); - }, + () => world.contextMenu().popUpAtHand(world), 'show the\nWorld\'s menu' ); } @@ -4385,13 +4353,12 @@ Morph.prototype.setAlphaScaled = function (alpha) { Morph.prototype.attach = function () { var choices = this.overlappedMorphs(), - menu = new MenuMorph(this, 'choose new parent:'), - myself = this; + menu = new MenuMorph(this, 'choose new parent:'); - choices.forEach(function (each) { - menu.addItem(each.toString().slice(0, 50), function () { - each.add(myself); - myself.isDraggable = false; + choices.forEach(each => { + menu.addItem(each.toString().slice(0, 50), () => { + each.add(this); + this.isDraggable = false; }); }); if (choices.length > 0) { @@ -4423,7 +4390,7 @@ Morph.prototype.numericalSetters = function () { // Morph entry field tabbing: Morph.prototype.allEntryFields = function () { - return this.allChildren().filter(function (each) { + return this.allChildren().filter(each => { return each.isEditable && (each instanceof StringMorph || each instanceof TextMorph); @@ -4520,7 +4487,6 @@ Morph.prototype.evaluateString = function (code) { try { result = eval(code); - this.drawNew(); this.changed(); } catch (err) { this.inform(err); @@ -4591,6 +4557,11 @@ function ShadowMorph() { this.init(); } +ShadowMorph.prototype.init = function () { + ShadowMorph.uber.init.call(this); + this.isCachingImage = true; +}; + ShadowMorph.prototype.topMorphAt = function () { return null; }; @@ -4625,37 +4596,20 @@ HandleMorph.prototype.init = function ( this.minExtent = new Point(minX || 0, minY || 0); this.inset = new Point(insetX || 0, insetY || insetX || 0); this.type = type || 'resize'; // also: 'move', 'moveCenter', 'movePivot' + this.isHighlighted = false; HandleMorph.uber.init.call(this); this.color = new Color(255, 255, 255); this.isDraggable = false; - this.noticesTransparentClick = true; if (this.type === 'movePivot') { size *= 2; } this.setExtent(new Point(size, size)); + this.fixLayout(); }; // HandleMorph drawing: -HandleMorph.prototype.drawNew = function () { - this.normalImage = newCanvas(this.extent(), false, this.normalImage); - this.highlightImage = newCanvas(this.extent(), false, this.highlightImage); - if (this.type === 'movePivot') { - this.drawCrosshairsOnCanvas(this.normalImage, 0.6); - this.drawCrosshairsOnCanvas(this.highlightImage, 0.5); - } else { - this.drawOnCanvas( - this.normalImage, - this.color, - new Color(100, 100, 100) - ); - this.drawOnCanvas( - this.highlightImage, - new Color(100, 100, 255), - new Color(255, 255, 255) - ); - } - this.image = this.normalImage; +HandleMorph.prototype.fixLayout = function () { if (this.target) { if (this.type === 'moveCenter') { this.setCenter(this.target.center()); @@ -4673,113 +4627,140 @@ HandleMorph.prototype.drawNew = function () { } }; -HandleMorph.prototype.drawCrosshairsOnCanvas = function (aCanvas, fract) { - var ctx = aCanvas.getContext('2d'), - r = aCanvas.width / 2; +HandleMorph.prototype.render = function (ctx) { + if (this.type === 'movePivot') { + if (this.isHighlighted) { + this.renderCrosshairsOn(ctx, 0.5); + } else { + this.renderCrosshairsOn(ctx, 0.6); + } + } else { + if (this.isHighlighted) { + this.renderHandleOn( + ctx, + new Color(100, 100, 255), + new Color(255, 255, 255) + ); + } else { + this.renderHandleOn( + ctx, + this.color, + new Color(100, 100, 100) + ); + } + } +}; + +HandleMorph.prototype.renderCrosshairsOn = function (ctx, fract) { + var r = this.width() / 2; + + // semi-transparent white background blob ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; - ctx.arc(r, r, r * 0.9, radians(0), radians(360), false); + ctx.beginPath(); + ctx.arc( + r, + r, + r * 0.9, + radians(0), + radians(360), + false + ); ctx.fill(); + + // solid black ring ctx.strokeStyle = 'black'; ctx.lineWidth = 1; ctx.beginPath(); - ctx.arc(r, r, r * fract, radians(0), radians(360), false); + ctx.arc( + r, + r, + r * fract, + radians(0), + radians(360), + false + ); ctx.stroke(); + + // vertically centered horizontal line ctx.moveTo(0, r); - ctx.lineTo(aCanvas.width, r); + ctx.lineTo(this.width(), r); ctx.stroke(); + + // horizontally centered vertical line ctx.moveTo(r, 0); - ctx.lineTo(r, aCanvas.height); + ctx.lineTo(r, this.height()); ctx.stroke(); }; -HandleMorph.prototype.drawOnCanvas = function ( - aCanvas, +HandleMorph.prototype.renderHandleOn = function ( + ctx, color, shadowColor ) { - var context = aCanvas.getContext('2d'), - isSquare = (this.type.indexOf('move') === 0), - p1, - p11, - p2, - p22, - i; + var isSquare = (this.type.indexOf('move') === 0), + p1 = new Point(0, this.height()), // bottom left + p2 = new Point(this.width(), 0), // top right + p11, p22, i; - context.lineWidth = 1; - context.lineCap = 'round'; - - context.strokeStyle = color.toString(); + ctx.lineWidth = 1; + ctx.lineCap = 'round'; + ctx.strokeStyle = color.toString(); if (isSquare) { - - p1 = this.bottomLeft().subtract(this.position()); p11 = p1.copy(); - p2 = this.topRight().subtract(this.position()); p22 = p2.copy(); - for (i = 0; i <= this.height(); i = i + 6) { p11.y = p1.y - i; p22.y = p2.y - i; - context.beginPath(); - context.moveTo(p11.x, p11.y); - context.lineTo(p22.x, p22.y); - context.closePath(); - context.stroke(); + ctx.beginPath(); + ctx.moveTo(p11.x, p11.y); + ctx.lineTo(p22.x, p22.y); + ctx.closePath(); + ctx.stroke(); } } - p1 = this.bottomLeft().subtract(this.position()); p11 = p1.copy(); - p2 = this.topRight().subtract(this.position()); p22 = p2.copy(); - for (i = 0; i <= this.width(); i = i + 6) { p11.x = p1.x + i; p22.x = p2.x + i; - context.beginPath(); - context.moveTo(p11.x, p11.y); - context.lineTo(p22.x, p22.y); - context.closePath(); - context.stroke(); + ctx.beginPath(); + ctx.moveTo(p11.x, p11.y); + ctx.lineTo(p22.x, p22.y); + ctx.closePath(); + ctx.stroke(); } - - context.strokeStyle = shadowColor.toString(); + ctx.strokeStyle = shadowColor.toString(); if (isSquare) { - - p1 = this.bottomLeft().subtract(this.position()); p11 = p1.copy(); - p2 = this.topRight().subtract(this.position()); p22 = p2.copy(); - for (i = -2; i <= this.height(); i = i + 6) { p11.y = p1.y - i; p22.y = p2.y - i; - context.beginPath(); - context.moveTo(p11.x, p11.y); - context.lineTo(p22.x, p22.y); - context.closePath(); - context.stroke(); + ctx.beginPath(); + ctx.moveTo(p11.x, p11.y); + ctx.lineTo(p22.x, p22.y); + ctx.closePath(); + ctx.stroke(); } } - p1 = this.bottomLeft().subtract(this.position()); p11 = p1.copy(); - p2 = this.topRight().subtract(this.position()); p22 = p2.copy(); - for (i = 2; i <= this.width(); i = i + 6) { p11.x = p1.x + i; p22.x = p2.x + i; - context.beginPath(); - context.moveTo(p11.x, p11.y); - context.lineTo(p22.x, p22.y); - context.closePath(); - context.stroke(); + ctx.beginPath(); + ctx.moveTo(p11.x, p11.y); + ctx.lineTo(p22.x, p22.y); + ctx.closePath(); + ctx.stroke(); } }; @@ -4789,8 +4770,7 @@ HandleMorph.prototype.step = null; HandleMorph.prototype.mouseDownLeft = function (pos) { var world = this.root(), - offset, - myself = this; + offset; if (!this.target) { return null; @@ -4800,29 +4780,30 @@ HandleMorph.prototype.mouseDownLeft = function (pos) { } else { offset = pos.subtract(this.bounds.origin); } - this.step = function () { + + this.step = () => { var newPos, newExt; if (world.hand.mouseButton) { newPos = world.hand.bounds.origin.copy().subtract(offset); if (this.type === 'resize') { newExt = newPos.add( - myself.extent().add(myself.inset) - ).subtract(myself.target.bounds.origin); - newExt = newExt.max(myself.minExtent); - myself.target.setExtent(newExt); + this.extent().add(this.inset) + ).subtract(this.target.bounds.origin); + newExt = newExt.max(this.minExtent); + this.target.setExtent(newExt); - myself.setPosition( - myself.target.bottomRight().subtract( - myself.extent().add(myself.inset) + this.setPosition( + this.target.bottomRight().subtract( + this.extent().add(this.inset) ) ); } else if (this.type === 'moveCenter') { - myself.target.setCenter(newPos); + this.target.setCenter(newPos); } else if (this.type === 'movePivot') { - myself.target.setPivot(newPos); - myself.setCenter(this.target.rotationCenter()); + this.target.setPivot(newPos); + this.setCenter(this.target.rotationCenter()); } else { // type === 'move' - myself.target.setPosition( + this.target.setPosition( newPos.subtract(this.target.extent()) .add(this.extent()) ); @@ -4831,10 +4812,9 @@ HandleMorph.prototype.mouseDownLeft = function (pos) { this.step = null; } }; + if (!this.target.step) { - this.target.step = function () { - nop(); - }; + this.target.step = nop; } }; @@ -4847,12 +4827,12 @@ HandleMorph.prototype.rootForGrab = function () { // HandleMorph events: HandleMorph.prototype.mouseEnter = function () { - this.image = this.highlightImage; + this.isHighlighted = true; this.changed(); }; HandleMorph.prototype.mouseLeave = function () { - this.image = this.normalImage; + this.isHighlighted = false; this.changed(); }; @@ -4860,15 +4840,13 @@ HandleMorph.prototype.mouseLeave = function () { HandleMorph.prototype.attach = function () { var choices = this.overlappedMorphs(), - menu = new MenuMorph(this, 'choose target:'), - myself = this; + menu = new MenuMorph(this, 'choose target:'); - choices.forEach(function (each) { - menu.addItem(each.toString().slice(0, 50), function () { - myself.isDraggable = false; - myself.target = each; - myself.drawNew(); - myself.noticesTransparentClick = true; + choices.forEach(each => { + menu.addItem(each.toString().slice(0, 50), () => { + this.isDraggable = false; + this.target = each; + this.fixLayout(); }); }); if (choices.length > 0) { @@ -4928,22 +4906,20 @@ PenMorph.prototype.changed = function () { // PenMorph display: -PenMorph.prototype.drawNew = function (facing, recycleImage) { +PenMorph.prototype.render = function (ctx, facing) { // my orientation can be overridden with the "facing" parameter to // implement Scratch-style rotation styles - // if a recycleImage canvas is given, it will be reused - // instead of creating a new one - var context, start, dest, left, right, len, + var start, dest, left, right, len, direction = facing || this.heading; +/* +++ should be obsolete if (this.isWarped) { this.wantsRedraw = true; return; } +*/ - this.image = newCanvas(this.extent(), null, recycleImage); - context = this.image.getContext('2d'); len = this.width() / 2; start = this.center().subtract(this.bounds.origin); @@ -4966,30 +4942,29 @@ PenMorph.prototype.drawNew = function (facing, recycleImage) { ); // draw arrow shape - context.fillStyle = this.color.toString(); - context.beginPath(); + ctx.fillStyle = this.color.toString(); + ctx.beginPath(); - context.moveTo(start.x, start.y); - context.lineTo(left.x, left.y); - context.lineTo(dest.x, dest.y); - context.lineTo(right.x, right.y); + ctx.moveTo(start.x, start.y); + ctx.lineTo(left.x, left.y); + ctx.lineTo(dest.x, dest.y); + ctx.lineTo(right.x, right.y); - context.closePath(); - context.strokeStyle = 'white'; - context.lineWidth = 3; - context.stroke(); - context.strokeStyle = 'black'; - context.lineWidth = 1; - context.stroke(); - context.fill(); + ctx.closePath(); + ctx.strokeStyle = 'white'; + ctx.lineWidth = 3; + ctx.stroke(); + ctx.strokeStyle = 'black'; + ctx.lineWidth = 1; + ctx.stroke(); + ctx.fill(); }; // PenMorph access: PenMorph.prototype.setHeading = function (degrees) { this.heading = ((+degrees % 360) + 360) % 360; - this.drawNew(); - this.changed(); + this.rerender(); }; PenMorph.prototype.numericalSetters = function () { @@ -5025,15 +5000,15 @@ PenMorph.prototype.setRotation = function () { } menu = new MenuMorph(this, name); dial = new DialMorph(null, null, this.heading); - dial.rootForGrab = function () {return this; }; + dial.rootForGrab = () => dial; dial.target = this; dial.action = 'setHeading'; menu.items.push(dial); menu.addLine(); - menu.addItem('(90) right', function () {this.setHeading(90); }); - menu.addItem('(-90) left', function () {this.setHeading(-90); }); - menu.addItem('(0) up', function () {this.setHeading(0); }); - menu.addItem('(180) down', function () {this.setHeading(180); }); + menu.addItem('(90) right', () => this.setHeading(90)); + menu.addItem('(-90) left', () => this.setHeading(-90)); + menu.addItem('(0) up', () => this.setHeading(0)); + menu.addItem('(180) down', () => this.setHeading(180)); menu.isDraggable = true; menu.popUpAtHand(this.world()); }; @@ -5094,21 +5069,21 @@ PenMorph.prototype.up = function () { }; PenMorph.prototype.clear = function () { - this.parent.drawNew(); - this.parent.changed(); + this.parent.rerender(); }; // PenMorph optimization for atomic recursion: PenMorph.prototype.startWarp = function () { - this.wantsRedraw = false; + this.wantsRedraw = false; // +++ should be obsolete now this.isWarped = true; }; PenMorph.prototype.endWarp = function () { this.isWarped = false; + //+++ should be obsolete now if (this.wantsRedraw) { - this.drawNew(); + // +++ this.drawNew(); this.wantsRedraw = false; } this.parent.changed(); @@ -5182,26 +5157,24 @@ function ColorPaletteMorph(target, sizePoint) { ColorPaletteMorph.prototype.init = function (target, size) { ColorPaletteMorph.uber.init.call(this); + this.isCachingImage = true; this.target = target; this.targetSetter = 'color'; - this.silentSetExtent(size); + this.setExtent(size); this.choice = null; - this.drawNew(); }; -ColorPaletteMorph.prototype.drawNew = function () { - var context, ext, x, y, h, l; +ColorPaletteMorph.prototype.render = function (ctx) { + var ext = this.extent(), + x, y, h, l; - ext = this.extent(); - this.image = newCanvas(this.extent(), false, this.image); - context = this.image.getContext('2d'); this.choice = new Color(); for (x = 0; x <= ext.x; x += 1) { h = 360 * x / ext.x; for (y = 0; y <= ext.y; y += 1) { l = 100 - (y / ext.y * 100); - context.fillStyle = 'hsl(' + h + ',100%,' + l + '%)'; - context.fillRect(x, y, 1, 1); + ctx.fillStyle = 'hsl(' + h + ',100%,' + l + '%)'; + ctx.fillRect(x, y, 1, 1); } } }; @@ -5222,8 +5195,7 @@ ColorPaletteMorph.prototype.updateTarget = function () { this.target[this.targetSetter](this.choice); } else { this.target[this.targetSetter] = this.choice; - this.target.drawNew(); - this.target.changed(); + this.target.rerender(); } } }; @@ -5244,14 +5216,13 @@ ColorPaletteMorph.prototype.developersMenu = function () { ColorPaletteMorph.prototype.setTarget = function () { var choices = this.overlappedMorphs(), - menu = new MenuMorph(this, 'choose target:'), - myself = this; + menu = new MenuMorph(this, 'choose target:'); choices.push(this.world()); - choices.forEach(function (each) { - menu.addItem(each.toString().slice(0, 50), function () { - myself.target = each; - myself.setTargetSetter(); + choices.forEach(each => { + menu.addItem(each.toString().slice(0, 50), () => { + this.target = each; + this.setTargetSetter(); }); }); if (choices.length === 1) { @@ -5264,13 +5235,10 @@ ColorPaletteMorph.prototype.setTarget = function () { ColorPaletteMorph.prototype.setTargetSetter = function () { var choices = this.target.colorSetters(), - menu = new MenuMorph(this, 'choose target property:'), - myself = this; + menu = new MenuMorph(this, 'choose target property:'); - choices.forEach(function (each) { - menu.addItem(each, function () { - myself.targetSetter = each; - }); + choices.forEach(each => { + menu.addItem(each, () => this.targetSetter = each); }); if (choices.length === 1) { this.targetSetter = choices[0]; @@ -5298,18 +5266,16 @@ function GrayPaletteMorph(target, sizePoint) { ); } -GrayPaletteMorph.prototype.drawNew = function () { - var context, ext, gradient; +GrayPaletteMorph.prototype.render = function (ctx) { + var ext = this.extent(), + gradient; - ext = this.extent(); - this.image = newCanvas(this.extent(), false, this.image); - context = this.image.getContext('2d'); this.choice = new Color(); - gradient = context.createLinearGradient(0, 0, ext.x, ext.y); + gradient = ctx.createLinearGradient(0, 0, ext.x, ext.y); gradient.addColorStop(0, 'black'); gradient.addColorStop(1, 'white'); - context.fillStyle = gradient; - context.fillRect(0, 0, ext.x, ext.y); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, ext.x, ext.y); }; // ColorPickerMorph /////////////////////////////////////////////////// @@ -5330,21 +5296,20 @@ ColorPickerMorph.prototype.init = function (defaultColor) { this.choice = defaultColor; ColorPickerMorph.uber.init.call(this); this.color = new Color(255, 255, 255); - this.silentSetExtent(new Point(80, 80)); - this.drawNew(); + this.setExtent(new Point(80, 80)); }; +/* ColorPickerMorph.prototype.drawNew = function () { ColorPickerMorph.uber.drawNew.call(this); this.buildSubmorphs(); }; +*/ -ColorPickerMorph.prototype.buildSubmorphs = function () { +ColorPickerMorph.prototype.fixLayout = function () { var cpal, gpal, x, y; - this.children.forEach(function (child) { - child.destroy(); - }); + this.children.forEach(child => child.destroy()); this.children = []; this.feedback = new Morph(); this.feedback.color = this.choice; @@ -5399,7 +5364,6 @@ BlinkerMorph.prototype.init = function (rate) { BlinkerMorph.uber.init.call(this); this.color = new Color(0, 0, 0); this.fps = rate || 2; - this.drawNew(); }; // BlinkerMorph stepping: @@ -5428,11 +5392,11 @@ CursorMorph.prototype.viewPadding = 1; // CursorMorph instance creation: -function CursorMorph(aStringOrTextMorph) { - this.init(aStringOrTextMorph); +function CursorMorph(aStringOrTextMorph, aTextarea) { + this.init(aStringOrTextMorph, aTextarea); } -CursorMorph.prototype.init = function (aStringOrTextMorph) { +CursorMorph.prototype.init = function (aStringOrTextMorph, aTextarea) { var ls; // additional properties: @@ -5441,199 +5405,203 @@ CursorMorph.prototype.init = function (aStringOrTextMorph) { this.originalContents = this.target.text; this.originalAlignment = this.target.alignment; this.slot = this.target.text.length; - this.textarea = null; + this.textarea = aTextarea; CursorMorph.uber.init.call(this); // override inherited defaults ls = fontHeight(this.target.fontSize); this.setExtent(new Point(Math.max(Math.floor(ls / 20), 1), ls)); - this.drawNew(); - this.image.getContext('2d').font = this.target.font(); + if (this.target instanceof TextMorph && (this.target.alignment !== 'left')) { this.target.setAlignmentToLeft(); } - this.gotoSlot(this.slot); - this.initializeTextarea(); -}; - -CursorMorph.prototype.initializeTextarea = function () { - var myself = this; - - this.textarea = document.createElement('textarea'); - this.textarea.style.zIndex = -1; - this.textarea.style.position = 'absolute'; - this.textarea.wrap = "off"; - this.textarea.style.overflow = "hidden"; - this.textarea.style.fontSize = this.target.fontSize + 'px'; - this.textarea.autofocus = true; this.textarea.value = this.target.text; - document.body.appendChild(this.textarea); + this.textarea.style.fontSize = this.target.fontSize + 'px'; + this.gotoSlot(this.slot); this.updateTextAreaPosition(); this.syncTextareaSelectionWith(this.target); - - - /* - There are three cases when the textarea gets inputs: - - 1. Inputs that represent special shortcuts of Snap!, so we - don't want the textarea to handle it. These events are captured in - "keydown" event handler. - - 2. inputs that change the content of the textarea, we need to update - the content of its target morph accordingly. This is handled in - the "input" event handler. - - 3. input that change the textarea without triggering an "input" event, - e.g. selection change, cursor movements. These are handled in the - "keyup" event handler. - - Note that some changes in case 2 are not caused by keyboards (for - example, select a word by clicking in IME window), so there are overlaps - between case 2 and case 3. but no one can replace the other. - */ - - this.textarea.addEventListener('keydown', function (event) { - /* Special shortcuts for Snap! system. - - ctrl-d, ctrl-i and ctrl-p: doit, inspect it and print it - - tab: goto next text field - - esc: discard the editing - - enter / shift+enter: accept the editing - */ - var keyName = event.key, - shift = event.shiftKey, - singleLineText = myself.target instanceof StringMorph; - - // other parts of the world need to know the current key - myself.world().currentKey = event.keyCode; - - if (!isNil(myself.target.receiver) && - (event.ctrlKey || event.metaKey)) { - if (keyName === 'd') { - myself.target.doIt(); - } else if (keyName === 'i') { - myself.target.inspectIt(); - } else if (keyName === 'p') { - myself.target.showIt(); - } - event.preventDefault(); - } else if (keyName === 'Tab' || keyName === 'U+0009') { - if (shift) { - myself.target.backTab(myself.target); - } else { - myself.target.tab(myself.target); - } - event.preventDefault(); - myself.target.escalateEvent('reactToEdit', myself.target); - } else if (keyName === 'Escape') { - myself.cancel(); - } else if (keyName === "Enter" && (singleLineText || shift)) { - myself.accept(); - } else { - myself.target.escalateEvent('reactToKeystroke', event); - } - }); - - this.textarea.addEventListener('input', function (event) { - // handle content change. - var target = myself.target, - textarea = myself.textarea, - filteredContent, - caret; - - myself.world().currentKey = null; - - // filter invalid chars for numeric fields - function filterText (content) { - var points = 0, - result = '', - i, ch, valid; - for (i = 0; i < content.length; i += 1) { - ch = content.charAt(i); - valid = ( - ('0' <= ch && ch <= '9') || // digits - (i === 0 && ch === '-') || // leading '-' - (ch === '.' && points === 0) // at most '.' - ); - if (valid) { - result += ch; - if (ch === '.') { - points += 1; - } - } - } - return result; - } - - if (target.isNumeric) { - filteredContent = filterText(textarea.value); - } else { - filteredContent = textarea.value; - } - - if (filteredContent.length < textarea.value.length) { - textarea.value = filteredContent; - caret = Math.min(textarea.selectionStart, filteredContent.length); - textarea.selectionEnd = caret; - textarea.selectionStart = caret; - } - // target morph: copy the content and selection status to the target. - target.text = filteredContent; - - if (textarea.selectionStart === textarea.selectionEnd) { - target.startMark = null; - target.endMark = null; - } else { - if (textarea.selectionDirection === 'backward') { - target.startMark = textarea.selectionEnd; - target.endMark = textarea.selectionStart; - } else { - target.startMark = textarea.selectionStart; - target.endMark = textarea.selectionEnd; - } - } - target.changed(); - target.drawNew(); - target.changed(); - - // cursor morph: copy the caret position to cursor morph. - myself.gotoSlot(textarea.selectionStart); - - myself.updateTextAreaPosition(); - - // the "reactToInput" event gets triggered AFTER "reactToKeystroke" - myself.target.escalateEvent('reactToInput', event); - - }); - - this.textarea.addEventListener('keyup', function (event) { - // handle selection change and cursor position change. - var textarea = myself.textarea, - target = myself.target; - - if (textarea.selectionStart === textarea.selectionEnd) { - target.startMark = null; - target.endMark = null; - } else { - if (textarea.selectionDirection === 'backward') { - target.startMark = textarea.selectionEnd; - target.endMark = textarea.selectionStart; - } else { - target.startMark = textarea.selectionStart; - target.endMark = textarea.selectionEnd; - } - } - target.changed(); - target.drawNew(); - target.changed(); - myself.gotoSlot(textarea.selectionEnd); - }); }; -CursorMorph.prototype.updateTextAreaPosition = function () { - var origin = this.target.bounds.origin; +// CursorMorph event handling + /* + There are three cases when the textarea gets inputs: + + 1. Inputs that represent special shortcuts of Snap!, so we + don't want the textarea to handle it. These events are captured in + "keydown" event handler. + + 2. inputs that change the content of the textarea, we need to update + the content of its target morph accordingly. This is handled in + the "input" event handler. + + 3. input that change the textarea without triggering an "input" event, + e.g. selection change, cursor movements. These are handled in the + "keyup" event handler. + + Note that some changes in case 2 are not caused by keyboards (for + example, select a word by clicking in IME window), so there are overlaps + between case 2 and case 3. but no one can replace the other. + */ + +CursorMorph.prototype.processKeyDown = function (event) { + /* Special shortcuts + - ctrl-d, ctrl-i and ctrl-p: doit, inspect it and print it + - tab: goto next text field + - esc: discard the editing + - enter / shift+enter: accept the editing + */ + var keyName = event.key, + shift = event.shiftKey, + singleLineText = this.target instanceof StringMorph, + dest; + + if (!isNil(this.target.receiver) && (event.ctrlKey || event.metaKey)) { + if (keyName === 'd') { + event.preventDefault(); + this.target.doIt(); + return; + } else if (keyName === 'i') { + event.preventDefault(); + this.target.inspectIt(); + return; + } else if (keyName === 'p') { + event.preventDefault(); + this.target.showIt(); + return; + } + } + + if (keyName === 'Tab' || keyName === 'U+0009') { + // +++ to do: refactor to use a CASE statement here + if (shift) { + this.target.backTab(this.target); + } else { + this.target.tab(this.target); + } + event.preventDefault(); + this.target.escalateEvent('reactToEdit', this.target); + } else if (keyName === 'Escape') { + this.cancel(); + } else if (keyName === "Enter" && (singleLineText || shift)) { + this.accept(); + } else { + // catch "up arrow" and "down arrow" keys + if (keyName === 'ArrowDown') { + dest = this.target.downFrom(this.slot); + this.textarea.setSelectionRange(dest, dest); + // +++ to do: allow holding shift to select + event.preventDefault(); + } + if (keyName === 'ArrowUp') { + dest = this.target.upFrom(this.slot); + this.textarea.setSelectionRange(dest, dest); + // +++ to do: allow holding shift to select + event.preventDefault(); + } + this.target.escalateEvent('reactToKeystroke', event); + } +}; + +CursorMorph.prototype.processKeyUp = function (event) { + // handle selection change and cursor position change. + var textarea = this.textarea, + target = this.target; + + if (textarea.selectionStart === textarea.selectionEnd) { + target.startMark = null; + target.endMark = null; + } else { + if (textarea.selectionDirection === 'backward') { + target.startMark = textarea.selectionEnd; + target.endMark = textarea.selectionStart; + } else { + target.startMark = textarea.selectionStart; + target.endMark = textarea.selectionEnd; + } + } + target.fixLayout(); + target.rerender(); + this.gotoSlot(textarea.selectionEnd); +}; + +CursorMorph.prototype.processInput = function (event) { + // handle content change. + var target = this.target, + textarea = this.textarea, + filteredContent, + caret; + + // filter invalid chars for numeric fields + function filterText (content) { + var points = 0, + result = '', + i, ch, valid; + for (i = 0; i < content.length; i += 1) { + ch = content.charAt(i); + valid = ( + ('0' <= ch && ch <= '9') || // digits + (i === 0 && ch === '-') || // leading '-' + (ch === '.' && points === 0) // at most '.' + ); + if (valid) { + result += ch; + if (ch === '.') { + points += 1; + } + } + } + return result; + } + + if (target.isNumeric) { + filteredContent = filterText(textarea.value); + } else { + filteredContent = textarea.value; + } + + if (filteredContent.length < textarea.value.length) { + textarea.value = filteredContent; + caret = Math.min(textarea.selectionStart, filteredContent.length); + textarea.selectionEnd = caret; + textarea.selectionStart = caret; + } + // target morph: copy the content and selection status to the target. + target.text = filteredContent; + + if (textarea.selectionStart === textarea.selectionEnd) { + target.startMark = null; + target.endMark = null; + } else { + if (textarea.selectionDirection === 'backward') { + target.startMark = textarea.selectionEnd; + target.endMark = textarea.selectionStart; + } else { + target.startMark = textarea.selectionStart; + target.endMark = textarea.selectionEnd; + } + } + target.changed(); + target.fixLayout(); + target.rerender(); + + // cursor morph: copy the caret position to cursor morph. + this.gotoSlot(textarea.selectionStart); + + this.updateTextAreaPosition(); + + // the "reactToInput" event gets triggered AFTER "reactToKeystroke" + this.target.escalateEvent('reactToInput', event); +}; + +// CursorMorph synching: + +CursorMorph.prototype.updateTextAreaPosition = function () { + var pos = getDocumentPositionOf(this.target.world().worldCanvas), + origin = this.target.bounds.origin.add(new Point(pos.x, pos.y)); + function number2px (n) { return Math.ceil(n) + 'px'; } @@ -5656,149 +5624,8 @@ CursorMorph.prototype.syncTextareaSelectionWith = function (targetMorph) { this.textarea.focus(); }; -// CursorMorph event processing: - -CursorMorph.prototype.processKeyPress = function (event) { - // this.inspectKeyEvent(event); - if (this.keyDownEventUsed) { - this.keyDownEventUsed = false; - return null; - } - if ((event.keyCode === 40) || event.charCode === 40) { - this.insert('('); - return null; - } - if ((event.keyCode === 37) || event.charCode === 37) { - this.insert('%'); - return null; - } - if (event.keyCode) { // Opera doesn't support charCode - if (event.ctrlKey && (!event.altKey)) { - this.ctrl(event.keyCode, event.shiftKey); - } else if (event.metaKey) { - this.cmd(event.keyCode, event.shiftKey); - } else { - this.insert( - String.fromCharCode(event.keyCode), - event.shiftKey - ); - } - } else if (event.charCode) { // all other browsers - if (event.ctrlKey && (!event.altKey)) { - this.ctrl(event.charCode, event.shiftKey); - } else if (event.metaKey) { - this.cmd(event.charCode, event.shiftKey); - } else { - this.insert( - String.fromCharCode(event.charCode), - event.shiftKey - ); - } - } - // notify target's parent of key event - this.target.escalateEvent('reactToKeystroke', event); -}; - -CursorMorph.prototype.processKeyDown = function (event) { - // this.inspectKeyEvent(event); - 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); - } - if (event.metaKey) { - this.cmd(event.keyCode, event.shiftKey); - // notify target's parent of key event - this.target.escalateEvent('reactToKeystroke', event); - } - - switch (event.keyCode) { - case 37: - 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: - 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: - this.goUp(shift); - this.keyDownEventUsed = true; - break; - case 40: - this.goDown(shift); - this.keyDownEventUsed = true; - break; - case 36: - this.goHome(shift); - this.keyDownEventUsed = true; - break; - case 35: - this.goEnd(shift); - this.keyDownEventUsed = true; - break; - case 46: - this.deleteRight(); - this.keyDownEventUsed = true; - break; - case 8: - this.deleteLeft(); - this.keyDownEventUsed = true; - break; - case 13: - if ((this.target instanceof StringMorph) || shift) { - this.accept(); - } else { - this.insert('\n'); - } - this.keyDownEventUsed = true; - break; - case 27: - this.cancel(); - this.keyDownEventUsed = true; - break; - default: - nop(); - // this.inspectKeyEvent(event); - } - // notify target's parent of key event - this.target.escalateEvent('reactToKeystroke', event); -}; - // CursorMorph navigation: -/* -// original non-scrolling code, commented out in case we need to fall back: - -CursorMorph.prototype.gotoSlot = function (newSlot) { - this.setPosition(this.target.slotPosition(newSlot)); - this.slot = Math.max(newSlot, 0); -}; -*/ - CursorMorph.prototype.gotoSlot = function (slot) { var length = this.target.text.length, pos = this.target.slotPosition(slot), @@ -5832,42 +5659,6 @@ CursorMorph.prototype.gotoSlot = function (slot) { } }; -CursorMorph.prototype.goLeft = function (shift, howMany) { - this.updateSelection(shift); - this.gotoSlot(this.slot - (howMany || 1)); - this.updateSelection(shift); -}; - -CursorMorph.prototype.goRight = function (shift, howMany) { - this.updateSelection(shift); - this.gotoSlot(this.slot + (howMany || 1)); - this.updateSelection(shift); -}; - -CursorMorph.prototype.goUp = function (shift) { - this.updateSelection(shift); - this.gotoSlot(this.target.upFrom(this.slot)); - this.updateSelection(shift); -}; - -CursorMorph.prototype.goDown = function (shift) { - this.updateSelection(shift); - this.gotoSlot(this.target.downFrom(this.slot)); - this.updateSelection(shift); -}; - -CursorMorph.prototype.goHome = function (shift) { - this.updateSelection(shift); - this.gotoSlot(this.target.startOfLine(this.slot)); - this.updateSelection(shift); -}; - -CursorMorph.prototype.goEnd = function (shift) { - this.updateSelection(shift); - this.gotoSlot(this.target.endOfLine(this.slot)); - this.updateSelection(shift); -}; - CursorMorph.prototype.gotoPos = function (aPoint) { this.gotoSlot(this.target.slotAt(aPoint)); this.show(); @@ -5882,7 +5673,6 @@ CursorMorph.prototype.updateSelection = function (shift) { this.target.endMark = this.slot; } else if (this.target.endMark !== this.slot) { this.target.endMark = this.slot; - this.target.drawNew(); this.target.changed(); } } else { @@ -5912,152 +5702,20 @@ CursorMorph.prototype.cancel = function () { CursorMorph.prototype.undo = function () { this.target.text = this.originalContents; this.target.changed(); - this.target.drawNew(); + this.target.fixLayout(); this.target.changed(); this.gotoSlot(0); }; -CursorMorph.prototype.insert = function (aChar, shiftKey) { - var text; - if (aChar === '\u0009') { - this.target.escalateEvent('reactToEdit', this.target); - if (shiftKey) { - return this.target.backTab(this.target); - } - return this.target.tab(this.target); - } - if (!this.target.isNumeric || - !isNaN(parseFloat(aChar)) || - contains(['-', '.'], aChar)) { - if (this.target.selection() !== '') { - this.gotoSlot(this.target.selectionStartSlot()); - this.target.deleteSelection(); - } - text = this.target.text; - text = text.slice(0, this.slot) + - aChar + - text.slice(this.slot); - this.target.text = text; - this.target.drawNew(); - this.target.changed(); - this.goRight(false, aChar.length); - } -}; - -CursorMorph.prototype.ctrl = function (aChar, shiftKey) { - if (aChar === 64 || (aChar === 65 && shiftKey)) { - this.insert('@'); - } else if (aChar === 65) { - this.target.selectAll(); - } else if (aChar === 90) { - this.undo(); - } else if (aChar === 123) { - this.insert('{'); - } else if (aChar === 125) { - this.insert('}'); - } else if (aChar === 91) { - this.insert('['); - } else if (aChar === 93) { - this.insert(']'); - } else if (!isNil(this.target.receiver)) { - if (aChar === 68) { - this.target.doIt(); - } else if (aChar === 73) { - this.target.inspectIt(); - } else if (aChar === 80) { - this.target.showIt(); - } - } - - -}; - -CursorMorph.prototype.cmd = function (aChar, shiftKey) { - if (aChar === 64 || (aChar === 65 && shiftKey)) { - this.insert('@'); - } else if (aChar === 65) { - this.target.selectAll(); - } else if (aChar === 90) { - this.undo(); - } else if (!isNil(this.target.receiver)) { - if (aChar === 68) { - this.target.doIt(); - } else if (aChar === 73) { - this.target.inspectIt(); - } else if (aChar === 80) { - this.target.showIt(); - } - } -}; - -CursorMorph.prototype.deleteRight = function () { - var text; - if (this.target.selection() !== '') { - this.gotoSlot(this.target.selectionStartSlot()); - this.target.deleteSelection(); - } else { - text = this.target.text; - this.target.changed(); - text = text.slice(0, this.slot) + text.slice(this.slot + 1); - this.target.text = text; - this.target.drawNew(); - } -}; - -CursorMorph.prototype.deleteLeft = function () { - var text; - if (this.target.selection()) { - this.gotoSlot(this.target.selectionStartSlot()); - return this.target.deleteSelection(); - } - text = this.target.text; - this.target.changed(); - this.target.text = text.substring(0, this.slot - 1) + - text.substr(this.slot); - this.target.drawNew(); - this.goLeft(); -}; - // CursorMorph destroying: CursorMorph.prototype.destroy = function () { if (this.target.alignment !== this.originalAlignment) { this.target.alignment = this.originalAlignment; - this.target.drawNew(); this.target.changed(); } - this.destroyTextarea(); CursorMorph.uber.destroy.call(this); -}; - -CursorMorph.prototype.destroyTextarea = function () { - document.body.removeChild(this.textarea); - this.textarea = null; -}; - -// CursorMorph utilities: - -CursorMorph.prototype.inspectKeyEvent = function (event) { - // private - this.inform( - 'Key pressed: ' + - String.fromCharCode(event.charCode) + - '\n------------------------' + - '\ncharCode: ' + - event.charCode.toString() + - '\nkeyCode: ' + - event.keyCode.toString() + - '\nkey: ' + - event.key.toString() + - '\nshiftKey: ' + - event.shiftKey.toString() + - '\naltKey: ' + - event.altKey.toString() + - '\nctrlKey: ' + - event.ctrlKey.toString() + - '\ncmdKey: ' + - event.metaKey.toString() - ); + this.target.world().resetKeyboardHandler(); }; // BoxMorph //////////////////////////////////////////////////////////// @@ -6087,41 +5745,38 @@ BoxMorph.prototype.init = function (edge, border, borderColor) { // BoxMorph drawing: -BoxMorph.prototype.drawNew = function () { - var context; - - this.image = newCanvas(this.extent(), false, this.image); - context = this.image.getContext('2d'); +BoxMorph.prototype.render = function (ctx) { if ((this.edge === 0) && (this.border === 0)) { - BoxMorph.uber.drawNew.call(this); - return null; + BoxMorph.uber.render.call(this, ctx); + return; } - context.fillStyle = this.color.toString(); - context.beginPath(); + ctx.fillStyle = this.color.toString(); + ctx.beginPath(); this.outlinePath( - context, + ctx, Math.max(this.edge - this.border, 0), this.border ); - context.closePath(); - context.fill(); + ctx.closePath(); + ctx.fill(); if (this.border > 0) { - context.lineWidth = this.border; - context.strokeStyle = this.borderColor.toString(); - context.beginPath(); - this.outlinePath(context, this.edge, this.border / 2); - context.closePath(); - context.stroke(); + ctx.lineWidth = this.border; + ctx.strokeStyle = this.borderColor.toString(); + ctx.beginPath(); + this.outlinePath(ctx, this.edge, this.border / 2); + ctx.closePath(); + ctx.stroke(); } }; -BoxMorph.prototype.outlinePath = function (context, radius, inset) { - var offset = radius + inset, - w = this.width(), - h = this.height(); +BoxMorph.prototype.outlinePath = function (ctx, corner, inset) { + var w = this.width(), + h = this.height(), + radius = Math.min(corner, (Math.min(w, h) - inset) / 2), + offset = radius + inset; // top left: - context.arc( + ctx.arc( offset, offset, radius, @@ -6130,7 +5785,7 @@ BoxMorph.prototype.outlinePath = function (context, radius, inset) { false ); // top right: - context.arc( + ctx.arc( w - offset, offset, radius, @@ -6139,7 +5794,7 @@ BoxMorph.prototype.outlinePath = function (context, radius, inset) { false ); // bottom right: - context.arc( + ctx.arc( w - offset, h - offset, radius, @@ -6148,7 +5803,7 @@ BoxMorph.prototype.outlinePath = function (context, radius, inset) { false ); // bottom left: - context.arc( + ctx.arc( offset, h - offset, radius, @@ -6158,7 +5813,6 @@ BoxMorph.prototype.outlinePath = function (context, radius, inset) { ); }; - // BoxMorph menus: BoxMorph.prototype.developersMenu = function () { @@ -6166,7 +5820,7 @@ BoxMorph.prototype.developersMenu = function () { menu.addLine(); menu.addItem( "border width...", - function () { + () => { this.prompt( menu.title + '\nborder\nwidth:', this.setBorderWidth, @@ -6182,7 +5836,7 @@ BoxMorph.prototype.developersMenu = function () { ); menu.addItem( "border color...", - function () { + () => { this.pickColor( menu.title + '\nborder color:', this.setBorderColor, @@ -6194,7 +5848,7 @@ BoxMorph.prototype.developersMenu = function () { ); menu.addItem( "corner size...", - function () { + () => { this.prompt( menu.title + '\ncorner\nsize:', this.setCornerSize, @@ -6222,7 +5876,6 @@ BoxMorph.prototype.setBorderWidth = function (size) { this.border = Math.max(newSize, 0); } } - this.drawNew(); this.changed(); }; @@ -6230,7 +5883,6 @@ BoxMorph.prototype.setBorderColor = function (color) { // for context menu demo purposes if (color) { this.borderColor = color; - this.drawNew(); this.changed(); } }; @@ -6246,7 +5898,6 @@ BoxMorph.prototype.setCornerSize = function (size) { this.edge = Math.max(newSize, 0); } } - this.drawNew(); this.changed(); }; @@ -6315,15 +5966,14 @@ SpeechBubbleMorph.prototype.init = function ( borderColor || new Color(140, 140, 140) ); this.color = color || new Color(230, 230, 230); - this.drawNew(); + this.fixLayout(); }; // SpeechBubbleMorph invoking: SpeechBubbleMorph.prototype.popUp = function (world, pos, isClickable) { - this.drawNew(); + this.fixLayout(); this.setPosition(pos.subtract(new Point(0, this.height()))); - this.addShadow(new Point(2, 2), 80); this.keepWithin(world); world.add(this); this.fullChanged(); @@ -6331,9 +5981,7 @@ SpeechBubbleMorph.prototype.popUp = function (world, pos, isClickable) { world.hand.temporaries.push(this); if (!isClickable) { - this.mouseEnter = function () { - this.destroy(); - }; + this.mouseEnter = this.destroy; } else { this.isClickable = true; } @@ -6341,8 +5989,9 @@ SpeechBubbleMorph.prototype.popUp = function (world, pos, isClickable) { // SpeechBubbleMorph drawing: -SpeechBubbleMorph.prototype.drawNew = function () { - // re-build my contents +SpeechBubbleMorph.prototype.fixLayout = function () { + // determine my extent and arrange my contents + if (this.contentsMorph) { this.contentsMorph.destroy(); } @@ -6359,9 +6008,11 @@ SpeechBubbleMorph.prototype.drawNew = function () { ); } else if (this.contents instanceof HTMLCanvasElement) { this.contentsMorph = new Morph(); - this.contentsMorph.silentSetWidth(this.contents.width); - this.contentsMorph.silentSetHeight(this.contents.height); - this.contentsMorph.image = this.contents; + this.contentsMorph.setExtent(new Point( + this.contents.width, + this.contents.height + )); + this.contentsMorph.cachedImage = this.contents; } else { this.contentsMorph = new TextMorph( this.contents.toString(), @@ -6375,16 +6026,17 @@ SpeechBubbleMorph.prototype.drawNew = function () { this.add(this.contentsMorph); // adjust my layout - this.silentSetWidth(this.contentsMorph.width() + - (this.padding ? this.padding * 2 : this.edge * 2)); - this.silentSetHeight(this.contentsMorph.height() + - this.edge + - this.border * 2 + - this.padding * 2 + - 2); - - // draw my outline - SpeechBubbleMorph.uber.drawNew.call(this); + this.setExtent( + new Point( + this.contentsMorph.width() + + (this.padding ? this.padding * 2 : this.edge * 2), + this.contentsMorph.height() + + this.edge + + this.border * 2 + + this.padding * 2 + + 2 + ) + ); // position my contents this.contentsMorph.setPosition(this.position().add( @@ -6393,25 +6045,26 @@ SpeechBubbleMorph.prototype.drawNew = function () { this.border + this.padding + 1 ) )); + + // refresh a shallow shadow + this.removeShadow(); + this.addShadow(new Point(2, 2), 80); + }; -SpeechBubbleMorph.prototype.outlinePath = function ( - context, - radius, - inset -) { +SpeechBubbleMorph.prototype.outlinePath = function (ctx, radius, inset) { var offset = radius + inset, w = this.width(), h = this.height(), rad; function circle(x, y, r) { - context.moveTo(x + r, y); - context.arc(x, y, r, radians(0), radians(360)); + ctx.moveTo(x + r, y); + ctx.arc(x, y, r, radians(0), radians(360)); } // top left: - context.arc( + ctx.arc( offset, offset, radius, @@ -6420,7 +6073,7 @@ SpeechBubbleMorph.prototype.outlinePath = function ( false ); // top right: - context.arc( + ctx.arc( w - offset, offset, radius, @@ -6429,7 +6082,7 @@ SpeechBubbleMorph.prototype.outlinePath = function ( false ); // bottom right: - context.arc( + ctx.arc( w - offset, h - offset - radius, radius, @@ -6439,27 +6092,27 @@ SpeechBubbleMorph.prototype.outlinePath = function ( ); if (!this.isThought) { // draw speech bubble hook if (this.isPointingRight) { - context.lineTo( + ctx.lineTo( offset + radius, h - offset ); - context.lineTo( + ctx.lineTo( radius / 2 + inset, h - inset ); } else { // pointing left - context.lineTo( + ctx.lineTo( w - (radius / 2 + inset), h - inset ); - context.lineTo( + ctx.lineTo( w - (offset + radius), h - offset ); } } // bottom left: - context.arc( + ctx.arc( offset, h - offset - radius, radius, @@ -6469,7 +6122,7 @@ SpeechBubbleMorph.prototype.outlinePath = function ( ); if (this.isThought === true) { // use anything but "true" to draw nothing // close large bubble: - context.lineTo( + ctx.lineTo( inset, offset ); @@ -6477,23 +6130,47 @@ SpeechBubbleMorph.prototype.outlinePath = function ( if (this.isPointingRight) { // tip bubble: rad = radius / 4; - circle(rad + inset, h - rad - inset, rad); + circle( + rad + inset, + h - rad - inset, + rad + ); // middle bubble: rad = radius / 3.2; - circle(rad * 2 + inset, h - rad - inset * 2, rad); + circle( + (rad * 2) + inset, + h - rad - (inset * 2), + rad + ); // top bubble: rad = radius / 2.8; - circle(rad * 3 + inset * 2, h - rad - inset * 4, rad); + circle( + (rad * 3) + inset * 2, + h - rad - (inset * 4), + rad + ); } else { // pointing left // tip bubble: rad = radius / 4; - circle(w - (rad + inset), h - rad - inset, rad); + circle( + w - (rad + inset), + h - rad - inset, + rad + ); // middle bubble: rad = radius / 3.2; - circle(w - (rad * 2 + inset), h - rad - inset * 2, rad); + circle( + w - (rad * 2 + inset), + h - rad - inset * 2, + rad + ); // top bubble: rad = radius / 2.8; - circle(w - (rad * 3 + inset * 2), h - rad - inset * 4, rad); + circle( + w - (rad * 3 + inset * 2), + h - rad - inset * 4, + rad + ); } } }; @@ -6506,12 +6183,12 @@ SpeechBubbleMorph.prototype.outlinePath = function ( */ SpeechBubbleMorph.prototype.shadowImage = function (off, color) { - // fallback for Windows Chrome-Shadow bug + // for "flat" design mode var fb, img, outline, sha, ctx, offset = off || new Point(7, 7), clr = color || new Color(0, 0, 0); fb = this.extent(); - img = this.image; + img = this.getImage(); outline = newCanvas(fb); ctx = outline.getContext('2d'); ctx.drawImage(img, 0, 0); @@ -6536,7 +6213,7 @@ SpeechBubbleMorph.prototype.shadowImageBlurred = function (off, color) { blur = this.shadowBlur, clr = color || new Color(0, 0, 0); fb = this.extent().add(blur * 2); - img = this.image; + img = this.getImage(); sha = newCanvas(fb); ctx = sha.getContext('2d'); ctx.shadowOffsetX = offset.x; @@ -6560,14 +6237,6 @@ SpeechBubbleMorph.prototype.shadowImageBlurred = function (off, color) { return sha; }; -// SpeechBubbleMorph resizing - -SpeechBubbleMorph.prototype.fixLayout = function () { - this.removeShadow(); - this.drawNew(); - this.addShadow(new Point(2, 2), 80); -}; - // DialMorph ////////////////////////////////////////////////////// // I am a knob than can be turned to select a number @@ -6596,7 +6265,6 @@ DialMorph.prototype.init = function (min, max, value, tick, radius) { DialMorph.uber.init.call(this); this.color = new Color(230, 230, 230); - this.noticesTransparentClick = true; this.setRadius(radius || MorphicPreferences.menuFontSize * 4); }; @@ -6616,7 +6284,6 @@ DialMorph.prototype.setValue = function (value, snapToTick, noUpdate) { this.value -= this.value % this.tick % this.value; } } - this.drawNew(); this.changed(); if (noUpdate) {return; } this.updateTarget(); @@ -6643,17 +6310,14 @@ DialMorph.prototype.setExtent = function (aPoint) { DialMorph.uber.setExtent.call(this, new Point(size, size)); }; -DialMorph.prototype.drawNew = function () { - var ctx, i, angle, x1, y1, x2, y2, - light = this.color.lighter().toString(), - range = this.max - this.min, - ticks = range / this.tick, - face = this.radius * 0.75, - inner = face * 0.85, - outer = face * 0.95; - - this.image = newCanvas(this.extent(), false, this.image); - ctx = this.image.getContext('2d'); +DialMorph.prototype.render = function (ctx) { + var i, angle, x1, y1, x2, y2, + light = this.color.lighter().toString(), + range = this.max - this.min, + ticks = range / this.tick, + face = this.radius * 0.75, + inner = face * 0.85, + outer = face * 0.95; // draw a light border: ctx.fillStyle = light; @@ -6669,7 +6333,7 @@ DialMorph.prototype.drawNew = function () { ctx.closePath(); ctx.fill(); - // fill circle: + // fill circle: ctx.fillStyle = this.color.toString(); ctx.beginPath(); ctx.arc( @@ -6688,10 +6352,10 @@ DialMorph.prototype.drawNew = function () { ctx.fillStyle = (this.fillColor || this.color.darker()).toString(); ctx.beginPath(); ctx.arc( - this.radius, - this.radius, - face, - Math.PI / -2, + this.radius, + this.radius, + face, + Math.PI / -2, angle, false ); @@ -6699,22 +6363,22 @@ DialMorph.prototype.drawNew = function () { ctx.closePath(); ctx.fill(); - // draw ticks: - ctx.strokeStyle = new Color(35, 35, 35).toString(); - ctx.lineWidth = 1; - for (i = 0; i < ticks; i += 1) { - angle = (i - 3) * (Math.PI * 2) / ticks - Math.PI / 2; - ctx.beginPath(); - x1 = this.radius + Math.cos(angle) * inner; - y1 = this.radius + Math.sin(angle) * inner; - x2 = this.radius + Math.cos(angle) * outer; + // draw ticks: + ctx.strokeStyle = new Color(35, 35, 35).toString(); + ctx.lineWidth = 1; + for (i = 0; i < ticks; i += 1) { + angle = (i - 3) * (Math.PI * 2) / ticks - Math.PI / 2; + ctx.beginPath(); + x1 = this.radius + Math.cos(angle) * inner; + y1 = this.radius + Math.sin(angle) * inner; + x2 = this.radius + Math.cos(angle) * outer; y2 = this.radius + Math.sin(angle) * outer; ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); - } + } - // draw a filled center: + // draw a filled center: inner = face * 0.05; ctx.fillStyle = 'black'; ctx.beginPath(); @@ -6737,7 +6401,7 @@ DialMorph.prototype.drawNew = function () { x1 = this.radius + Math.cos(angle) * inner; y1 = this.radius + Math.sin(angle) * inner; x2 = this.radius + Math.cos(angle) * outer; - y2 = this.radius + Math.sin(angle) * outer; + y2 = this.radius + Math.sin(angle) * outer; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); @@ -6769,15 +6433,15 @@ DialMorph.prototype.drawNew = function () { ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); - ctx.lineWidth = 3; - ctx.strokeStyle = light; + ctx.lineWidth = 3; + ctx.strokeStyle = light; ctx.stroke(); ctx.lineWidth = 1; ctx.strokeStyle = 'black'; ctx.stroke(); // draw arrow tip: - angle = radians(degrees(angle) - 4); + angle = radians(degrees(angle) - 4); x1 = this.radius + Math.cos(angle) * this.radius * 0.9; y1 = this.radius + Math.sin(angle) * this.radius * 0.9; ctx.beginPath(); @@ -6802,12 +6466,12 @@ DialMorph.prototype.drawNew = function () { DialMorph.prototype.step = null; DialMorph.prototype.mouseDownLeft = function (pos) { - var world, myself = this; - world = this.root(); - this.step = function () { + var world = this.root(); + + this.step = () => { if (world.hand.mouseButton) { - myself.setValue( - myself.getValueOf(world.hand.bounds.origin), + this.setValue( + this.getValueOf(world.hand.bounds.origin), world.currentKey !== 16 // snap to tick ); } else { @@ -6832,14 +6496,13 @@ DialMorph.prototype.developersMenu = function () { DialMorph.prototype.setTarget = function () { var choices = this.overlappedMorphs(), - menu = new MenuMorph(this, 'choose target:'), - myself = this; + menu = new MenuMorph(this, 'choose target:'); choices.push(this.world()); - choices.forEach(function (each) { - menu.addItem(each.toString().slice(0, 50), function () { - myself.target = each; - myself.setTargetSetter(); + choices.forEach(each => { + menu.addItem(each.toString().slice(0, 50), () => { + this.target = each; + this.setTargetSetter(); }); }); if (choices.length === 1) { @@ -6852,13 +6515,10 @@ DialMorph.prototype.setTarget = function () { DialMorph.prototype.setTargetSetter = function () { var choices = this.target.numericalSetters(), - menu = new MenuMorph(this, 'choose target property:'), - myself = this; + menu = new MenuMorph(this, 'choose target property:'); - choices.forEach(function (each) { - menu.addItem(each, function () { - myself.action = each; - }); + choices.forEach(each => { + menu.addItem(each, () => this.action = each); }); if (choices.length === 1) { this.action = choices[0]; @@ -6908,60 +6568,78 @@ CircleBoxMorph.prototype.autoOrientation = function () { } }; -CircleBoxMorph.prototype.drawNew = function () { - var radius, center1, center2, rect, points, x, y, - context, ext, - myself = this; +CircleBoxMorph.prototype.render = function (ctx) { + var w = this.width(), + h = this.height(), + radius; if (this.autoOrient) { this.autoOrientation(); } - this.image = newCanvas(this.extent(), false, this.image); - context = this.image.getContext('2d'); if (this.orientation === 'vertical') { - radius = this.width() / 2; - x = this.center().x; - center1 = new Point(x, this.top() + radius); - center2 = new Point(x, this.bottom() - radius); - rect = this.bounds.origin.add(new Point(0, radius)).corner( - this.bounds.corner.subtract(new Point(0, radius)) - ); - } else { - radius = this.height() / 2; - y = this.center().y; - center1 = new Point(this.left() + radius, y); - center2 = new Point(this.right() - radius, y); - rect = this.bounds.origin.add(new Point(radius, 0)).corner( - this.bounds.corner.subtract(new Point(radius, 0)) - ); - } - points = [ center1.subtract(this.bounds.origin), - center2.subtract(this.bounds.origin)]; - points.forEach(function (center) { - context.fillStyle = myself.color.toString(); - context.beginPath(); - context.arc( - center.x, - center.y, + radius = w / 2; + ctx.beginPath(); + + // top semi-circle + ctx.arc( radius, - 0, - 2 * Math.PI, + radius, + radius, + radians(180), + radians(0), false ); - context.closePath(); - context.fill(); - }); - rect = rect.translateBy(this.bounds.origin.neg()); - ext = rect.extent(); - if (ext.x > 0 && ext.y > 0) { - context.fillRect( - rect.origin.x, - rect.origin.y, - rect.width(), - rect.height() + + // right line + ctx.lineTo( + w, + h - radius + ); + + // bottom semi-circle + ctx.arc( + radius, + h - radius, + radius, + radians(0), + radians(180), + false + ); + + } else { + radius = h / 2; + ctx.beginPath(); + + // left semi-circle + ctx.arc( + radius, + radius, + radius, + radians(90), + radians(-90), + false + ); + + // top line + ctx.lineTo( + w - radius, + 0 + ); + + // right semi-circle + ctx.arc( + w - radius, + radius, + radius, + radians(-90), + radians(90), + false ); } + ctx.closePath(); + ctx.fillStyle = this.color.toString(); + ctx.fill(); }; // CircleBoxMorph menu: @@ -6993,10 +6671,8 @@ CircleBoxMorph.prototype.toggleOrientation = function () { } else { this.orientation = 'vertical'; } - this.silentSetExtent(new Point(this.height(), this.width())); + this.setExtent(new Point(this.height(), this.width())); this.setCenter(center); - this.drawNew(); - this.changed(); }; // SliderButtonMorph /////////////////////////////////////////////////// @@ -7017,6 +6693,7 @@ SliderButtonMorph.prototype.init = function (orientation) { this.color = new Color(80, 80, 80); this.highlightColor = new Color(90, 90, 140); this.pressColor = new Color(80, 80, 160); + this.userState = 'normal'; // 'highlight', 'pressed' this.is3D = false; this.hasMiddleDip = true; SliderButtonMorph.uber.init.call(this, orientation); @@ -7024,65 +6701,48 @@ SliderButtonMorph.prototype.init = function (orientation) { SliderButtonMorph.prototype.autoOrientation = nop; -SliderButtonMorph.prototype.drawNew = function () { - var colorBak = this.color.copy(); - - SliderButtonMorph.uber.drawNew.call(this); - if (this.is3D || !MorphicPreferences.isFlat) { - this.drawEdges(); +SliderButtonMorph.prototype.render = function (ctx) { + var colorBak = this.color; + if (this.userState === 'highlight') { + this.color = this.highlightColor; + } else if (this.userState === 'pressed') { + this.color = this.pressColor; } - this.normalImage = this.image; - - this.image = null; // make sure not to reuse ther image canvas - this.color = this.highlightColor.copy(); - SliderButtonMorph.uber.drawNew.call(this); + SliderButtonMorph.uber.render.call(this, ctx); if (this.is3D || !MorphicPreferences.isFlat) { - this.drawEdges(); + this.renderEdges(ctx); } - this.highlightImage = this.image; - - this.image = null; // make sure not to reuse ther image canvas - this.color = this.pressColor.copy(); - SliderButtonMorph.uber.drawNew.call(this); - if (this.is3D || !MorphicPreferences.isFlat) { - this.drawEdges(); - } - this.pressImage = this.image; - this.color = colorBak; - this.image = this.normalImage; - }; -SliderButtonMorph.prototype.drawEdges = function () { - var context = this.image.getContext('2d'), - gradient, +SliderButtonMorph.prototype.renderEdges = function (ctx) { + var gradient, radius, w = this.width(), h = this.height(); - context.lineJoin = 'round'; - context.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.lineCap = 'round'; if (this.orientation === 'vertical') { - context.lineWidth = w / 3; - gradient = context.createLinearGradient( + ctx.lineWidth = w / 3; + gradient = ctx.createLinearGradient( 0, 0, - context.lineWidth, + ctx.lineWidth, 0 ); gradient.addColorStop(0, 'white'); gradient.addColorStop(1, this.color.toString()); - context.strokeStyle = gradient; - context.beginPath(); - context.moveTo(context.lineWidth * 0.5, w / 2); - context.lineTo(context.lineWidth * 0.5, h - w / 2); - context.stroke(); + ctx.strokeStyle = gradient; + ctx.beginPath(); + ctx.moveTo(ctx.lineWidth * 0.5, w / 2); + ctx.lineTo(ctx.lineWidth * 0.5, h - w / 2); + ctx.stroke(); - gradient = context.createLinearGradient( - w - context.lineWidth, + gradient = ctx.createLinearGradient( + w - ctx.lineWidth, 0, w, 0 @@ -7090,17 +6750,17 @@ SliderButtonMorph.prototype.drawEdges = function () { gradient.addColorStop(0, this.color.toString()); gradient.addColorStop(1, 'black'); - context.strokeStyle = gradient; - context.beginPath(); - context.moveTo(w - context.lineWidth * 0.5, w / 2); - context.lineTo(w - context.lineWidth * 0.5, h - w / 2); - context.stroke(); + ctx.strokeStyle = gradient; + ctx.beginPath(); + ctx.moveTo(w - ctx.lineWidth * 0.5, w / 2); + ctx.lineTo(w - ctx.lineWidth * 0.5, h - w / 2); + ctx.stroke(); if (this.hasMiddleDip) { - gradient = context.createLinearGradient( - context.lineWidth, + gradient = ctx.createLinearGradient( + ctx.lineWidth, 0, - w - context.lineWidth, + w - ctx.lineWidth, 0 ); @@ -7110,9 +6770,9 @@ SliderButtonMorph.prototype.drawEdges = function () { gradient.addColorStop(0.65, this.color.toString()); gradient.addColorStop(1, 'white'); - context.fillStyle = gradient; - context.beginPath(); - context.arc( + ctx.fillStyle = gradient; + ctx.beginPath(); + ctx.arc( w / 2, h / 2, radius, @@ -7120,47 +6780,47 @@ SliderButtonMorph.prototype.drawEdges = function () { radians(360), false ); - context.closePath(); - context.fill(); + ctx.closePath(); + ctx.fill(); } } else if (this.orientation === 'horizontal') { - context.lineWidth = h / 3; - gradient = context.createLinearGradient( + ctx.lineWidth = h / 3; + gradient = ctx.createLinearGradient( 0, 0, 0, - context.lineWidth + ctx.lineWidth ); gradient.addColorStop(0, 'white'); gradient.addColorStop(1, this.color.toString()); - context.strokeStyle = gradient; - context.beginPath(); - context.moveTo(h / 2, context.lineWidth * 0.5); - context.lineTo(w - h / 2, context.lineWidth * 0.5); - context.stroke(); + ctx.strokeStyle = gradient; + ctx.beginPath(); + ctx.moveTo(h / 2, ctx.lineWidth * 0.5); + ctx.lineTo(w - h / 2, ctx.lineWidth * 0.5); + ctx.stroke(); - gradient = context.createLinearGradient( + gradient = ctx.createLinearGradient( 0, - h - context.lineWidth, + h - ctx.lineWidth, 0, h ); gradient.addColorStop(0, this.color.toString()); gradient.addColorStop(1, 'black'); - context.strokeStyle = gradient; - context.beginPath(); - context.moveTo(h / 2, h - context.lineWidth * 0.5); - context.lineTo(w - h / 2, h - context.lineWidth * 0.5); - context.stroke(); + ctx.strokeStyle = gradient; + ctx.beginPath(); + ctx.moveTo(h / 2, h - ctx.lineWidth * 0.5); + ctx.lineTo(w - h / 2, h - ctx.lineWidth * 0.5); + ctx.stroke(); if (this.hasMiddleDip) { - gradient = context.createLinearGradient( + gradient = ctx.createLinearGradient( 0, - context.lineWidth, + ctx.lineWidth, 0, - h - context.lineWidth + h - ctx.lineWidth ); radius = h / 4; @@ -7169,9 +6829,9 @@ SliderButtonMorph.prototype.drawEdges = function () { gradient.addColorStop(0.65, this.color.toString()); gradient.addColorStop(1, 'white'); - context.fillStyle = gradient; - context.beginPath(); - context.arc( + ctx.fillStyle = gradient; + ctx.beginPath(); + ctx.arc( this.width() / 2, this.height() / 2, radius, @@ -7179,8 +6839,8 @@ SliderButtonMorph.prototype.drawEdges = function () { radians(360), false ); - context.closePath(); - context.fill(); + ctx.closePath(); + ctx.fill(); } } }; @@ -7188,24 +6848,24 @@ SliderButtonMorph.prototype.drawEdges = function () { //SliderButtonMorph events: SliderButtonMorph.prototype.mouseEnter = function () { - this.image = this.highlightImage; - this.changed(); + this.userState = 'highlight'; + this.rerender(); }; SliderButtonMorph.prototype.mouseLeave = function () { - this.image = this.normalImage; - this.changed(); + this.userState = 'normal'; + this.rerender(); }; SliderButtonMorph.prototype.mouseDownLeft = function (pos) { - this.image = this.pressImage; - this.changed(); + this.userState = 'pressed'; + this.rerender(); this.escalateEvent('mouseDownLeft', pos); }; SliderButtonMorph.prototype.mouseClickLeft = function () { - this.image = this.highlightImage; - this.changed(); + this.userState = 'highlight'; + this.rerender(); }; SliderButtonMorph.prototype.mouseMove = function () { @@ -7257,7 +6917,7 @@ SliderMorph.prototype.init = function ( this.alpha = 0.3; this.color = color || new Color(0, 0, 0); this.setExtent(new Point(20, 100)); - // this.drawNew(); + this.fixLayout(); }; SliderMorph.prototype.autoOrientation = nop; @@ -7279,15 +6939,14 @@ SliderMorph.prototype.unitSize = function () { this.rangeSize(); }; -SliderMorph.prototype.drawNew = function () { +SliderMorph.prototype.fixLayout = function () { var bw, bh, posX, posY; - SliderMorph.uber.drawNew.call(this); this.button.orientation = this.orientation; if (this.orientation === 'vertical') { bw = this.width() - 2; bh = Math.max(bw, Math.round(this.height() * this.ratio())); - this.button.silentSetExtent(new Point(bw, bh)); + this.button.setExtent(new Point(bw, bh)); posX = 1; posY = Math.min( Math.round((this.value - this.start) * this.unitSize()), @@ -7296,7 +6955,7 @@ SliderMorph.prototype.drawNew = function () { } else { bh = this.height() - 2; bw = Math.max(bh, Math.round(this.width() * this.ratio())); - this.button.silentSetExtent(new Point(bw, bh)); + this.button.setExtent(new Point(bw, bh)); posY = 1; posX = Math.min( Math.round((this.value - this.start) * this.unitSize()), @@ -7306,8 +6965,6 @@ SliderMorph.prototype.drawNew = function () { this.button.setPosition( new Point(posX, posY).add(this.bounds.origin) ); - this.button.drawNew(); - this.button.changed(); }; SliderMorph.prototype.updateValue = function () { @@ -7342,7 +6999,7 @@ SliderMorph.prototype.developersMenu = function () { ); menu.addItem( "floor...", - function () { + () => { this.prompt( menu.title + '\nfloor:', this.setStart, @@ -7358,7 +7015,7 @@ SliderMorph.prototype.developersMenu = function () { ); menu.addItem( "ceiling...", - function () { + () => { this.prompt( menu.title + '\nceiling:', this.setStop, @@ -7374,7 +7031,7 @@ SliderMorph.prototype.developersMenu = function () { ); menu.addItem( "button size...", - function () { + () => { this.prompt( menu.title + '\nbutton size:', this.setSize, @@ -7426,8 +7083,8 @@ SliderMorph.prototype.setStart = function (num, noUpdate) { } this.value = Math.max(this.value, this.start); if (!noUpdate) {this.updateTarget(); } - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; SliderMorph.prototype.setStop = function (num, noUpdate) { @@ -7443,8 +7100,8 @@ SliderMorph.prototype.setStop = function (num, noUpdate) { } this.value = Math.min(this.value, this.stop); if (!noUpdate) {this.updateTarget(); } - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; SliderMorph.prototype.setSize = function (num, noUpdate) { @@ -7466,20 +7123,19 @@ SliderMorph.prototype.setSize = function (num, noUpdate) { } this.value = Math.min(this.value, this.stop - this.size); if (!noUpdate) {this.updateTarget(); } - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; SliderMorph.prototype.setTarget = function () { var choices = this.overlappedMorphs(), - menu = new MenuMorph(this, 'choose target:'), - myself = this; + menu = new MenuMorph(this, 'choose target:'); choices.push(this.world()); - choices.forEach(function (each) { - menu.addItem(each.toString().slice(0, 50), function () { - myself.target = each; - myself.setTargetSetter(); + choices.forEach(each => { + menu.addItem(each.toString().slice(0, 50), () => { + this.target = each; + this.setTargetSetter(); }); }); if (choices.length === 1) { @@ -7492,13 +7148,10 @@ SliderMorph.prototype.setTarget = function () { SliderMorph.prototype.setTargetSetter = function () { var choices = this.target.numericalSetters(), - menu = new MenuMorph(this, 'choose target property:'), - myself = this; + menu = new MenuMorph(this, 'choose target property:'); - choices.forEach(function (each) { - menu.addItem(each, function () { - myself.action = each; - }); + choices.forEach(each => { + menu.addItem(each, () => this.action = each); }); if (choices.length === 1) { this.action = choices[0]; @@ -7519,7 +7172,7 @@ SliderMorph.prototype.numericalSetters = function () { SliderMorph.prototype.step = null; SliderMorph.prototype.mouseDownLeft = function (pos) { - var world, myself = this; + var world; if (!this.button.bounds.containsPoint(pos)) { this.offset = new Point(); // return null; @@ -7527,31 +7180,32 @@ SliderMorph.prototype.mouseDownLeft = function (pos) { this.offset = pos.subtract(this.button.bounds.origin); } world = this.root(); - this.step = function () { + + this.step = () => { var mousePos, newX, newY; if (world.hand.mouseButton) { mousePos = world.hand.bounds.origin; - if (myself.orientation === 'vertical') { - newX = myself.button.bounds.origin.x; + if (this.orientation === 'vertical') { + newX = this.button.bounds.origin.x; newY = Math.max( Math.min( - mousePos.y - myself.offset.y, - myself.bottom() - myself.button.height() + mousePos.y - this.offset.y, + this.bottom() - this.button.height() ), - myself.top() + this.top() ); } else { - newY = myself.button.bounds.origin.y; + newY = this.button.bounds.origin.y; newX = Math.max( Math.min( - mousePos.x - myself.offset.x, - myself.right() - myself.button.width() + mousePos.x - this.offset.x, + this.right() - this.button.width() ), - myself.left() + this.left() ); } - myself.button.setPosition(new Point(newX, newY)); - myself.updateValue(); + this.button.setPosition(new Point(newX, newY)); + this.updateValue(); } else { this.step = null; } @@ -7585,28 +7239,25 @@ MouseSensorMorph.prototype.init = function (edge, border, borderColor) { this.isTouched = false; this.upStep = 0.05; this.downStep = 0.02; - this.noticesTransparentClick = false; - this.drawNew(); }; MouseSensorMorph.prototype.touch = function () { - var myself = this; if (!this.isTouched) { this.isTouched = true; this.alpha = 0.6; - this.step = function () { - if (myself.isTouched) { - if (myself.alpha < 1) { - myself.alpha = myself.alpha + myself.upStep; + this.step = () => { + if (this.isTouched) { + if (this.alpha < 1) { + this.alpha += this.upStep; } - } else if (myself.alpha > (myself.downStep)) { - myself.alpha = myself.alpha - myself.downStep; + } else if (this.alpha > this.downStep) { + this.alpha -= this.downStep; } else { - myself.alpha = 0; - myself.step = null; + this.alpha = 0; + this.step = null; } - myself.changed(); + this.changed(); }; } }; @@ -7662,19 +7313,12 @@ InspectorMorph.prototype.init = function (target) { InspectorMorph.uber.init.call(this); // override inherited properties: - this.silentSetExtent( - new Point( - MorphicPreferences.handleSize * 20, - MorphicPreferences.handleSize * 20 * 2 / 3 - ) - ); this.isDraggable = true; this.border = 1; this.edge = MorphicPreferences.isFlat ? 1 : 5; this.color = new Color(60, 60, 60); this.borderColor = new Color(95, 95, 95); this.fps = 25; - this.drawNew(); // panes: this.label = null; @@ -7690,6 +7334,13 @@ InspectorMorph.prototype.init = function (target) { if (this.target) { this.buildPanes(); } + + this.setExtent( + new Point( + MorphicPreferences.handleSize * 20, + MorphicPreferences.handleSize * 20 * 2 / 3 + ) + ); }; InspectorMorph.prototype.setTarget = function (target) { @@ -7705,8 +7356,8 @@ InspectorMorph.prototype.updateCurrentSelection = function () { root = this.root(); if (root && - (root.keyboardReceiver instanceof CursorMorph) && - (root.keyboardReceiver.target === currentTxt)) { + (root.keyboardFocus instanceof CursorMorph) && + (root.keyboardFocus.target === currentTxt)) { this.hasUserEditedDetails = true; return; } @@ -7729,10 +7380,10 @@ InspectorMorph.prototype.updateCurrentSelection = function () { }; InspectorMorph.prototype.buildPanes = function () { - var attribs = [], property, myself = this, ctrl, ev, doubleClickAction; + var attribs = [], property, ctrl, ev, doubleClickAction; // remove existing panes - this.children.forEach(function (m) { + this.children.forEach(m => { if (m !== this.work) { // keep work pane around m.destroy(); } @@ -7744,7 +7395,6 @@ InspectorMorph.prototype.buildPanes = function () { this.label.fontSize = MorphicPreferences.menuFontSize; this.label.isBold = true; this.label.color = new Color(255, 255, 255); - this.label.drawNew(); this.add(this.label); // properties list @@ -7754,21 +7404,21 @@ InspectorMorph.prototype.buildPanes = function () { } } if (this.showing === 'attributes') { - attribs = attribs.filter(function (prop) { - return typeof myself.target[prop] !== 'function'; - }); + attribs = attribs.filter( + prop => typeof this.target[prop] !== 'function' + ); } else if (this.showing === 'methods') { - attribs = attribs.filter(function (prop) { - return typeof myself.target[prop] === 'function'; - }); + attribs = attribs.filter( + prop => typeof this.target[prop] === 'function' + ); } // otherwise show all properties - doubleClickAction = function () { + doubleClickAction = () => { var world, inspector; - if (!isObject(myself.currentProperty)) {return; } - world = myself.world(); + if (!isObject(this.currentProperty)) {return; } + world = this.world(); inspector = new InspectorMorph( - myself.currentProperty + this.currentProperty ); inspector.setPosition(world.hand.position()); inspector.keepWithin(world); @@ -7783,9 +7433,9 @@ InspectorMorph.prototype.buildPanes = function () { [ // format list [ // format element: [color, predicate(element] new Color(0, 0, 180), - function (element) { + element => { return Object.prototype.hasOwnProperty.call( - myself.target, + this.target, element ); } @@ -7795,9 +7445,9 @@ InspectorMorph.prototype.buildPanes = function () { doubleClickAction ); - this.list.action = function () { - myself.hasUserEditedDetails = false; - myself.updateCurrentSelection(); + this.list.action = () => { + this.hasUserEditedDetails = false; + this.updateCurrentSelection(); }; this.list.hBar.alpha = 0.6; @@ -7841,57 +7491,60 @@ InspectorMorph.prototype.buildPanes = function () { // properties button this.buttonSubset = new TriggerMorph(); this.buttonSubset.labelString = 'show...'; - this.buttonSubset.action = function () { + this.buttonSubset.createLabel(); + this.buttonSubset.action = () => { var menu; menu = new MenuMorph(); menu.addItem( 'attributes', - function () { - myself.showing = 'attributes'; - myself.buildPanes(); + () => { + this.showing = 'attributes'; + this.buildPanes(); } ); menu.addItem( 'methods', - function () { - myself.showing = 'methods'; - myself.buildPanes(); + () => { + this.showing = 'methods'; + this.buildPanes(); } ); menu.addItem( 'all', - function () { - myself.showing = 'all'; - myself.buildPanes(); + () => { + this.showing = 'all'; + this.buildPanes(); } ); menu.addLine(); menu.addItem( - (myself.markOwnProperties ? + (this.markOwnProperties ? 'un-mark own' : 'mark own'), - function () { - myself.markOwnProperties = !myself.markOwnProperties; - myself.buildPanes(); + () => { + this.markOwnProperties = !this.markOwnProperties; + this.buildPanes(); }, 'highlight\n\'own\' properties' ); - menu.popUpAtHand(myself.world()); + menu.popUpAtHand(this.world()); }; + this.add(this.buttonSubset); // inspect button this.buttonInspect = new TriggerMorph(); this.buttonInspect.labelString = 'inspect...'; - this.buttonInspect.action = function () { + this.buttonInspect.createLabel(); + this.buttonInspect.action = () => { var menu, world, inspector; - if (isObject(myself.currentProperty)) { + if (isObject(this.currentProperty)) { menu = new MenuMorph(); menu.addItem( 'in new inspector...', - function () { - world = myself.world(); + () => { + world = this.world(); inspector = new InspectorMorph( - myself.currentProperty + this.currentProperty ); inspector.setPosition(world.hand.position()); inspector.keepWithin(world); @@ -7901,15 +7554,13 @@ InspectorMorph.prototype.buildPanes = function () { ); menu.addItem( 'here...', - function () { - myself.setTarget(myself.currentProperty); - } + () => this.setTarget(this.currentProperty) ); - menu.popUpAtHand(myself.world()); + menu.popUpAtHand(this.world()); } else { - myself.inform( - (myself.currentProperty === null ? - 'null' : typeof myself.currentProperty) + + this.inform( + (this.currentProperty === null ? + 'null' : typeof this.currentProperty) + '\nis not inspectable' ); } @@ -7917,27 +7568,25 @@ InspectorMorph.prototype.buildPanes = function () { this.add(this.buttonInspect); // edit button - this.buttonEdit = new TriggerMorph(); this.buttonEdit.labelString = 'edit...'; - this.buttonEdit.action = function () { - var menu; - menu = new MenuMorph(myself); + this.buttonEdit.createLabel(); + this.buttonEdit.action = () => { + var menu = new MenuMorph(this); menu.addItem("save", 'save', 'accept changes'); menu.addLine(); menu.addItem("add property...", 'addProperty'); menu.addItem("rename...", 'renameProperty'); menu.addItem("remove...", 'removeProperty'); - menu.popUpAtHand(myself.world()); + menu.popUpAtHand(this.world()); }; this.add(this.buttonEdit); // close button this.buttonClose = new TriggerMorph(); this.buttonClose.labelString = 'close'; - this.buttonClose.action = function () { - myself.destroy(); - }; + this.buttonClose.createLabel(); + this.buttonClose.action = () => this.destroy(); this.add(this.buttonClose); // resizer @@ -7956,8 +7605,6 @@ InspectorMorph.prototype.buildPanes = function () { InspectorMorph.prototype.fixLayout = function () { var x, y, r, b, w, h; - Morph.prototype.trackChanges = false; - // label x = this.left() + this.edge; y = this.top() + this.edge; @@ -7966,10 +7613,7 @@ InspectorMorph.prototype.fixLayout = function () { this.label.setPosition(new Point(x, y)); this.label.setWidth(w); if (this.label.height() > (this.height() - 50)) { - this.silentSetHeight(this.label.height() + 50); - this.drawNew(); - this.changed(); - this.resizer.drawNew(); + this.bounds.setHeight(this.label.height() + 50); } // list @@ -8027,9 +7671,8 @@ InspectorMorph.prototype.fixLayout = function () { this.buttonClose.setPosition(new Point(x, y)); this.buttonClose.setExtent(new Point(w, h)); - Morph.prototype.trackChanges = true; - this.changed(); - + // resizer + this.resizer.fixLayout(); }; InspectorMorph.prototype.setExtent = function (aPoint) { @@ -8043,57 +7686,42 @@ InspectorMorph.prototype.save = function () { var txt = this.detail.contents.children[0].text.toString(), prop = this.list.selected; try { - // this.target[prop] = evaluate(txt); this.target.evaluateString('this.' + prop + ' = ' + txt); this.hasUserEditedDetails = false; - if (this.target.drawNew) { - this.target.changed(); - this.target.drawNew(); - this.target.changed(); - } + this.target.changed(); } catch (err) { this.inform(err); } }; InspectorMorph.prototype.addProperty = function () { - var myself = this; this.prompt( 'new property name:', - function (prop) { + prop => { if (prop) { - myself.target[prop] = null; - myself.buildPanes(); - if (myself.target.drawNew) { - myself.target.changed(); - myself.target.drawNew(); - myself.target.changed(); - } + this.target[prop] = null; + this.buildPanes(); + this.target.changed(); } }, this, - 'property' // Chrome cannot handle empty strings (others do) + 'property' ); }; InspectorMorph.prototype.renameProperty = function () { - var myself = this, - propertyName = this.list.selected; + var propertyName = this.list.selected; this.prompt( 'property name:', - function (prop) { + prop => { try { - delete (myself.target[propertyName]); - myself.target[prop] = myself.currentProperty; + delete (this.target[propertyName]); + this.target[prop] = this.currentProperty; } catch (err) { - myself.inform(err); - } - myself.buildPanes(); - if (myself.target.drawNew) { - myself.target.changed(); - myself.target.drawNew(); - myself.target.changed(); + this.inform(err); } + this.buildPanes(); + this.target.changed(); }, this, propertyName @@ -8106,11 +7734,7 @@ InspectorMorph.prototype.removeProperty = function () { delete (this.target[prop]); this.currentProperty = null; this.buildPanes(); - if (this.target.drawNew) { - this.target.changed(); - this.target.drawNew(); - this.target.changed(); - } + this.target.changed(); } catch (err) { this.inform(err); } @@ -8123,7 +7747,6 @@ InspectorMorph.prototype.step = function () { var lbl = this.target.toString(); if (this.label.text === lbl) {return; } this.label.text = lbl; - this.label.drawNew(); this.fixLayout(); }; @@ -8280,7 +7903,7 @@ MenuMorph.prototype.createLabel = function () { text.alignment = 'center'; text.color = new Color(255, 255, 255); text.backgroundColor = this.borderColor; - text.drawNew(); + text.fixLayout(); this.label = new BoxMorph(3, 0); if (MorphicPreferences.isFlat) { this.label.edge = 0; @@ -8288,22 +7911,18 @@ MenuMorph.prototype.createLabel = function () { this.label.color = this.borderColor; this.label.borderColor = this.borderColor; this.label.setExtent(text.extent().add(4)); - this.label.drawNew(); this.label.add(text); this.label.text = text; }; -MenuMorph.prototype.drawNew = function () { - var myself = this, - item, +MenuMorph.prototype.createItems = function () { + var item, fb, x, y, isLine = false; - this.children.forEach(function (m) { - m.destroy(); - }); + this.children.forEach(m => m.destroy()); this.children = []; if (!this.isListContents) { this.edge = MorphicPreferences.isFlat ? 0 : 5; @@ -8311,7 +7930,7 @@ MenuMorph.prototype.drawNew = function () { } this.color = new Color(255, 255, 255); this.borderColor = new Color(60, 60, 60); - this.silentSetExtent(new Point(0, 0)); + this.setExtent(new Point(0, 0)); y = 2; x = this.left() + 4; @@ -8326,7 +7945,7 @@ MenuMorph.prototype.drawNew = function () { } } y += 1; - this.items.forEach(function (tuple) { + this.items.forEach(tuple => { isLine = false; if (tuple instanceof StringFieldMorph || tuple instanceof ColorPickerMorph || @@ -8336,16 +7955,16 @@ MenuMorph.prototype.drawNew = function () { } else if (tuple[0] === 0) { isLine = true; item = new Morph(); - item.color = myself.borderColor; + item.color = this.borderColor; item.setHeight(tuple[1]); } else { item = new MenuItemMorph( - myself.target, + this.target, tuple[1], tuple[0], - myself.fontSize || MorphicPreferences.menuFontSize, + this.fontSize || MorphicPreferences.menuFontSize, MorphicPreferences.menuFontName, - myself.environment, + this.environment, tuple[2], // bubble help hint tuple[3], // color tuple[4], // bold @@ -8358,7 +7977,7 @@ MenuMorph.prototype.drawNew = function () { y += 1; } item.setPosition(new Point(x, y)); - myself.add(item); + this.add(item); y = y + item.height(); if (isLine) { y += 1; @@ -8366,9 +7985,8 @@ MenuMorph.prototype.drawNew = function () { }); fb = this.fullBounds(); - this.silentSetExtent(fb.extent().add(4)); + this.setExtent(fb.extent().add(4)); this.adjustWidths(); - MenuMorph.uber.drawNew.call(this); }; MenuMorph.prototype.maxWidth = function () { @@ -8379,7 +7997,7 @@ MenuMorph.prototype.maxWidth = function () { w = this.parent.scrollFrame.width(); } } - this.children.forEach(function (item) { + this.children.forEach(item => { if (item instanceof MenuItemMorph) { w = Math.max( w, @@ -8400,46 +8018,39 @@ MenuMorph.prototype.maxWidth = function () { }; MenuMorph.prototype.adjustWidths = function () { - var w = this.maxWidth(), - isSelected, - myself = this; - this.children.forEach(function (item) { + var w = this.maxWidth(); + this.children.forEach(item => { if (!(item instanceof DialMorph)) { - item.silentSetWidth(w); + item.setWidth(w); } - if (item instanceof MenuItemMorph) { - item.fixLayout(); - isSelected = (item.image === item.pressImage); - item.createBackgrounds(); - if (isSelected) { - item.image = item.pressImage; - } - } else { - item.drawNew(); - if (item === myself.label) { - item.text.setPosition( - item.center().subtract( - item.text.extent().floorDivideBy(2) - ) - ); - } + item.fixLayout(); + if (item === this.label) { + item.text.setPosition( + item.center().subtract( + item.text.extent().floorDivideBy(2) + ) + ); } }); }; MenuMorph.prototype.unselectAllItems = function () { - this.children.forEach(function (item) { + this.children.forEach(item => { if (item instanceof MenuItemMorph) { - item.image = item.normalImage; + if (item.userState !== 'normal') { + item.userState = 'normal'; + item.rerender(); + } } else if (item instanceof ScrollFrameMorph) { - item.contents.children.forEach(function (morph) { - if (morph instanceof MenuItemMorph) { - morph.image = morph.normalImage; + item.contents.children.forEach(morph => { + if (morph instanceof MenuItemMorph && + morph.userState !== 'normal') { + morph.userState = 'normal'; + morph.rerender(); } }); } }); - this.changed(); }; // MenuMorph popping up @@ -8447,7 +8058,7 @@ MenuMorph.prototype.unselectAllItems = function () { MenuMorph.prototype.popup = function (world, pos) { var scroller; - this.drawNew(); + this.createItems(); this.setPosition(pos); this.addShadow(new Point(2, 2), 80); this.keepWithin(world); @@ -8457,7 +8068,6 @@ MenuMorph.prototype.popup = function (world, pos) { this.removeShadow(); scroller = this.scroll(); this.bounds.corner.y = world.bottom() - 2; - MenuMorph.uber.drawNew.call(this); this.addShadow(new Point(2, 2), 80); scroller.setHeight(world.bottom() - scroller.top() - 6); scroller.adjustScrollBars(); // ? @@ -8482,9 +8092,7 @@ MenuMorph.prototype.scroll = function () { first = this.children[start]; scroller.setPosition(first.position()); - this.children.slice(start).forEach(function (morph) { - scroller.addContents(morph); - }); + this.children.slice(start).forEach(morph => scroller.addContents(morph)); this.add(scroller); scroller.setWidth(first.width()); return scroller; @@ -8497,7 +8105,7 @@ MenuMorph.prototype.popUpAtHand = function (world) { MenuMorph.prototype.popUpCenteredAtHand = function (world) { var wrrld = world || this.world; - this.drawNew(); + this.fixLayout(); this.popup( wrrld, wrrld.hand.position().subtract( @@ -8508,7 +8116,7 @@ MenuMorph.prototype.popUpCenteredAtHand = function (world) { MenuMorph.prototype.popUpCenteredInWorld = function (world) { var wrrld = world || this.world; - this.drawNew(); + this.fixLayout(); this.popup( wrrld, wrrld.center().subtract( @@ -8538,7 +8146,7 @@ MenuMorph.prototype.closeSubmenu = function () { // MenuMorph keyboard accessibility MenuMorph.prototype.getFocus = function () { - this.world.keyboardReceiver = this; + this.world.keyboardFocus = this; this.selection = null; this.selectFirst(); this.hasFocus = true; @@ -8582,9 +8190,10 @@ MenuMorph.prototype.processKeyPress = function (event) { MenuMorph.prototype.selectFirst = function () { var scroller, items, i; - scroller = detect(this.children, function (morph) { - return morph instanceof ScrollFrameMorph; - }); + scroller = detect( + this.children, + morph => morph instanceof ScrollFrameMorph + ); items = scroller ? scroller.contents.children : this.children; for (i = 0; i < items.length; i += 1) { if (items[i] instanceof MenuItemMorph) { @@ -8597,13 +8206,12 @@ MenuMorph.prototype.selectFirst = function () { MenuMorph.prototype.selectUp = function () { var scroller, triggers, idx; - scroller = detect(this.children, function (morph) { - return morph instanceof ScrollFrameMorph; - }); + scroller = detect( + this.children, + morph => morph instanceof ScrollFrameMorph + ); triggers = (scroller ? scroller.contents.children : this.children).filter( - function (each) { - return each instanceof MenuItemMorph; - } + each => each instanceof MenuItemMorph ); if (!this.selection) { if (triggers.length) { @@ -8621,13 +8229,12 @@ MenuMorph.prototype.selectUp = function () { MenuMorph.prototype.selectDown = function () { var scroller, triggers, idx; - scroller = detect(this.children, function (morph) { - return morph instanceof ScrollFrameMorph; - }); + scroller = detect( + this.children, + morph => morph instanceof ScrollFrameMorph + ); triggers = (scroller ? scroller.contents.children : this.children).filter( - function (each) { - return each instanceof MenuItemMorph; - } + each => each instanceof MenuItemMorph ); if (!this.selection) { if (triggers.length) { @@ -8657,21 +8264,21 @@ MenuMorph.prototype.leaveSubmenu = function () { menu.submenu = null; menu.hasFocus = true; this.destroy(); - menu.world.keyboardReceiver = menu; + menu.world.keyboardFocus = menu; } }; MenuMorph.prototype.select = function (aMenuItem) { this.unselectAllItems(); - aMenuItem.image = aMenuItem.highlightImage; - aMenuItem.changed(); + aMenuItem.userState = 'highlight'; + aMenuItem.rerender(); aMenuItem.scrollIntoView(); this.selection = aMenuItem; }; MenuMorph.prototype.destroy = function () { if (this.hasFocus) { - this.world.keyboardReceiver = null; + this.world.keyboardFocus = null; } MenuMorph.uber.destroy.call(this); }; @@ -8760,8 +8367,7 @@ StringMorph.prototype.init = function ( // override inherited properites: this.color = color || new Color(0, 0, 0); - this.noticesTransparentClick = true; - this.drawNew(); + this.fixLayout(); // determine my extent }; StringMorph.prototype.toString = function () { @@ -8796,13 +8402,13 @@ StringMorph.prototype.font = function () { this.fontStyle; }; -StringMorph.prototype.drawNew = function () { - var context, width, start, stop, i, p, c, x, y, +StringMorph.prototype.fixLayout = function () { + // determine my extent depending on my current settings + var width, shadowOffset = this.shadowOffset || new Point(), txt = this.isPassword ? - this.password('*', this.text.length) : this.text; + this.password('*', this.text.length) : this.text; - // determine my extent this.measureCtx.font = this.font(); width = Math.max( this.measureCtx.measureText(txt).width + Math.abs(shadowOffset.x), @@ -8815,47 +8421,6 @@ StringMorph.prototype.drawNew = function () { ) ); - // initialize my surface property - this.image = newCanvas(this.extent(), false, this.image); - context = this.image.getContext('2d'); - - // prepare context for drawing text - context.font = this.font(); - context.textAlign = 'left'; - context.textBaseline = 'bottom'; - - // first draw the shadow, if any - if (this.shadowColor) { - x = Math.max(shadowOffset.x, 0); - y = Math.max(shadowOffset.y, 0); - context.fillStyle = this.shadowColor.toString(); - context.fillText(txt, x, fontHeight(this.fontSize) + y); - } - - // now draw the actual text - x = Math.abs(Math.min(shadowOffset.x, 0)); - y = Math.abs(Math.min(shadowOffset.y, 0)); - context.fillStyle = this.color.toString(); - - if (this.isShowingBlanks) { - this.renderWithBlanks(context, x, fontHeight(this.fontSize) + y); - } else { - context.fillText(txt, x, fontHeight(this.fontSize) + y); - } - - // draw the selection - start = Math.min(this.startMark, this.endMark); - stop = Math.max(this.startMark, this.endMark); - for (i = start; i < stop; i += 1) { - p = this.slotPosition(i).subtract(this.position()); - c = txt.charAt(i); - context.fillStyle = this.markedBackgoundColor.toString(); - context.fillRect(p.x, p.y, context.measureText(c).width + 1 + x, - fontHeight(this.fontSize) + y); - context.fillStyle = this.markedTextColor.toString(); - context.fillText(c, p.x + x, fontHeight(this.fontSize) + y); - } - // notify my parent of layout change if (this.parent) { if (this.parent.fixLayout) { @@ -8864,39 +8429,90 @@ StringMorph.prototype.drawNew = function () { } }; -StringMorph.prototype.renderWithBlanks = function (context, startX, y) { - var space = context.measureText(' ').width, - blank = newCanvas(new Point(space, this.height())), - ctx = blank.getContext('2d'), +StringMorph.prototype.render = function (ctx) { + var start, stop, i, p, c, x, y, + shadowOffset = this.shadowOffset || new Point(), + txt = this.isPassword ? + this.password('*', this.text.length) : this.text; + + // prepare context for drawing text + ctx.font = this.font(); + ctx.textAlign = 'left'; + ctx.textBaseline = 'bottom'; + + // first draw the shadow, if any + if (this.shadowColor) { + x = Math.max(shadowOffset.x, 0); + y = Math.max(shadowOffset.y, 0); + ctx.fillStyle = this.shadowColor.toString(); + ctx.fillText(txt, x, fontHeight(this.fontSize) + y); + } + + // now draw the actual text + x = Math.abs(Math.min(shadowOffset.x, 0)); + y = Math.abs(Math.min(shadowOffset.y, 0)); + ctx.fillStyle = this.color.toString(); + + if (this.isShowingBlanks) { + this.renderWithBlanks( + ctx, + x, + fontHeight(this.fontSize) + y + ); + } else { + ctx.fillText( + txt, + x, + fontHeight(this.fontSize) + y + ); + } + + // draw the selection + start = Math.min(this.startMark, this.endMark); + stop = Math.max(this.startMark, this.endMark); + for (i = start; i < stop; i += 1) { + p = this.slotPosition(i).subtract(this.position()); + c = txt.charAt(i); + ctx.fillStyle = this.markedBackgoundColor.toString(); + ctx.fillRect(p.x, p.y, ctx.measureText(c).width + 1 + x, + fontHeight(this.fontSize) + y); + ctx.fillStyle = this.markedTextColor.toString(); + ctx.fillText(c, p.x, fontHeight(this.fontSize) + p.y); + } +}; + +StringMorph.prototype.renderWithBlanks = function (ctx, startX, y) { + var space = ctx.measureText(' ').width, + blanksColor = this.blanksColor.toString(), + top = y - this.height() / 2, words = this.text.split(' '), x = startX || 0, isFirst = true; - // create the blank form - ctx.fillStyle = this.blanksColor.toString(); - ctx.arc( - space / 2, - blank.height / 2, - space / 2, - radians(0), - radians(360) - ); - ctx.fill(); - function drawBlank() { - context.drawImage(blank, x, 0); + ctx.fillStyle = blanksColor; + ctx.beginPath(); + ctx.arc( + x + space / 2, + top, + space / 2, + radians(0), + radians(360) + ); + ctx.fill(); x += space; } // render my text inserting blanks - words.forEach(function (word) { + words.forEach(word => { if (!isFirst) { drawBlank(); } isFirst = false; if (word !== '') { - context.fillText(word, x, y); - x += context.measureText(word).width; + ctx.fillStyle = this.color.toString(); + ctx.fillText(word, x, y); + x += ctx.measureText(word).width; } }); }; @@ -8909,15 +8525,15 @@ StringMorph.prototype.slotPosition = function (slot) { var txt = this.isPassword ? this.password('*', this.text.length) : this.text, dest = Math.min(Math.max(slot, 0), txt.length), - context = this.image.getContext('2d'), xOffset, x, y, idx; xOffset = 0; + this.measureCtx.font = this.font(); for (idx = 0; idx < dest; idx += 1) { - xOffset += context.measureText(txt[idx]).width; + xOffset += this.measureCtx.measureText(txt[idx]).width; } this.pos = dest; x = this.left() + xOffset; @@ -8933,15 +8549,15 @@ StringMorph.prototype.slotAt = function (aPoint) { var txt = this.isPassword ? this.password('*', this.text.length) : this.text, idx = 0, - charX = 0, - context = this.image.getContext('2d'); + charX = 0; + this.measureCtx.font = this.font(); while (aPoint.x - this.left() > charX) { - charX += context.measureText(txt[idx]).width; + charX += this.measureCtx.measureText(txt[idx]).width; idx += 1; if (idx === txt.length) { - if ((context.measureText(txt).width - - (context.measureText(txt[idx - 1]).width / 2)) < + if ((this.measureCtx.measureText(txt).width - + (this.measureCtx.measureText(txt[idx - 1]).width / 2)) < (aPoint.x - this.left())) { return idx; } @@ -8950,7 +8566,7 @@ StringMorph.prototype.slotAt = function (aPoint) { // 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) { + charX - this.measureCtx.measureText(txt[idx - 1]).width / 2) { return idx; } else { return idx - 1; @@ -9026,7 +8642,7 @@ StringMorph.prototype.developersMenu = function () { menu.addItem("edit", 'edit'); menu.addItem( "font size...", - function () { + () => { this.prompt( menu.title + '\nfont\nsize:', this.setFontSize, @@ -9082,43 +8698,43 @@ StringMorph.prototype.toggleIsDraggable = function () { StringMorph.prototype.toggleShowBlanks = function () { this.isShowingBlanks = !this.isShowingBlanks; this.changed(); - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; StringMorph.prototype.toggleWeight = function () { this.isBold = !this.isBold; this.changed(); - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; StringMorph.prototype.toggleItalic = function () { this.isItalic = !this.isItalic; this.changed(); - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; StringMorph.prototype.toggleIsPassword = function () { this.isPassword = !this.isPassword; this.changed(); - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; StringMorph.prototype.setSerif = function () { this.fontStyle = 'serif'; this.changed(); - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; StringMorph.prototype.setSansSerif = function () { this.fontStyle = 'sans-serif'; this.changed(); - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; StringMorph.prototype.setFontSize = function (size) { @@ -9135,16 +8751,16 @@ StringMorph.prototype.setFontSize = function (size) { } } this.changed(); - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; StringMorph.prototype.setText = function (size) { // for context menu demo purposes this.text = Math.round(size).toString(); this.changed(); - this.drawNew(); - this.changed(); + this.fixLayout(); + this.rerender(); }; StringMorph.prototype.numericalSetters = function () { @@ -9184,7 +8800,6 @@ StringMorph.prototype.clearSelection = function () { this.currentlySelecting = false; this.startMark = null; this.endMark = null; - this.drawNew(); this.changed(); }; @@ -9208,7 +8823,6 @@ StringMorph.prototype.selectAll = function () { cursor.gotoSlot(this.text.length); cursor.syncTextareaSelectionWith(this); } - this.drawNew(); this.changed(); } }; @@ -9233,7 +8847,6 @@ StringMorph.prototype.shiftClick = function (pos) { cursor.gotoPos(pos); this.endMark = cursor.slot; cursor.syncTextareaSelectionWith(this); - this.drawNew(); this.changed(); } this.currentlySelecting = false; @@ -9323,8 +8936,6 @@ StringMorph.prototype.selectWordAt = function (slot) { this.startMark = slot; this.endMark = this.nextWordFrom(slot); } - - this.drawNew(); this.changed(); }; @@ -9339,13 +8950,11 @@ StringMorph.prototype.selectBetweenWordsAt = function (slot) { && !isWordChar(this.text[this.endMark])) { this.endMark += 1; } - - this.drawNew(); this.changed(); }; StringMorph.prototype.enableSelecting = function () { - this.mouseDownLeft = function (pos) { + this.mouseDownLeft = (pos) => { var crs = this.root().cursor, already = crs ? crs.target === this : false; if (this.world().currentKey === 16) { @@ -9363,7 +8972,7 @@ StringMorph.prototype.enableSelecting = function () { } } }; - this.mouseMove = function (pos) { + this.mouseMove = (pos) => { if (this.isEditable && this.currentlySelecting && (!this.isDraggable)) { @@ -9371,7 +8980,6 @@ StringMorph.prototype.enableSelecting = function () { if (newMark !== this.endMark) { this.endMark = newMark; this.root().cursor.syncTextareaSelectionWith(this); - this.drawNew(); this.changed(); } } @@ -9471,8 +9079,7 @@ TextMorph.prototype.init = function ( // override inherited properites: this.color = new Color(0, 0, 0); - this.noticesTransparentClick = true; - this.drawNew(); + this.fixLayout(); // determine my extent }; TextMorph.prototype.toString = function () { @@ -9483,8 +9090,7 @@ TextMorph.prototype.toString = function () { TextMorph.prototype.font = StringMorph.prototype.font; TextMorph.prototype.parse = function () { - var myself = this, - paragraphs = this.text.split('\n'), + var paragraphs = this.text.split('\n'), context = this.measureCtx, oldline = '', newline, @@ -9497,29 +9103,29 @@ TextMorph.prototype.parse = function () { this.lineSlots = [0]; this.words = []; - paragraphs.forEach(function (p) { - myself.words = myself.words.concat(p.split(' ')); - myself.words.push('\n'); + paragraphs.forEach(p => { + this.words = this.words.concat(p.split(' ')); + this.words.push('\n'); }); - this.words.forEach(function (word) { + this.words.forEach(word => { if (word === '\n') { - myself.lines.push(oldline); - myself.lineSlots.push(slot); - myself.maxLineWidth = Math.max( - myself.maxLineWidth, + this.lines.push(oldline); + this.lineSlots.push(slot); + this.maxLineWidth = Math.max( + this.maxLineWidth, context.measureText(oldline).width ); oldline = ''; } else { - if (myself.maxWidth > 0) { + if (this.maxWidth > 0) { newline = oldline + word + ' '; w = context.measureText(newline).width; - if (w > myself.maxWidth) { - myself.lines.push(oldline); - myself.lineSlots.push(slot); - myself.maxLineWidth = Math.max( - myself.maxLineWidth, + if (w > this.maxWidth) { + this.lines.push(oldline); + this.lineSlots.push(slot); + this.maxLineWidth = Math.max( + this.maxLineWidth, context.measureText(oldline).width ); oldline = word + ' '; @@ -9534,9 +9140,9 @@ TextMorph.prototype.parse = function () { }); }; -TextMorph.prototype.drawNew = function () { - var context, height, i, line, width, shadowHeight, shadowWidth, - offx, offy, x, y, start, stop, p, c; +TextMorph.prototype.fixLayout = function () { + // determine my extent depending on my current settings + var height, shadowHeight, shadowWidth; this.parse(); @@ -9554,29 +9160,39 @@ TextMorph.prototype.drawNew = function () { ); } - this.image = newCanvas(this.extent(), false, this.image); + // notify my parent of layout change + if (this.parent) { + if (this.parent.layoutChanged) { + this.parent.layoutChanged(); + } + } +}; + +TextMorph.prototype.render = function (ctx) { + var shadowWidth = Math.abs(this.shadowOffset.x), + shadowHeight = Math.abs(this.shadowOffset.y), + i, line, width, offx, offy, x, y, start, stop, p, c; // prepare context for drawing text - context = this.image.getContext('2d'); - context.font = this.font(); - context.textAlign = 'left'; - context.textBaseline = 'bottom'; + ctx.font = this.font(); + ctx.textAlign = 'left'; + ctx.textBaseline = 'bottom'; // fill the background, if desired if (this.backgroundColor) { - context.fillStyle = this.backgroundColor.toString(); - context.fillRect(0, 0, this.width(), this.height()); + ctx.fillStyle = this.backgroundColor.toString(); + ctx.fillRect(0, 0, this.width(), this.height()); } // draw the shadow, if any if (this.shadowColor) { offx = Math.max(this.shadowOffset.x, 0); offy = Math.max(this.shadowOffset.y, 0); - context.fillStyle = this.shadowColor.toString(); + ctx.fillStyle = this.shadowColor.toString(); for (i = 0; i < this.lines.length; i = i + 1) { line = this.lines[i]; - width = context.measureText(line).width + shadowWidth; + width = ctx.measureText(line).width + shadowWidth; if (this.alignment === 'right') { x = this.width() - width; } else if (this.alignment === 'center') { @@ -9586,18 +9202,18 @@ TextMorph.prototype.drawNew = function () { } y = (i + 1) * (fontHeight(this.fontSize) + shadowHeight) - shadowHeight; - context.fillText(line, x + offx, y + offy); + ctx.fillText(line, x + offx, y + offy); } } // now draw the actual text offx = Math.abs(Math.min(this.shadowOffset.x, 0)); offy = Math.abs(Math.min(this.shadowOffset.y, 0)); - context.fillStyle = this.color.toString(); + ctx.fillStyle = this.color.toString(); for (i = 0; i < this.lines.length; i = i + 1) { line = this.lines[i]; - width = context.measureText(line).width + shadowWidth; + width = ctx.measureText(line).width + shadowWidth; if (this.alignment === 'right') { x = this.width() - width; } else if (this.alignment === 'center') { @@ -9605,9 +9221,8 @@ TextMorph.prototype.drawNew = function () { } else { // 'left' x = 0; } - y = (i + 1) * (fontHeight(this.fontSize) + shadowHeight) - - shadowHeight; - context.fillText(line, x + offx, y + offy); + y = (i + 1) * (fontHeight(this.fontSize) + shadowHeight) - shadowHeight; + ctx.fillText(line, x + offx, y + offy); } // draw the selection @@ -9616,25 +9231,19 @@ TextMorph.prototype.drawNew = function () { for (i = start; i < stop; i += 1) { p = this.slotPosition(i).subtract(this.position()); c = this.text.charAt(i); - context.fillStyle = this.markedBackgoundColor.toString(); - context.fillRect(p.x, p.y, context.measureText(c).width + 1, + ctx.fillStyle = this.markedBackgoundColor.toString(); + ctx.fillRect(p.x, p.y, ctx.measureText(c).width + 1, fontHeight(this.fontSize)); - context.fillStyle = this.markedTextColor.toString(); - context.fillText(c, p.x, p.y + fontHeight(this.fontSize)); - } - - // notify my parent of layout change - if (this.parent) { - if (this.parent.layoutChanged) { - this.parent.layoutChanged(); - } + ctx.fillStyle = this.markedTextColor.toString(); + ctx.fillText(c, p.x, p.y + fontHeight(this.fontSize)); } }; TextMorph.prototype.setExtent = function (aPoint) { this.maxWidth = Math.max(aPoint.x, 0); this.changed(); - this.drawNew(); + this.fixLayout(); + this.rerender(); }; // TextMorph mesuring: @@ -9665,7 +9274,7 @@ TextMorph.prototype.slotPosition = function (slot) { // answer the physical position point of the given index ("slot") // where the cursor should be placed var colRow = this.columnRow(slot), - context = this.image.getContext('2d'), + ctx = this.measureCtx, shadowHeight = Math.abs(this.shadowOffset.y), xOffset = 0, yOffset, @@ -9673,9 +9282,10 @@ TextMorph.prototype.slotPosition = function (slot) { y, idx; + ctx.font = this.font(); yOffset = colRow.y * (fontHeight(this.fontSize) + shadowHeight); for (idx = 0; idx < colRow.x; idx += 1) { - xOffset += context.measureText(this.lines[colRow.y][idx]).width; + xOffset += ctx.measureText(this.lines[colRow.y][idx]).width; } x = this.left() + xOffset; y = this.top() + yOffset; @@ -9692,7 +9302,7 @@ TextMorph.prototype.slotAt = function (aPoint) { col = 0, columnLength, shadowHeight = Math.abs(this.shadowOffset.y), - context = this.image.getContext('2d'), + ctx = this.measureCtx, textWidth; while (aPoint.y - this.top() > @@ -9701,7 +9311,8 @@ TextMorph.prototype.slotAt = function (aPoint) { } row = Math.max(row, 1); - textWidth = context.measureText(this.lines[row - 1]).width; + ctx.font = this.font(); + textWidth = ctx.measureText(this.lines[row - 1]).width; if (this.alignment === 'right') { charX = this.width() - textWidth; } else if (this.alignment === 'center') { @@ -9711,13 +9322,13 @@ TextMorph.prototype.slotAt = function (aPoint) { } columnLength = this.lines[row - 1].length; while (col < columnLength - 2 && aPoint.x - this.left() > charX) { - charX += context.measureText(this.lines[row - 1][col]).width; + charX += ctx.measureText(this.lines[row - 1][col]).width; 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) { + charX - ctx.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; @@ -9812,7 +9423,7 @@ TextMorph.prototype.developersMenu = function () { menu.addItem("edit", 'edit'); menu.addItem( "font size...", - function () { + () => { this.prompt( menu.title + '\nfont\nsize:', this.setFontSize, @@ -9857,20 +9468,17 @@ TextMorph.prototype.developersMenu = function () { TextMorph.prototype.setAlignmentToLeft = function () { this.alignment = 'left'; - this.drawNew(); - this.changed(); + this.rerender(); }; TextMorph.prototype.setAlignmentToRight = function () { this.alignment = 'right'; - this.drawNew(); - this.changed(); + this.rerender(); }; TextMorph.prototype.setAlignmentToCenter = function () { this.alignment = 'center'; - this.drawNew(); - this.changed(); + this.rerender(); }; TextMorph.prototype.toggleIsDraggable @@ -10013,44 +9621,27 @@ TriggerMorph.prototype.init = function ( this.labelColor = labelColor || new Color(0, 0, 0); this.labelBold = labelBold || false; this.labelItalic = labelItalic || false; + this.userState = 'normal'; // 'highlight', 'pressed' // initialize inherited properties: TriggerMorph.uber.init.call(this); // override inherited properites: this.color = new Color(255, 255, 255); - this.drawNew(); + this.createLabel(); }; // TriggerMorph drawing: -TriggerMorph.prototype.drawNew = function () { - this.createBackgrounds(); - if (this.labelString !== null) { - this.createLabel(); +TriggerMorph.prototype.render = function (ctx) { + var colorBak = this.color; + if (this.userState === 'highlight') { + this.color = this.highlightColor; + } else if (this.userState === 'pressed') { + this.color = this.pressColor; } -}; - -TriggerMorph.prototype.createBackgrounds = function () { - var context, - ext = this.extent(); - - this.normalImage = newCanvas(ext, false, this.normalImage); - context = this.normalImage.getContext('2d'); - context.fillStyle = this.color.toString(); - context.fillRect(0, 0, ext.x, ext.y); - - this.highlightImage = newCanvas(ext, false, this.highlightImage); - context = this.highlightImage.getContext('2d'); - context.fillStyle = this.highlightColor.toString(); - context.fillRect(0, 0, ext.x, ext.y); - - this.pressImage = newCanvas(ext, false, this.pressImage); - context = this.pressImage.getContext('2d'); - context.fillStyle = this.pressColor.toString(); - context.fillRect(0, 0, ext.x, ext.y); - - this.image = this.normalImage; + TriggerMorph.uber.render.call(this, ctx); + this.color = colorBak; }; TriggerMorph.prototype.createLabel = function () { @@ -10068,12 +9659,16 @@ TriggerMorph.prototype.createLabel = function () { null, // shadow color this.labelColor ); + this.fixLayout(); + this.add(this.label); +}; + +TriggerMorph.prototype.fixLayout = function () { this.label.setPosition( this.center().subtract( this.label.extent().floorDivideBy(2) ) ); - this.add(this.label); }; // TriggerMorph action: @@ -10147,16 +9742,16 @@ TriggerMorph.prototype.triggerDoubleClick = function () { TriggerMorph.prototype.mouseEnter = function () { var contents = this.hint instanceof Function ? this.hint() : this.hint; - this.image = this.highlightImage; - this.changed(); + this.userState = 'highlight'; + this.rerender(); if (contents) { this.bubbleHelp(contents); } }; TriggerMorph.prototype.mouseLeave = function () { - this.image = this.normalImage; - this.changed(); + this.userState = 'normal'; + this.rerender(); if (this.schedule) { this.schedule.isActive = false; } @@ -10166,13 +9761,13 @@ TriggerMorph.prototype.mouseLeave = function () { }; TriggerMorph.prototype.mouseDownLeft = function () { - this.image = this.pressImage; - this.changed(); + this.userState = 'pressed'; + this.rerender(); }; TriggerMorph.prototype.mouseClickLeft = function () { - this.image = this.highlightImage; - this.changed(); + this.userState = 'highlight'; + this.rerender(); this.trigger(); }; @@ -10187,15 +9782,14 @@ TriggerMorph.prototype.rootForGrab = function () { // TriggerMorph bubble help: TriggerMorph.prototype.bubbleHelp = function (contents) { - var world = this.world(), - myself = this; + var world = this.world(); this.schedule = new Animation( nop, nop, 0, 500, nop, - function () {myself.popUpbubbleHelp(contents); } + () => this.popUpbubbleHelp(contents) ); world.animations.push(this.schedule); }; @@ -10275,8 +9869,7 @@ MenuItemMorph.prototype.createLabel = function () { h = Math.max(h, this.shortcut.height()); this.add(this.shortcut); } - this.silentSetExtent(new Point(w + 8, h)); - this.fixLayout(); + this.setExtent(new Point(w + 8, h)); }; MenuItemMorph.prototype.fixLayout = function () { @@ -10305,30 +9898,34 @@ MenuItemMorph.prototype.createLabelPart = function (source) { lbl.setCenter(icon.center()); lbl.setLeft(icon.right() + 4); part.bounds = (icon.bounds.merge(lbl.bounds)); - part.drawNew(); + part.rerender(); return part; } // assume it's either a Morph or a Canvas return this.createIcon(source); }; -MenuItemMorph.prototype.createIcon = function (source) { +MenuItemMorph.prototype.createIcon = function (source) { // +++ under construction // source can be either a Morph or an HTMLCanvasElement var icon = new Morph(), src; - icon.image = source instanceof Morph ? source.fullImage() : source; + // +++ to do: tell Morph to cache the image + icon.isCachingImage = true; // +++ review + icon.cachedImage = source instanceof Morph ? source.fullImage() : source; // +++ review this // adjust shadow dimensions if (source instanceof Morph && source.getShadow()) { - src = icon.image; - icon.image = newCanvas( + src = icon.cachedImage; + icon.cachedImage = newCanvas( source.fullBounds().extent().subtract( this.shadowBlur * (useBlurredShadows ? 1 : 2) ) ); - icon.image.getContext('2d').drawImage(src, 0, 0); + icon.cachedImage.getContext('2d').drawImage(src, 0, 0); // +++ review } - icon.silentSetWidth(icon.image.width); - icon.silentSetHeight(icon.image.height); + icon.setExtent(new Point( + icon.cachedImage.width, + icon.cachedImage.height) + ); return icon; }; @@ -10355,8 +9952,8 @@ MenuItemMorph.prototype.mouseEnter = function () { menu.closeSubmenu(); } if (!this.isListItem()) { - this.image = this.highlightImage; - this.changed(); + this.userState = 'highlight'; + this.rerender(); } if (this.action instanceof MenuMorph) { this.delaySubmenu(); @@ -10368,11 +9965,11 @@ MenuItemMorph.prototype.mouseEnter = function () { MenuItemMorph.prototype.mouseLeave = function () { if (!this.isListItem()) { if (this.isShowingSubmenu()) { - this.image = this.highlightImage; + this.userState = 'highlight'; } else { - this.image = this.normalImage; + this.userState = 'normal'; } - this.changed(); + this.rerender(); } if (this.schedule) { this.schedule.isActive = false; @@ -10387,8 +9984,8 @@ MenuItemMorph.prototype.mouseDownLeft = function (pos) { this.parentThatIsA(MenuMorph).unselectAllItems(); this.escalateEvent('mouseDownLeft', pos); } - this.image = this.pressImage; - this.changed(); + this.userState = 'pressed'; + this.rerender(); }; MenuItemMorph.prototype.mouseMove = function () { @@ -10419,7 +10016,7 @@ MenuItemMorph.prototype.isListItem = function () { MenuItemMorph.prototype.isSelectedListItem = function () { if (this.isListItem()) { - return this.image === this.pressImage; + return this.userState === 'pressed'; } return false; }; @@ -10435,15 +10032,14 @@ MenuItemMorph.prototype.isShowingSubmenu = function () { // MenuItemMorph submenus: MenuItemMorph.prototype.delaySubmenu = function () { - var world = this.world(), - myself = this; + var world = this.world(); this.schedule = new Animation( nop, nop, 0, 500, nop, - function () {myself.popUpSubmenu(); } + () => this.popUpSubmenu() ); world.animations.push(this.schedule); }; @@ -10451,7 +10047,7 @@ MenuItemMorph.prototype.delaySubmenu = function () { MenuItemMorph.prototype.popUpSubmenu = function () { var menu = this.parentThatIsA(MenuMorph); if (!(this.action instanceof MenuMorph)) {return; } - this.action.drawNew(); + this.action.createItems(); this.action.setPosition(this.topRight().subtract(new Point(0, 5))); this.action.addShadow(new Point(2, 2), 80); this.action.keepWithin(this.world()); @@ -10481,12 +10077,10 @@ FrameMorph.prototype.init = function (aScrollFrame) { FrameMorph.uber.init.call(this); this.color = new Color(255, 250, 245); - this.drawNew(); this.acceptsDrops = true; if (this.scrollFrame) { this.isDraggable = false; - this.noticesTransparentClick = false; this.alpha = 0; } }; @@ -10501,27 +10095,25 @@ FrameMorph.prototype.fullBounds = function () { FrameMorph.prototype.fullImage = function () { // use only for shadows - return this.image; + return this.getImage(); }; -FrameMorph.prototype.fullDrawOn = function (aCanvas, aRect) { - var rectangle, dirty; - if (!this.isVisible) { - return null; - } - rectangle = aRect || this.fullBounds(); - dirty = this.bounds.intersect(rectangle); - if (!dirty.extent().gt(new Point(0, 0))) { - return null; - } - this.drawOn(aCanvas, dirty); - this.children.forEach(function (child) { +FrameMorph.prototype.fullDrawOn = function (ctx, aRect) { + var shadow, clipped; + if (!this.isVisible) {return; } + clipped = this.bounds.intersect(aRect); + if (!clipped.extent().gt(ZERO)) {return; } + this.drawOn(ctx, clipped); + this.children.forEach(child => { if (child instanceof ShadowMorph) { - child.fullDrawOn(aCanvas, rectangle); + shadow = child; } else { - child.fullDrawOn(aCanvas, dirty); + child.fullDrawOn(ctx, clipped); } }); + if (shadow) { + shadow.drawOn(ctx, aRect); + } }; // FrameMorph navigation: @@ -10535,8 +10127,10 @@ FrameMorph.prototype.topMorphAt = function (point) { result = this.children[i].topMorphAt(point); if (result) {return result; } } - return this.noticesTransparentClick || - !this.isTransparentAt(point) ? this : null; + if (this.isFreeForm) { + return this.isTransparentAt(point) ? null : this; + } + return this; }; // FrameMorph scrolling support: @@ -10546,7 +10140,7 @@ FrameMorph.prototype.submorphBounds = function () { if (this.children.length > 0) { result = this.children[0].bounds; - this.children.forEach(function (child) { + this.children.forEach(child => { result = result.merge(child.fullBounds()); }); } @@ -10582,13 +10176,9 @@ FrameMorph.prototype.keepInScrollFrame = function () { FrameMorph.prototype.adjustBounds = function () { var subBounds, - newBounds, - myself = this; - - if (this.scrollFrame === null) { - return null; - } + newBounds; + if (this.scrollFrame === null) {return; } subBounds = this.submorphBounds(); if (subBounds && (!this.scrollFrame.isTextLineWrapping)) { newBounds = subBounds @@ -10600,21 +10190,18 @@ FrameMorph.prototype.adjustBounds = function () { } if (!this.bounds.eq(newBounds)) { this.bounds = newBounds; - this.drawNew(); this.keepInScrollFrame(); } - if (this.scrollFrame.isTextLineWrapping) { - this.children.forEach(function (morph) { + this.children.forEach(morph => { if (morph instanceof TextMorph) { - morph.setWidth(myself.width()); - myself.setHeight( - Math.max(morph.height(), myself.scrollFrame.height()) + morph.setWidth(this.width()); + this.setHeight( + Math.max(morph.height(), this.scrollFrame.height()) ); } }); } - this.scrollFrame.adjustScrollBars(); }; @@ -10644,10 +10231,7 @@ FrameMorph.prototype.developersMenu = function () { }; FrameMorph.prototype.keepAllSubmorphsWithin = function () { - var myself = this; - this.children.forEach(function (m) { - m.keepWithin(myself); - }); + this.children.forEach(m => m.keepWithin(this)); }; // ScrollFrameMorph //////////////////////////////////////////////////// @@ -10661,8 +10245,6 @@ function ScrollFrameMorph(scroller, size, sliderColor) { } ScrollFrameMorph.prototype.init = function (scroller, size, sliderColor) { - var myself = this; - ScrollFrameMorph.uber.init.call(this); this.scrollBarSize = size || MorphicPreferences.scrollBarSize; this.autoScrollTrigger = null; @@ -10683,11 +10265,11 @@ ScrollFrameMorph.prototype.init = function (scroller, size, sliderColor) { sliderColor ); this.hBar.setHeight(this.scrollBarSize); - this.hBar.action = function (num) { - myself.contents.setPosition( + this.hBar.action = (num) => { + this.contents.setPosition( new Point( - myself.left() - num, - myself.contents.position().y + this.left() - num, + this.contents.position().y ) ); }; @@ -10702,11 +10284,11 @@ ScrollFrameMorph.prototype.init = function (scroller, size, sliderColor) { sliderColor ); this.vBar.setWidth(this.scrollBarSize); - this.vBar.action = function (num) { - myself.contents.setPosition( + this.vBar.action = (num) => { + this.contents.setPosition( new Point( - myself.contents.position().x, - myself.top() - num + this.contents.position().x, + this.top() - num ) ); }; @@ -10733,11 +10315,13 @@ ScrollFrameMorph.prototype.adjustScrollBars = function () { ) ); this.hBar.start = 0; - this.hBar.stop = this.contents.width() - this.width() + this.scrollBarSize; + this.hBar.stop = this.contents.width() - + this.width() + + this.scrollBarSize; this.hBar.size = this.width() / this.contents.width() * this.hBar.stop; this.hBar.value = this.left() - this.contents.left(); - this.hBar.drawNew(); + this.hBar.fixLayout(); } else { this.hBar.hide(); } @@ -10755,11 +10339,13 @@ ScrollFrameMorph.prototype.adjustScrollBars = function () { ) ); this.vBar.start = 0; - this.vBar.stop = this.contents.height() - this.height() + this.scrollBarSize; + this.vBar.stop = this.contents.height() - + this.height() + + this.scrollBarSize; this.vBar.size = this.height() / this.contents.height() * this.vBar.stop; this.vBar.value = this.top() - this.contents.top(); - this.vBar.drawNew(); + this.vBar.fixLayout(); } else { this.vBar.hide(); } @@ -10782,9 +10368,7 @@ ScrollFrameMorph.prototype.addContents = function (aMorph) { }; ScrollFrameMorph.prototype.setContents = function (aMorph) { - this.contents.children.forEach(function (m) { - m.destroy(); - }); + this.contents.children.forEach(m => m.destroy()); this.contents.children = []; aMorph.setPosition(this.position().add(this.padding + 2)); this.addContents(aMorph); @@ -10855,16 +10439,15 @@ ScrollFrameMorph.prototype.mouseDownLeft = function (pos) { var world = this.root(), hand = world.hand, oldPos = pos, - myself = this, deltaX = 0, deltaY = 0, friction = 0.8; - this.step = function () { + this.step = () => { var newPos; if (hand.mouseButton && (hand.children.length === 0) && - (myself.bounds.containsPoint(hand.bounds.origin))) { + (this.bounds.containsPoint(hand.bounds.origin))) { if (hand.grabPosition && (hand.grabPosition.distanceTo(hand.position()) <= @@ -10876,29 +10459,25 @@ ScrollFrameMorph.prototype.mouseDownLeft = function (pos) { newPos = hand.bounds.origin; deltaX = newPos.x - oldPos.x; if (deltaX !== 0) { - myself.scrollX(deltaX); + this.scrollX(deltaX); } deltaY = newPos.y - oldPos.y; if (deltaY !== 0) { - myself.scrollY(deltaY); + this.scrollY(deltaY); } oldPos = newPos; } else { - if (!myself.hasVelocity) { - myself.step = function () { - nop(); - }; + if (!this.hasVelocity) { + this.step = nop; } else { if ((Math.abs(deltaX) < 0.5) && (Math.abs(deltaY) < 0.5)) { - myself.step = function () { - nop(); - }; + this.step = nop; } else { deltaX = deltaX * friction; - myself.scrollX(Math.round(deltaX)); + this.scrollX(Math.round(deltaX)); deltaY = deltaY * friction; - myself.scrollY(Math.round(deltaY)); + this.scrollY(Math.round(deltaY)); } } } @@ -10907,8 +10486,7 @@ ScrollFrameMorph.prototype.mouseDownLeft = function (pos) { }; ScrollFrameMorph.prototype.startAutoScrolling = function () { - var myself = this, - inset = MorphicPreferences.scrollBarSize * 3, + var inset = MorphicPreferences.scrollBarSize * 3, world = this.world(), hand, inner, @@ -10921,18 +10499,16 @@ ScrollFrameMorph.prototype.startAutoScrolling = function () { if (!this.autoScrollTrigger) { this.autoScrollTrigger = Date.now(); } - this.step = function () { + this.step = () => { pos = hand.bounds.origin; - inner = myself.bounds.insetBy(inset); - if ((myself.bounds.containsPoint(pos)) && + inner = this.bounds.insetBy(inset); + if ((this.bounds.containsPoint(pos)) && (!(inner.containsPoint(pos))) && (hand.children.length > 0)) { - myself.autoScroll(pos); + this.autoScroll(pos); } else { - myself.step = function () { - nop(); - }; - myself.autoScrollTrigger = null; + this.step = nop; + this.autoScrollTrigger = null; } }; }; @@ -10999,19 +10575,18 @@ ScrollFrameMorph.prototype.mouseScroll = function (y, x) { // ScrollFrameMorph duplicating: ScrollFrameMorph.prototype.updateReferences = function (map) { - var myself = this; ScrollFrameMorph.uber.updateReferences.call(this, map); if (this.hBar) { - this.hBar.action = function (num) { - myself.contents.setPosition( - new Point(myself.left() - num, myself.contents.position().y) + this.hBar.action = (num) => { + this.contents.setPosition( + new Point(this.left() - num, this.contents.position().y) ); }; } if (this.vBar) { - this.vBar.action = function (num) { - myself.contents.setPosition( - new Point(myself.contents.position().x, myself.top() - num) + this.vBar.action = (num) => { + this.contents.setPosition( + new Point(this.contents.position().x, this.top() - num) ); }; } @@ -11109,7 +10684,6 @@ ListMorph.prototype.init = function ( }; ListMorph.prototype.buildListContents = function () { - var myself = this; if (this.listContents) { this.listContents.destroy(); } @@ -11121,12 +10695,12 @@ ListMorph.prototype.buildListContents = function () { if (this.elements.length === 0) { this.elements = ['(empty)']; } - this.elements.forEach(function (element) { + this.elements.forEach(element => { var color = null, bold = false, italic = false; - myself.format.forEach(function (pair) { + this.format.forEach(pair => { if (pair[1].call(null, element)) { if (pair[0] === 'bold') { bold = true; @@ -11137,18 +10711,18 @@ ListMorph.prototype.buildListContents = function () { } } }); - myself.listContents.addItem( - myself.labelGetter(element), // label string + this.listContents.addItem( + this.labelGetter(element), // label string element, // action null, // hint color, bold, italic, - myself.doubleClickAction + this.doubleClickAction ); }); this.listContents.isListContents = true; - this.listContents.drawNew(); + this.listContents.createItems(); this.listContents.setPosition(this.contents.position()); this.addContents(this.listContents); }; @@ -11184,8 +10758,8 @@ ListMorph.prototype.activeIndex = function () { ListMorph.prototype.activateIndex = function (idx) { var item = this.listContents.children[idx]; if (!item) {return; } - item.image = item.pressImage; - item.changed(); + item.userState = 'pressed'; + item.rerender(); item.trigger(); }; @@ -11238,16 +10812,14 @@ StringFieldMorph.prototype.init = function ( this.color = new Color(255, 255, 255); this.isEditable = true; this.acceptsDrops = false; - this.drawNew(); + this.createText(); }; -StringFieldMorph.prototype.drawNew = function () { +StringFieldMorph.prototype.createText = function () { var txt; txt = this.text ? this.string() : this.defaultContents; this.text = null; - this.children.forEach(function (child) { - child.destroy(); - }); + this.children.forEach(child => child.destroy()); this.children = []; this.text = new StringMorph( txt, @@ -11263,13 +10835,12 @@ StringFieldMorph.prototype.drawNew = function () { this.text.isEditable = this.isEditable; this.text.isDraggable = false; this.text.enableSelecting(); - this.silentSetExtent( + this.setExtent( new Point( Math.max(this.width(), this.minWidth), this.text.height() ) ); - StringFieldMorph.uber.drawNew.call(this); this.add(this.text); }; @@ -11427,12 +10998,8 @@ HandMorph.prototype.morphAtPointer = function () { }; HandMorph.prototype.allMorphsAtPointer = function () { - var morphs = this.world.allChildren(), - myself = this; - return morphs.filter(function (m) { - return m.isVisible && - m.visibleBounds().containsPoint(myself.bounds.origin); - }); + return this.world.allChildren().filter(m => m.isVisible && + m.visibleBounds().containsPoint(this.bounds.origin)); }; // HandMorph dragging and dropping: @@ -11463,13 +11030,11 @@ HandMorph.prototype.grab = function (aMorph) { this.world.stopEditing(); this.grabOrigin = aMorph.situation(); if (!(aMorph instanceof MenuMorph)) { - aMorph.addShadow(); + aMorph.addShadow(); } if (aMorph.prepareToBeGrabbed) { aMorph.prepareToBeGrabbed(this); } - aMorph.cachedFullImage = aMorph.fullImageClassic(); - aMorph.cachedFullBounds = aMorph.fullBounds(); this.add(aMorph); this.changed(); if (oldParent && oldParent.reactToGrabOf) { @@ -11486,8 +11051,6 @@ HandMorph.prototype.drop = function () { target = target.selectForEdit ? target.selectForEdit() : target; this.changed(); target.add(morphToDrop); - morphToDrop.cachedFullImage = null; - morphToDrop.cachedFullBounds = null; morphToDrop.changed(); if (!(morphToDrop instanceof MenuMorph)) { morphToDrop.removeShadow(); @@ -11571,16 +11134,15 @@ HandMorph.prototype.processMouseDown = function (event) { }; HandMorph.prototype.processTouchStart = function (event) { - var myself = this; MorphicPreferences.isTouchDevice = true; clearInterval(this.touchHoldTimeout); if (event.touches.length === 1) { this.touchHoldTimeout = setInterval( // simulate mouseRightClick - function () { - myself.processMouseDown({button: 2}); - myself.processMouseUp({button: 2}); + () => { + this.processMouseDown({button: 2}); + this.processMouseUp({button: 2}); event.preventDefault(); - clearInterval(myself.touchHoldTimeout); + clearInterval(this.touchHoldTimeout); }, 400 ); @@ -11662,7 +11224,6 @@ HandMorph.prototype.processMouseMove = function (event) { var pos, posInDocument = getDocumentPositionOf(this.world.worldCanvas), mouseOverNew, - myself = this, morph, topMorph; @@ -11712,37 +11273,37 @@ HandMorph.prototype.processMouseMove = function (event) { } } - this.mouseOverList.forEach(function (old) { + this.mouseOverList.forEach(old => { if (!contains(mouseOverNew, old)) { if (old.mouseLeave) { old.mouseLeave(); } - if (old.mouseLeaveDragging && myself.mouseButton) { + if (old.mouseLeaveDragging && this.mouseButton) { old.mouseLeaveDragging(); } } }); - mouseOverNew.forEach(function (newMorph) { - if (!contains(myself.mouseOverList, newMorph)) { + mouseOverNew.forEach(newMorph => { + if (!contains(this.mouseOverList, newMorph)) { if (newMorph.mouseEnter) { newMorph.mouseEnter(); } - if (newMorph.mouseEnterDragging && myself.mouseButton) { + if (newMorph.mouseEnterDragging && this.mouseButton) { newMorph.mouseEnterDragging(); } } // autoScrolling support: - if (myself.children.length > 0) { + if (this.children.length > 0) { if (newMorph instanceof ScrollFrameMorph && newMorph.enableAutoScrolling && - newMorph.contents.allChildren().some(function (any) { - return any.wantsDropOf(myself.children[0]); + newMorph.contents.allChildren().some(any => { + return any.wantsDropOf(this.children[0]); }) ) { if (!newMorph.bounds.insetBy( MorphicPreferences.scrollBarSize * 3 - ).containsPoint(myself.bounds.origin)) { + ).containsPoint(this.bounds.origin)) { newMorph.startAutoScrolling(); } } @@ -11813,13 +11374,9 @@ HandMorph.prototype.processDrop = function (event) { while (!target.droppedSVG) { target = target.parent; } - pic.onload = function () { - target.droppedSVG(pic, aFile.name); - }; + pic.onload = () => target.droppedSVG(pic, aFile.name); frd = new FileReader(); - frd.onloadend = function (e) { - pic.src = e.target.result; - }; + frd.onloadend = (e) => pic.src = e.target.result; frd.readAsDataURL(aFile); } @@ -11829,15 +11386,13 @@ HandMorph.prototype.processDrop = function (event) { while (!target.droppedImage) { target = target.parent; } - pic.onload = function () { + pic.onload = () => { canvas = newCanvas(new Point(pic.width, pic.height), true); canvas.getContext('2d').drawImage(pic, 0, 0); target.droppedImage(canvas, aFile.name); }; frd = new FileReader(); - frd.onloadend = function (e) { - pic.src = e.target.result; - }; + frd.onloadend = (e) => pic.src = e.target.result; frd.readAsDataURL(aFile); } @@ -11847,7 +11402,7 @@ HandMorph.prototype.processDrop = function (event) { while (!target.droppedAudio) { target = target.parent; } - frd.onloadend = function (e) { + frd.onloadend = (e) => { snd.src = e.target.result; target.droppedAudio(snd, aFile.name); }; @@ -11859,7 +11414,7 @@ HandMorph.prototype.processDrop = function (event) { while (!target.droppedText) { target = target.parent; } - frd.onloadend = function (e) { + frd.onloadend = (e) => { target.droppedText(e.target.result, aFile.name, aFile.type); }; frd.readAsText(aFile); @@ -11870,7 +11425,7 @@ HandMorph.prototype.processDrop = function (event) { while (!target.droppedBinary) { target = target.parent; } - frd.onloadend = function (e) { + frd.onloadend = (e) => { target.droppedBinary(e.target.result, aFile.name); }; frd.readAsArrayBuffer(aFile); @@ -11879,7 +11434,7 @@ HandMorph.prototype.processDrop = function (event) { function readURL(url, callback) { var request = new XMLHttpRequest(); request.open('GET', url); - request.onreadystatechange = function () { + request.onreadystatechange = () => { if (request.readyState === 4) { if (request.responseText) { callback(request.responseText); @@ -11945,7 +11500,7 @@ HandMorph.prototype.processDrop = function (event) { target = target.parent; } img = new Image(); - img.onload = function () { + img.onload = () => { canvas = newCanvas(new Point(img.width, img.height), true); canvas.getContext('2d').drawImage(img, 0, 0); target.droppedImage(canvas); @@ -11957,9 +11512,9 @@ HandMorph.prototype.processDrop = function (event) { } readURL( url, - function (txt) { + txt => { var pic = new Image(); - pic.onload = function () { + pic.onload = () => { target.droppedSVG( pic, url.slice( @@ -11978,7 +11533,7 @@ HandMorph.prototype.processDrop = function (event) { target = target.parent; } img = new Image(); - img.onload = function () { + img.onload = () => { canvas = newCanvas(new Point(img.width, img.height), true); canvas.getContext('2d').drawImage(img, 0, 0); target.droppedImage(canvas); @@ -11997,12 +11552,11 @@ HandMorph.prototype.destroyTemporaries = function () { that it needs to remove them. The primary purpose of temporaries is to display tools tips of speech bubble help. */ - var myself = this; - this.temporaries.forEach(function (morph) { + this.temporaries.forEach(morph => { if (!(morph.isClickable - && morph.bounds.containsPoint(myself.position()))) { + && morph.bounds.containsPoint(this.position()))) { morph.destroy(); - myself.temporaries.splice(myself.temporaries.indexOf(morph), 1); + this.temporaries.splice(this.temporaries.indexOf(morph), 1); } }); }; @@ -12027,15 +11581,13 @@ function WorldMorph(aCanvas, fillPage) { WorldMorph.prototype.init = function (aCanvas, fillPage) { WorldMorph.uber.init.call(this); - this.color = new Color(205, 205, 205); // (130, 130, 130) + this.color = new Color(205, 205, 205); this.alpha = 1; this.bounds = new Rectangle(0, 0, aCanvas.width, aCanvas.height); - this.drawNew(); this.isVisible = true; this.isDraggable = false; this.currentKey = null; // currently pressed key code this.worldCanvas = aCanvas; - this.noticesTransparentClick = true; // additional properties: this.stamp = Date.now(); // reference in multi-world setups @@ -12050,47 +11602,39 @@ WorldMorph.prototype.init = function (aCanvas, fillPage) { this.broken = []; this.animations = []; this.hand = new HandMorph(this); - this.keyboardReceiver = null; + this.keyboardHandler = null; + this.keyboardFocus = null; this.cursor = null; this.lastEditedText = null; this.activeMenu = null; this.activeHandle = null; - this.virtualKeyboard = null; + this.initKeyboardHandler(); + this.resetKeyboardHandler(); this.initEventListeners(); }; // World Morph display: -WorldMorph.prototype.brokenFor = function (aMorph) { - // private - var fb = aMorph.fullBounds(); - return this.broken.filter(function (rect) { - return rect.intersects(fb); - }); -}; - -WorldMorph.prototype.fullDrawOn = function (aCanvas, aRect) { - WorldMorph.uber.fullDrawOn.call(this, aCanvas, aRect); - this.hand.fullDrawOn(aCanvas, aRect); +WorldMorph.prototype.fullDrawOn = function (aContext, aRect) { + WorldMorph.uber.fullDrawOn.call(this, aContext, aRect); + this.hand.fullDrawOn(aContext, aRect); }; WorldMorph.prototype.updateBroken = function () { - var myself = this; + var ctx = this.worldCanvas.getContext('2d'); this.condenseDamages(); - this.broken.forEach(function (rect) { - if (rect.extent().gt(new Point(0, 0))) { - myself.fullDrawOn(myself.worldCanvas, rect); + this.broken.forEach(rect => { + if (rect.extent().gt(ZERO)) { + this.fullDrawOn(ctx, rect); } }); this.broken = []; }; WorldMorph.prototype.stepAnimations = function () { - this.animations.forEach(function (anim) {anim.step(); }); - this.animations = this.animations.filter(function (anim) { - return anim.isActive; - }); + this.animations.forEach(anim => anim.step()); + this.animations = this.animations.filter(anim => anim.isActive); }; WorldMorph.prototype.condenseDamages = function () { @@ -12099,10 +11643,10 @@ WorldMorph.prototype.condenseDamages = function () { function condense(src) { var trgt = [], hit; - src.forEach(function (rect) { + src.forEach(rect => { hit = detect( trgt, - function (each) {return each.isNearTo(rect, 20); } + each => each.isNearTo(rect, 20) ); if (hit) { hit.mergeWith(rect); @@ -12129,8 +11673,7 @@ WorldMorph.prototype.doOneCycle = function () { WorldMorph.prototype.fillPage = function () { var clientHeight = window.innerHeight, - clientWidth = window.innerWidth, - myself = this; + clientWidth = window.innerWidth; this.worldCanvas.style.position = "absolute"; this.worldCanvas.style.left = "0px"; @@ -12154,9 +11697,9 @@ WorldMorph.prototype.fillPage = function () { this.worldCanvas.height = clientHeight; this.setHeight(clientHeight); } - this.children.forEach(function (child) { + this.children.forEach(child => { if (child.reactToWorldResize) { - child.reactToWorldResize(myself.bounds.copy()); + child.reactToWorldResize(this.bounds.copy()); } }); }; @@ -12190,76 +11733,86 @@ WorldMorph.prototype.getGlobalPixelColor = function (point) { // WorldMorph events: -WorldMorph.prototype.initVirtualKeyboard = function () { - var myself = this; - - if (this.virtualKeyboard) { - document.body.removeChild(this.virtualKeyboard); - this.virtualKeyboard = null; - } - if (!MorphicPreferences.isTouchDevice - || !MorphicPreferences.useVirtualKeyboard) { +WorldMorph.prototype.initKeyboardHandler = function () { + var kbd = document.getElementById('morphic_keyboard'); + if (kbd) { // share existing handler with other worlds + this.keyboardHandler = kbd; return; } - this.virtualKeyboard = document.createElement("input"); - this.virtualKeyboard.type = "text"; - this.virtualKeyboard.style.color = "transparent"; - this.virtualKeyboard.style.backgroundColor = "transparent"; - this.virtualKeyboard.style.border = "none"; - this.virtualKeyboard.style.outline = "none"; - this.virtualKeyboard.style.position = "absolute"; - this.virtualKeyboard.style.top = "0px"; - this.virtualKeyboard.style.left = "0px"; - this.virtualKeyboard.style.width = "0px"; - this.virtualKeyboard.style.height = "0px"; - this.virtualKeyboard.autocapitalize = "none"; // iOS specific - document.body.appendChild(this.virtualKeyboard); + kbd = document.createElement('textarea'); + kbd.setAttribute('id', 'morphic_keyboard'); + kbd.world = this; + kbd.style.zIndex = -1; + kbd.style.position = 'absolute'; + kbd.wrap = "off"; + kbd.style.overflow = "hidden"; + kbd.autofocus = true; + document.body.appendChild(kbd); + this.keyboardHandler = kbd; - this.virtualKeyboard.addEventListener( + kbd.addEventListener( "keydown", - function (event) { + event => { // remember the keyCode in the world's currentKey property - myself.currentKey = event.keyCode; - if (myself.keyboardReceiver) { - myself.keyboardReceiver.processKeyDown(event); + kbd.world.currentKey = event.keyCode; + if (kbd.world.activeMenu && !kbd.world.activeMenu.hasFocus) { + kbd.world.stopEditing(); + kbd.world.activeMenu.getFocus(); } - // supress backspace override - if (event.keyCode === 8) { - event.preventDefault(); + if (kbd.world.keyboardFocus && + kbd.world.keyboardFocus.processKeyDown) { + kbd.world.keyboardFocus.processKeyDown(event); } // supress tab override and make sure tab gets // received by all browsers if (event.keyCode === 9) { - if (myself.keyboardReceiver) { - myself.keyboardReceiver.processKeyPress(event); + if (kbd.world.keyboardFocus && + kbd.world.keyboardFocus.processKeyPress) { + kbd.world.keyboardFocus.processKeyPress(event); } event.preventDefault(); } }, - false + true ); - this.virtualKeyboard.addEventListener( + kbd.addEventListener( "keyup", - function (event) { + event => { // flush the world's currentKey property - myself.currentKey = null; + kbd.world.currentKey = null; // dispatch to keyboard receiver - if (myself.keyboardReceiver) { - if (myself.keyboardReceiver.processKeyUp) { - myself.keyboardReceiver.processKeyUp(event); - } + if (kbd.world.keyboardFocus && + kbd.world.keyboardFocus.processKeyUp) { + kbd.world.keyboardFocus.processKeyUp(event); } event.preventDefault(); }, false ); - this.virtualKeyboard.addEventListener( + kbd.addEventListener( "keypress", - function (event) { - if (myself.keyboardReceiver) { - myself.keyboardReceiver.processKeyPress(event); + event => { + if (kbd.world.keyboardFocus && + kbd.world.keyboardFocus.processKeyPress) { + kbd.world.keyboardFocus.processKeyPress(event); + event.preventDefault(); + } + }, + false + ); + + kbd.addEventListener( + "input", + event => { + if (kbd.world.keyboardFocus && + kbd.world.keyboardFocus.processInput) { + // flush the world's currentKey property + kbd.world.currentKey = null; + kbd.world.keyboardFocus.processInput(event); + } else { + kbd.world.keyboardHandler.value = ''; } event.preventDefault(); }, @@ -12267,178 +11820,119 @@ WorldMorph.prototype.initVirtualKeyboard = function () { ); }; -WorldMorph.prototype.initEventListeners = function () { - var canvas = this.worldCanvas, myself = this; +WorldMorph.prototype.resetKeyboardHandler = function () { + var pos = getDocumentPositionOf(this.worldCanvas); - if (myself.useFillPage) { - myself.fillPage(); + function number2px (n) { + return Math.ceil(n) + 'px'; + } + + this.keyboardHandler.value = ''; + this.keyboardHandler.style.top = number2px(pos.y); + this.keyboardHandler.style.left = number2px(pos.x); +} + +WorldMorph.prototype.initEventListeners = function () { + var canvas = this.worldCanvas; + + if (this.useFillPage) { + this.fillPage(); } else { this.changed(); } canvas.addEventListener( "mousedown", - function (event) { + event => { event.preventDefault(); - canvas.focus(); - myself.hand.processMouseDown(event); + this.keyboardHandler.world = this; // focus the current world + this.resetKeyboardHandler(); + if (!this.onNextStep) { + // horrible kludge to keep Safari from popping up + // a overlay when right-clicking out of a focused + // and edited text or string element + this.keyboardHandler.blur(); + this.onNextStep = () => this.keyboardHandler.focus(); + } + this.hand.processMouseDown(event); }, - false + true ); canvas.addEventListener( "touchstart", - function (event) { - myself.hand.processTouchStart(event); - }, + event => this.hand.processTouchStart(event), false ); canvas.addEventListener( "mouseup", - function (event) { + event => { event.preventDefault(); - myself.hand.processMouseUp(event); + this.hand.processMouseUp(event); }, false ); canvas.addEventListener( "dblclick", - function (event) { + event => { event.preventDefault(); - myself.hand.processDoubleClick(event); + this.hand.processDoubleClick(event); }, false ); canvas.addEventListener( "touchend", - function (event) { - myself.hand.processTouchEnd(event); - }, + event => this.hand.processTouchEnd(event), false ); canvas.addEventListener( "mousemove", - function (event) { - myself.hand.processMouseMove(event); - }, + event => this.hand.processMouseMove(event), false ); canvas.addEventListener( "touchmove", - function (event) { - myself.hand.processTouchMove(event); - }, + event => this.hand.processTouchMove(event), false ); canvas.addEventListener( "contextmenu", - function (event) { - // suppress context menu for Mac-Firefox - event.preventDefault(); - }, - false - ); - - canvas.addEventListener( - "keydown", - function (event) { - // remember the keyCode in the world's currentKey property - myself.currentKey = event.keyCode; - if (myself.keyboardReceiver) { - myself.keyboardReceiver.processKeyDown(event); - } - // supress backspace override - if (event.keyCode === 8) { - event.preventDefault(); - } - // supress tab override and make sure tab gets - // received by all browsers - if (event.keyCode === 9) { - if (myself.keyboardReceiver) { - myself.keyboardReceiver.processKeyPress(event); - } - event.preventDefault(); - } - if ((event.ctrlKey && (!event.altKey) || event.metaKey) && - (event.keyCode !== 86)) { // allow pasting-in - event.preventDefault(); - } - }, - false - ); - - canvas.addEventListener( - "keyup", - function (event) { - // flush the world's currentKey property - myself.currentKey = null; - // dispatch to keyboard receiver - if (myself.keyboardReceiver) { - if (myself.keyboardReceiver.processKeyUp) { - myself.keyboardReceiver.processKeyUp(event); - } - } - event.preventDefault(); - }, - false - ); - - canvas.addEventListener( - "keypress", - function (event) { - if (myself.keyboardReceiver) { - myself.keyboardReceiver.processKeyPress(event); - } - event.preventDefault(); - }, - false + event => event.preventDefault(), + true // suppress context menu for Mac-Firefox ); canvas.addEventListener( // Safari, Chrome "mousewheel", - function (event) { - myself.hand.processMouseScroll(event); + event => { + this.hand.processMouseScroll(event); event.preventDefault(); }, false ); canvas.addEventListener( // Firefox "DOMMouseScroll", - function (event) { - myself.hand.processMouseScroll(event); + event => { + this.hand.processMouseScroll(event); event.preventDefault(); }, false ); - document.body.addEventListener( - "paste", - function (event) { - var txt = event.clipboardData.getData("Text"); - if (txt && myself.cursor) { - myself.cursor.insert(txt); - } - }, - false - ); - window.addEventListener( "dragover", - function (event) { - event.preventDefault(); - }, - false + event => event.preventDefault(), + true ); window.addEventListener( "drop", - function (event) { - myself.hand.processDrop(event); + event => { + this.hand.processDrop(event); event.preventDefault(); }, false @@ -12446,15 +11940,15 @@ WorldMorph.prototype.initEventListeners = function () { window.addEventListener( "resize", - function () { - if (myself.useFillPage) { - myself.fillPage(); + () => { + if (this.useFillPage) { + this.fillPage(); } }, false ); - window.onbeforeunload = function (evt) { + window.onbeforeunload = (evt) => { var e = evt || window.event, msg = "Are you sure you want to leave?"; // For IE and Firefox @@ -12535,9 +12029,7 @@ WorldMorph.prototype.contextMenu = function () { ); menu.addItem( "screenshot...", - function () { - window.open(this.fullImageClassic().toDataURL()); - }, + () => window.open(this.fullImage().toDataURL()), 'open a new window\nwith a picture of this morph' ); menu.addLine(); @@ -12566,7 +12058,7 @@ WorldMorph.prototype.contextMenu = function () { } menu.addItem( "color...", - function () { + () => { this.pickColor( menu.title + localize('\ncolor:'), this.setColor, @@ -12589,6 +12081,19 @@ WorldMorph.prototype.contextMenu = function () { 'smaller menu fonts\nand sliders' ); } + if (MorphicPreferences.showHoles) { + menu.addItem( + 'hide holes', + 'toggleHolesDisplay', + 'debug untouchable regions' + ); + } else { + menu.addItem( + 'show holes', + 'toggleHolesDisplay', + 'debug untouchable regions' + ); + } menu.addLine(); } if (this.isDevMode) { @@ -12613,45 +12118,35 @@ WorldMorph.prototype.userCreateMorph = function () { } menu = new MenuMorph(this, 'make a morph'); - menu.addItem('rectangle', function () { - create(new Morph()); - }); - menu.addItem('box', function () { - create(new BoxMorph()); - }); - menu.addItem('circle box', function () { - create(new CircleBoxMorph()); - }); + menu.addItem('rectangle', () => create(new Morph())); + menu.addItem('box', () => create(new BoxMorph())); + menu.addItem('circle box', () => create(new CircleBoxMorph())); menu.addLine(); - menu.addItem('slider', function () { - create(new SliderMorph()); - }); - menu.addItem('dial', function () { + menu.addItem('slider', () => create(new SliderMorph())); + menu.addItem('dial', () => { newMorph = new DialMorph(); newMorph.pickUp(this); }); - menu.addItem('frame', function () { + menu.addItem('frame', () => { newMorph = new FrameMorph(); newMorph.setExtent(new Point(350, 250)); create(newMorph); }); - menu.addItem('scroll frame', function () { + menu.addItem('scroll frame', () => { newMorph = new ScrollFrameMorph(); newMorph.contents.acceptsDrops = true; newMorph.contents.adjustBounds(); newMorph.setExtent(new Point(350, 250)); create(newMorph); }); - menu.addItem('handle', function () { - create(new HandleMorph()); - }); + menu.addItem('handle', () => create(new HandleMorph())); menu.addLine(); - menu.addItem('string', function () { + menu.addItem('string', () => { newMorph = new StringMorph('Hello, World!'); newMorph.isEditable = true; create(newMorph); }); - menu.addItem('text', function () { + menu.addItem('text', () => { newMorph = new TextMorph( "Ich wei\u00DF nicht, was soll es bedeuten, dass ich so " + "traurig bin, ein M\u00E4rchen aus uralten Zeiten, das " + @@ -12671,25 +12166,19 @@ WorldMorph.prototype.userCreateMorph = function () { ); newMorph.isEditable = true; newMorph.maxWidth = 300; - newMorph.drawNew(); + newMorph.fixLayout(); create(newMorph); }); - menu.addItem('speech bubble', function () { + menu.addItem('speech bubble', () => { newMorph = new SpeechBubbleMorph('Hello, World!'); create(newMorph); }); menu.addLine(); - menu.addItem('gray scale palette', function () { - create(new GrayPaletteMorph()); - }); - menu.addItem('color palette', function () { - create(new ColorPaletteMorph()); - }); - menu.addItem('color picker', function () { - create(new ColorPickerMorph()); - }); + menu.addItem('gray scale palette', () => create(new GrayPaletteMorph())); + menu.addItem('color palette', () => create(new ColorPaletteMorph())); + menu.addItem('color picker', () => create(new ColorPickerMorph())); menu.addLine(); - menu.addItem('sensor demo', function () { + menu.addItem('sensor demo', () => { newMorph = new MouseSensorMorph(); newMorph.setColor(new Color(230, 200, 100)); newMorph.edge = 35; @@ -12699,7 +12188,7 @@ WorldMorph.prototype.userCreateMorph = function () { newMorph.setExtent(new Point(100, 100)); create(newMorph); }); - menu.addItem('animation demo', function () { + menu.addItem('animation demo', () => { var foo, bar, baz, garply, fred; foo = new BouncerMorph(); @@ -12748,15 +12237,11 @@ WorldMorph.prototype.userCreateMorph = function () { create(foo); }); - menu.addItem('pen', function () { - create(new PenMorph()); - }); - if (myself.customMorphs) { + menu.addItem('pen', () => create(new PenMorph())); + if (this.customMorphs) { menu.addLine(); - myself.customMorphs().forEach(function (morph) { - menu.addItem(morph.toString(), function () { - create(morph); - }); + this.customMorphs().forEach(morph => { + menu.addItem(morph.toString(), () => create(morph)); }); } menu.popUpAtHand(this); @@ -12767,13 +12252,11 @@ WorldMorph.prototype.toggleDevMode = function () { }; WorldMorph.prototype.hideAll = function () { - this.children.forEach(function (child) { - child.hide(); - }); + this.children.forEach(child => child.hide()); }; WorldMorph.prototype.showAllHiddens = function () { - this.forAllChildren(function (child) { + this.forAllChildren(child => { if (!child.isVisible) { child.show(); } @@ -12810,33 +12293,20 @@ WorldMorph.prototype.edit = function (aStringOrTextMorph) { if (!isNil(this.lastEditedText)) { this.stopEditing(); } - - var pos = getDocumentPositionOf(this.worldCanvas); - if (!aStringOrTextMorph.isEditable) { return null; } if (this.cursor) { this.cursor.destroy(); } - this.cursor = new CursorMorph(aStringOrTextMorph); + this.cursor = new CursorMorph(aStringOrTextMorph, this.keyboardHandler); + this.keyboardFocus = this.cursor; aStringOrTextMorph.parent.add(this.cursor); - this.keyboardReceiver = this.cursor; - - this.initVirtualKeyboard(); - if (MorphicPreferences.isTouchDevice - && MorphicPreferences.useVirtualKeyboard) { - this.virtualKeyboard.style.top = this.cursor.top() + pos.y + "px"; - this.virtualKeyboard.style.left = this.cursor.left() + pos.x + "px"; - this.virtualKeyboard.focus(); - } - if (MorphicPreferences.useSliderForInput) { if (!aStringOrTextMorph.parentThatIsA(MenuMorph)) { this.slide(aStringOrTextMorph); } } - if (this.lastEditedText !== aStringOrTextMorph) { aStringOrTextMorph.escalateEvent('freshTextEdit', aStringOrTextMorph); } @@ -12867,14 +12337,14 @@ WorldMorph.prototype.slide = function (aStringOrTextMorph) { slider.button.highlightColor.b += 100; slider.button.pressColor = slider.button.color.copy(); slider.button.pressColor.b += 150; - slider.silentSetHeight(MorphicPreferences.scrollBarSize); - slider.silentSetWidth(MorphicPreferences.menuFontSize * 10); - slider.drawNew(); - slider.action = function (num) { + slider.setExtent(new Point( + MorphicPreferences.scrollBarSize * 10, + MorphicPreferences.menuFontSize + )); + slider.action = (num) => { aStringOrTextMorph.changed(); aStringOrTextMorph.text = Math.round(num).toString(); - aStringOrTextMorph.drawNew(); - aStringOrTextMorph.changed(); + aStringOrTextMorph.rerender(); aStringOrTextMorph.escalateEvent( 'reactToSliderEdit', aStringOrTextMorph @@ -12891,17 +12361,11 @@ WorldMorph.prototype.stopEditing = function () { this.cursor.destroy(); this.cursor = null; } - if (this.keyboardReceiver && this.keyboardReceiver.stopEditing) { - this.keyboardReceiver.stopEditing(); - } - this.keyboardReceiver = null; - if (this.virtualKeyboard) { - this.virtualKeyboard.blur(); - document.body.removeChild(this.virtualKeyboard); - this.virtualKeyboard = null; + if (this.keyboardFocus && this.keyboardFocus.stopEditing) { + this.keyboardFocus.stopEditing(); } + this.keyboardFocus = null; this.lastEditedText = null; - this.worldCanvas.focus(); }; WorldMorph.prototype.toggleBlurredShadows = function () { @@ -12915,3 +12379,8 @@ WorldMorph.prototype.togglePreferences = function () { MorphicPreferences = standardSettings; } }; + +WorldMorph.prototype.toggleHolesDisplay = function () { + MorphicPreferences.showHoles = !MorphicPreferences.showHoles; + this.rerender(); +};