From 2b881c592cb840af0d804fddb039d62f8d262bfb Mon Sep 17 00:00:00 2001 From: abe33 Date: Wed, 9 Dec 2015 15:35:41 +0100 Subject: [PATCH 01/12] :fire: old documentation --- lib/minimap-element.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/minimap-element.js b/lib/minimap-element.js index 9b59123b..dd42bdaa 100644 --- a/lib/minimap-element.js +++ b/lib/minimap-element.js @@ -891,10 +891,6 @@ export default class MinimapElement { // ## ## ## ## ## ### ## ## ## // ######## ### ######## ## ## ## ###### - // Internal: - // - // config - An {Object} mapping the config name to observe with the listener - // {Function} to call when the setting was changed. /** * Helper method to register config observers. * From b339e73ebbae0a93279bd002d046b069578cf282 Mon Sep 17 00:00:00 2001 From: abe33 Date: Wed, 9 Dec 2015 16:26:25 +0100 Subject: [PATCH 02/12] Add a CanvasLayer class to handle onscreen/offscreen canvases --- lib/canvas-layer.js | 72 +++++++++++++++++++++++++++ spec/canvas-layer-spec.js | 100 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 lib/canvas-layer.js create mode 100644 spec/canvas-layer-spec.js diff --git a/lib/canvas-layer.js b/lib/canvas-layer.js new file mode 100644 index 00000000..f2db3f56 --- /dev/null +++ b/lib/canvas-layer.js @@ -0,0 +1,72 @@ +'use babel' + +export default class CanvasLayer { + constructor () { + /** + * The onscreen canvas. + * @type {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas') + /** + * The onscreen canvas context. + * @type {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d') + this.canvas.webkitImageSmoothingEnabled = false + this.context.imageSmoothingEnabled = false + + /** + * The offscreen canvas. + * @type {HTMLCanvasElement} + * @access private + */ + this.offscreenCanvas = document.createElement('canvas') + /** + * The offscreen canvas context. + * @type {CanvasRenderingContext2D} + * @access private + */ + this.offscreenContext = this.offscreenCanvas.getContext('2d') + this.offscreenCanvas.webkitImageSmoothingEnabled = false + this.offscreenContext.imageSmoothingEnabled = false + } + + attach (parent) { + if (this.canvas.parentNode) { return } + + parent.appendChild(this.canvas) + } + + setSize (width = 0, height = 0) { + this.canvas.width = width + this.canvas.height = height + this.context.imageSmoothingEnabled = false + this.resetOffscreenSize() + } + + resetOffscreenSize () { + this.offscreenCanvas.width = this.canvas.width + this.offscreenCanvas.height = this.canvas.height + this.offscreenContext.imageSmoothingEnabled = false + } + + copyToOffscreen () { + this.offscreenContext.drawImage(this.canvas, 0, 0) + } + + copyFromOffscreen () { + this.context.drawImage(this.offscreenCanvas, 0, 0) + } + + copyPartFromOffscreen (srcY, destY, height) { + this.context.drawImage( + this.offscreenCanvas, + 0, srcY, this.offscreenCanvas.width, height, + 0, destY, this.offscreenCanvas.width, height + ) + } + + clearCanvas () { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height) + } +} diff --git a/spec/canvas-layer-spec.js b/spec/canvas-layer-spec.js new file mode 100644 index 00000000..415a6a49 --- /dev/null +++ b/spec/canvas-layer-spec.js @@ -0,0 +1,100 @@ +'use babel' + +import CanvasLayer from '../lib/canvas-layer' + +describe('CanvasLayer', () => { + let [layer] = [] + + beforeEach(() => { + layer = new CanvasLayer() + + layer.setSize(100, 300) + }) + + it('has two canvas', () => { + expect(layer.canvas).toBeDefined() + expect(layer.offscreenCanvas).toBeDefined() + }) + + it('has a context for each canvas', () => { + expect(layer.context).toBeDefined() + expect(layer.offscreenContext).toBeDefined() + }) + + it('disables the smoothing for the canvas', () => { + expect(layer.canvas.webkitImageSmoothingEnabled).toBeFalsy() + expect(layer.offscreenCanvas.webkitImageSmoothingEnabled).toBeFalsy() + + expect(layer.context.imageSmoothingEnabled).toBeFalsy() + expect(layer.offscreenContext.imageSmoothingEnabled).toBeFalsy() + }) + + describe('.prototype.attach', () => { + it('attaches the onscreen canvas to the provided element', () => { + let jasmineContent = document.body.querySelector('#jasmine-content') + + layer.attach(jasmineContent) + + expect(jasmineContent.querySelector('canvas')).toExist() + }) + }) + + describe('.prototype.resetOffscreenSize', () => { + it('sets the width of the offscreen canvas to the ', () => { + layer.canvas.width = 500 + layer.canvas.height = 400 + + expect(layer.offscreenCanvas.width).toEqual(100) + expect(layer.offscreenCanvas.height).toEqual(300) + + layer.resetOffscreenSize() + + expect(layer.offscreenCanvas.width).toEqual(500) + expect(layer.offscreenCanvas.height).toEqual(400) + }) + }) + + describe('.prototype.copyToOffscreen', () => { + it('copies the onscreen bitmap onto the offscreen canvas', () => { + spyOn(layer.offscreenContext, 'drawImage') + + layer.copyToOffscreen() + + expect(layer.offscreenContext.drawImage).toHaveBeenCalledWith(layer.canvas, 0, 0) + }) + }) + + describe('.prototype.copyFromOffscreen', () => { + it('copies the offscreen bitmap onto the onscreen canvas', () => { + spyOn(layer.context, 'drawImage') + + layer.copyFromOffscreen() + + expect(layer.context.drawImage).toHaveBeenCalledWith(layer.offscreenCanvas, 0, 0) + }) + }) + + describe('.prototype.copyPartFromOffscren', () => { + it('copies to the onscreen canvas the region that were specified', () => { + spyOn(layer.context, 'drawImage') + + layer.copyPartFromOffscreen(50, 100, 150) + + expect(layer.context.drawImage).toHaveBeenCalledWith( + layer.offscreenCanvas, + 0, 50, 100, 150, + 0, 100, 100, 150 + ) + }) + }) + + describe('.prototype.clearCanvas', () => { + it('clears the whole canvas region', () => { + spyOn(layer.context, 'clearRect') + + layer.clearCanvas() + + expect(layer.context.clearRect).toHaveBeenCalledWith(0, 0, layer.canvas.width, layer.canvas.height) + }) + }) +}) From 4699258287ffd99974e3cd3c9765f03c62f87e78 Mon Sep 17 00:00:00 2001 From: abe33 Date: Wed, 9 Dec 2015 16:53:17 +0100 Subject: [PATCH 03/12] Replace CanvasDrawer's canvas with a canvas layer instance --- lib/canvas-layer.js | 7 +++ lib/minimap-element.js | 20 +++++---- lib/mixins/canvas-drawer.js | 86 ++++++++++++++---------------------- spec/minimap-element-spec.js | 36 +++++++-------- 4 files changed, 68 insertions(+), 81 deletions(-) diff --git a/lib/canvas-layer.js b/lib/canvas-layer.js index f2db3f56..4b27543f 100644 --- a/lib/canvas-layer.js +++ b/lib/canvas-layer.js @@ -44,6 +44,13 @@ export default class CanvasLayer { this.resetOffscreenSize() } + getSize () { + return { + width: this.canvas.width, + height: this.canvas.height + } + } + resetOffscreenSize () { this.offscreenCanvas.width = this.canvas.width this.offscreenCanvas.height = this.canvas.height diff --git a/lib/minimap-element.js b/lib/minimap-element.js index dd42bdaa..29784a29 100644 --- a/lib/minimap-element.js +++ b/lib/minimap-element.js @@ -385,7 +385,7 @@ export default class MinimapElement { this.initializeCanvas() this.shadowRoot = this.createShadowRoot() - this.shadowRoot.appendChild(this.canvas) + this.attachCanvases(this.shadowRoot) this.createVisibleArea() this.createControls() @@ -396,7 +396,7 @@ export default class MinimapElement { } })) - this.subscriptions.add(this.subscribeTo(this.canvas, { + this.subscriptions.add(this.subscribeTo(this.getFrontCanvas(), { 'mousedown': (e) => { this.mousePressedOverCanvas(e) } })) } @@ -514,7 +514,7 @@ export default class MinimapElement { this.quickSettingsElement = null }) - let {top, left, right} = this.canvas.getBoundingClientRect() + let {top, left, right} = this.getFrontCanvas().getBoundingClientRect() this.quickSettingsElement.style.top = top + 'px' this.quickSettingsElement.attach() @@ -713,10 +713,11 @@ export default class MinimapElement { if (!(this.attached && this.isVisible() && this.minimap)) { return } let minimap = this.minimap minimap.enableCache() + let canvas = this.getFrontCanvas() let visibleAreaLeft = minimap.getTextEditorScaledScrollLeft() let visibleAreaTop = minimap.getTextEditorScaledScrollTop() - minimap.getScrollTop() - let visibleWidth = Math.min(this.canvas.width / devicePixelRatio, this.width) + let visibleWidth = Math.min(canvas.width / devicePixelRatio, this.width) if (this.adjustToSoftWrap && this.flexBasis) { this.style.flexBasis = this.flexBasis + 'px' @@ -749,9 +750,9 @@ export default class MinimapElement { } if (SPEC_MODE) { - this.applyStyles(this.canvas, {top: canvasTop + 'px'}) + this.applyStyles(canvas, {top: canvasTop + 'px'}) } else { - this.applyStyles(this.canvas, {transform: canvasTransform}) + this.applyStyles(canvas, {transform: canvasTransform}) } if (this.minimapScrollIndicator && minimap.canScroll() && !this.scrollIndicator) { @@ -876,9 +877,10 @@ export default class MinimapElement { delete this.flexBasis } - if (canvasWidth !== this.canvas.width || this.height !== this.canvas.height) { - this.canvas.width = canvasWidth * devicePixelRatio - this.canvas.height = (this.height + this.minimap.getLineHeight()) * devicePixelRatio + let canvas = this.getFrontCanvas() + if (canvasWidth !== canvas.width || this.height !== canvas.height) { + canvas.width = canvasWidth * devicePixelRatio + canvas.height = (this.height + this.minimap.getLineHeight()) * devicePixelRatio } } } diff --git a/lib/mixins/canvas-drawer.js b/lib/mixins/canvas-drawer.js index dd1f91ee..119de462 100644 --- a/lib/mixins/canvas-drawer.js +++ b/lib/mixins/canvas-drawer.js @@ -2,6 +2,7 @@ import _ from 'underscore-plus' import Mixin from 'mixto' +import CanvasLayer from '../canvas-layer' /** * The `CanvasDrawer` mixin is responsible for the rendering of a `Minimap` @@ -16,32 +17,10 @@ export default class CanvasDrawer extends Mixin { */ initializeCanvas () { /** - * The onscreen canvas. - * @type {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas') - /** - * The onscreen canvas context. - * @type {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d') - this.canvas.webkitImageSmoothingEnabled = false - this.context.imageSmoothingEnabled = false - - /** - * The offscreen canvas. - * @type {HTMLCanvasElement} - * @access private + * The main canvas layer. + * @type {CanvasLayer} */ - this.offscreenCanvas = document.createElement('canvas') - /** - * The offscreen canvas context. - * @type {CanvasRenderingContext2D} - * @access private - */ - this.offscreenContext = this.offscreenCanvas.getContext('2d') - this.offscreenCanvas.webkitImageSmoothingEnabled = false - this.offscreenContext.imageSmoothingEnabled = false + this.canvasLayer = new CanvasLayer() if (!this.pendingChanges) { /** @@ -53,6 +32,21 @@ export default class CanvasDrawer extends Mixin { } } + /** + * Returns the uppermost canvas in the MinimapElement. + * @return {HTMLCanvasElement} the html canvas element + */ + getFrontCanvas () { return this.canvasLayer.canvas } + + /** + * Attaches the canvases into the specified container + * @param {HTMLElement} parent the canvases' container + * @access private + */ + attachCanvases (parent) { + this.canvasLayer.attach(parent) + } + /** * Performs an update of the rendered `Minimap` based on the changes * registered in the instance. @@ -61,23 +55,29 @@ export default class CanvasDrawer extends Mixin { let firstRow = this.minimap.getFirstVisibleScreenRow() let lastRow = this.minimap.getLastVisibleScreenRow() let intactRanges = this.computeIntactRanges(firstRow, lastRow) - let context = this.context + let lineHeight = this.minimap.getLineHeight() * devicePixelRatio + let context = this.canvasLayer.context - context.clearRect(0, 0, this.canvas.width, this.canvas.height) + this.canvasLayer.clearCanvas() if (intactRanges.length === 0) { this.drawLines(context, firstRow, lastRow, 0) } else { for (let j = 0, len = intactRanges.length; j < len; j++) { let intact = intactRanges[j] - this.copyBitmapPart(context, this.offscreenCanvas, intact.offscreenRow, intact.start - firstRow, intact.end - intact.start) + + this.canvasLayer.copyPartFromOffscreen( + intact.offscreenRow * lineHeight, + (intact.start - firstRow) * lineHeight, + (intact.end - intact.start) * lineHeight + ) } this.fillGapsBetweenIntactRanges(context, intactRanges, firstRow, lastRow) } - this.offscreenCanvas.width = this.canvas.width - this.offscreenCanvas.height = this.canvas.height - this.offscreenContext.drawImage(this.canvas, 0, 0) + this.canvasLayer.resetOffscreenSize() + this.canvasLayer.copyToOffscreen() + /** * The first row in the last render of the offscreen canvas. * @type {number} @@ -195,10 +195,9 @@ export default class CanvasDrawer extends Mixin { let lineHeight = this.minimap.getLineHeight() * devicePixelRatio let charHeight = this.minimap.getCharHeight() * devicePixelRatio let charWidth = this.minimap.getCharWidth() * devicePixelRatio - let canvasWidth = this.canvas.width - let canvasHeight = this.canvas.height let displayCodeHighlights = this.displayCodeHighlights let decorations = this.minimap.decorationsByTypeThenRows(firstRow, lastRow) + let {width: canvasWidth, height: canvasHeight} = this.canvasLayer.getSize() let line = lines[0] let invisibleRegExp = this.getInvisibleRegExp(line) @@ -459,27 +458,6 @@ export default class CanvasDrawer extends Mixin { } } - /** - * Copy a part of the offscreen bitmap into the onscreen one to - * reduce the amount of rendered lines during scroll. - * - * @param {CanvasRenderingContext2D} context [description] - * @param {HTMLCanvasElement} bitmapCanvas [description] - * @param {number} srcRow the source row of the region to copy - * @param {number} destRow the destination row - * @param {number} rowCount the amount of lines to copy - * @access private - */ - copyBitmapPart (context, bitmapCanvas, srcRow, destRow, rowCount) { - let lineHeight = this.minimap.getLineHeight() * devicePixelRatio - - context.drawImage( - bitmapCanvas, - 0, srcRow * lineHeight, bitmapCanvas.width, rowCount * lineHeight, - 0, destRow * lineHeight, bitmapCanvas.width, rowCount * lineHeight - ) - } - // ######## ### ## ## ###### ######## ###### // ## ## ## ## ### ## ## ## ## ## ## // ## ## ## ## #### ## ## ## ## diff --git a/spec/minimap-element-spec.js b/spec/minimap-element-spec.js index a4d36b52..844434b9 100644 --- a/spec/minimap-element-spec.js +++ b/spec/minimap-element-spec.js @@ -366,8 +366,8 @@ describe('MinimapElement', () => { describe('when the editor visibility change', () => { it('does not modify the size of the canvas', () => { - let canvasWidth = minimapElement.canvas.width - let canvasHeight = minimapElement.canvas.height + let canvasWidth = minimapElement.getFrontCanvas().width + let canvasHeight = minimapElement.getFrontCanvas().height editorElement.style.display = 'none' minimapElement.measureHeightAndWidth() @@ -376,8 +376,8 @@ describe('MinimapElement', () => { runs(() => { nextAnimationFrame() - expect(minimapElement.canvas.width).toEqual(canvasWidth) - expect(minimapElement.canvas.height).toEqual(canvasHeight) + expect(minimapElement.getFrontCanvas().width).toEqual(canvasWidth) + expect(minimapElement.getFrontCanvas().height).toEqual(canvasHeight) }) }) @@ -436,7 +436,7 @@ describe('MinimapElement', () => { let [canvas, visibleArea, originalLeft, maxScroll] = [] beforeEach(() => { - canvas = minimapElement.canvas + canvas = minimapElement.getFrontCanvas() visibleArea = minimapElement.visibleArea originalLeft = visibleArea.getBoundingClientRect().left maxScroll = minimap.getTextEditorMaxScrollTop() @@ -530,13 +530,13 @@ describe('MinimapElement', () => { atom.config.set('minimap.scrollAnimation', false) - canvas = minimapElement.canvas + canvas = minimapElement.getFrontCanvas() mousedown(canvas) }) it('scrolls the editor to the line below the mouse', () => { let scrollTop - let {top, left, width, height} = minimapElement.canvas.getBoundingClientRect() + let {top, left, width, height} = minimapElement.getFrontCanvas().getBoundingClientRect() let middle = top + height / 2 // Should be 400 on stable and 480 on beta. @@ -556,7 +556,7 @@ describe('MinimapElement', () => { atom.config.set('minimap.scrollAnimation', true) atom.config.set('minimap.scrollAnimationDuration', 300) - canvas = minimapElement.canvas + canvas = minimapElement.getFrontCanvas() mousedown(canvas) waitsFor(() => { return nextAnimationFrame !== noAnimationFrame }) @@ -795,7 +795,7 @@ describe('MinimapElement', () => { atom.config.set('minimap.scrollAnimation', false) - canvas = minimapElement.canvas + canvas = minimapElement.getFrontCanvas() mousedown(canvas) }) @@ -991,7 +991,7 @@ describe('MinimapElement', () => { }) it('adjusts the width of the minimap canvas', () => { - expect(minimapElement.canvas.width / devicePixelRatio).toEqual(4) + expect(minimapElement.getFrontCanvas().width / devicePixelRatio).toEqual(4) }) it('offsets the minimap by the difference', () => { @@ -1006,7 +1006,7 @@ describe('MinimapElement', () => { waitsFor(() => { return nextAnimationFrame !== noAnimationFrame }) runs(() => { nextAnimationFrame() - expect(minimapElement.canvas.width / devicePixelRatio).toEqual(4) + expect(minimapElement.getFrontCanvas().width / devicePixelRatio).toEqual(4) }) }) }) @@ -1225,7 +1225,7 @@ describe('MinimapElement', () => { }) it('positions the quick settings view next to the minimap', () => { - let minimapBounds = minimapElement.canvas.getBoundingClientRect() + let minimapBounds = minimapElement.getFrontCanvas().getBoundingClientRect() let settingsBounds = quickSettingsElement.getBoundingClientRect() expect(realOffsetTop(quickSettingsElement)).toBeCloseTo(minimapBounds.top, 0) @@ -1252,7 +1252,7 @@ describe('MinimapElement', () => { }) it('positions the quick settings view next to the minimap', () => { - let minimapBounds = minimapElement.canvas.getBoundingClientRect() + let minimapBounds = minimapElement.getFrontCanvas().getBoundingClientRect() let settingsBounds = quickSettingsElement.getBoundingClientRect() expect(realOffsetTop(quickSettingsElement)).toBeCloseTo(minimapBounds.top, 0) @@ -1282,12 +1282,12 @@ describe('MinimapElement', () => { }) it('adjusts the size of the control div to fit in the minimap', () => { - expect(controls.clientWidth).toEqual(minimapElement.canvas.clientWidth / devicePixelRatio) + expect(controls.clientWidth).toEqual(minimapElement.getFrontCanvas().clientWidth / devicePixelRatio) }) it('positions the controls div over the canvas', () => { let controlsRect = controls.getBoundingClientRect() - let canvasRect = minimapElement.canvas.getBoundingClientRect() + let canvasRect = minimapElement.getFrontCanvas().getBoundingClientRect() expect(controlsRect.left).toEqual(canvasRect.left) expect(controlsRect.right).toEqual(canvasRect.right) }) @@ -1298,12 +1298,12 @@ describe('MinimapElement', () => { }) it('adjusts the size of the control div to fit in the minimap', () => { - expect(controls.clientWidth).toEqual(minimapElement.canvas.clientWidth / devicePixelRatio) + expect(controls.clientWidth).toEqual(minimapElement.getFrontCanvas().clientWidth / devicePixelRatio) }) it('positions the controls div over the canvas', () => { let controlsRect = controls.getBoundingClientRect() - let canvasRect = minimapElement.canvas.getBoundingClientRect() + let canvasRect = minimapElement.getFrontCanvas().getBoundingClientRect() expect(controlsRect.left).toEqual(canvasRect.left) expect(controlsRect.right).toEqual(canvasRect.right) }) @@ -1324,7 +1324,7 @@ describe('MinimapElement', () => { }) it('positions the quick settings view next to the minimap', () => { - let minimapBounds = minimapElement.canvas.getBoundingClientRect() + let minimapBounds = minimapElement.getFrontCanvas().getBoundingClientRect() let settingsBounds = quickSettingsElement.getBoundingClientRect() expect(realOffsetTop(quickSettingsElement)).toBeCloseTo(minimapBounds.top, 0) From c02d1dda3defb66ce22d6c87ac4794b37bfac675 Mon Sep 17 00:00:00 2001 From: abe33 Date: Wed, 9 Dec 2015 16:57:32 +0100 Subject: [PATCH 04/12] Use dedicated method to resize canvases --- lib/minimap-element.js | 6 ++++-- lib/mixins/canvas-drawer.js | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/minimap-element.js b/lib/minimap-element.js index 29784a29..2e348a4e 100644 --- a/lib/minimap-element.js +++ b/lib/minimap-element.js @@ -879,8 +879,10 @@ export default class MinimapElement { let canvas = this.getFrontCanvas() if (canvasWidth !== canvas.width || this.height !== canvas.height) { - canvas.width = canvasWidth * devicePixelRatio - canvas.height = (this.height + this.minimap.getLineHeight()) * devicePixelRatio + this.setCanvasesSize( + canvasWidth * devicePixelRatio, + (this.height + this.minimap.getLineHeight()) * devicePixelRatio + ) } } } diff --git a/lib/mixins/canvas-drawer.js b/lib/mixins/canvas-drawer.js index 119de462..a21c0d63 100644 --- a/lib/mixins/canvas-drawer.js +++ b/lib/mixins/canvas-drawer.js @@ -47,6 +47,10 @@ export default class CanvasDrawer extends Mixin { this.canvasLayer.attach(parent) } + setCanvasesSize (width, height) { + this.canvasLayer.setSize(width, height) + } + /** * Performs an update of the rendered `Minimap` based on the changes * registered in the instance. From 7c291f23ff837caefdbbdac69a5ae90a689e5ba4 Mon Sep 17 00:00:00 2001 From: abe33 Date: Wed, 9 Dec 2015 23:42:55 +0100 Subject: [PATCH 05/12] Make decorations dispatch their own change events --- lib/minimap-element.js | 5 +++++ lib/mixins/decoration-management.js | 22 +++++++++++++++++++++- spec/minimap-spec.js | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/minimap-element.js b/lib/minimap-element.js index 2e348a4e..1c98e5a8 100644 --- a/lib/minimap-element.js +++ b/lib/minimap-element.js @@ -641,6 +641,11 @@ export default class MinimapElement { this.requestUpdate() })) + this.subscriptions.add(this.minimap.onDidChangeDecorationRange((change) => { + this.pendingDecorationChanges.push(change) + this.requestUpdate() + })) + this.setStandAlone(this.minimap.isStandAlone()) if (this.width != null && this.height != null) { diff --git a/lib/mixins/decoration-management.js b/lib/mixins/decoration-management.js index 51ebc6e9..faab2d6b 100644 --- a/lib/mixins/decoration-management.js +++ b/lib/mixins/decoration-management.js @@ -133,6 +133,26 @@ export default class DecorationManagement extends Mixin { return this.emitter.on('did-change-decoration', callback) } + /** + * Registers an event listener to the `did-change-decoration-range` event. + * + * This event is triggered when the marker range targeted by the decoration + * was changed. + * + * @param {function(event:Object):void} callback a function to call when the + * event is triggered. + * the callback will be called + * with an event object with + * the following properties: + * - marker: the marker object that was decorated + * - decoration: the decoration object that was created + * @return {Disposable} a disposable to stop listening to the event + */ + onDidChangeDecorationRange (callback) { + return this.emitter.on('did-change-decoration-range', callback) + } + + /** * Registers an event listener to the `did-update-decoration` event. * @@ -446,7 +466,7 @@ export default class DecorationManagement extends Mixin { screenDelta: screenDelta } - this.emitChanges(changeEvent) + this.emitter.emit('did-change-decoration-range', changeEvent) } /** diff --git a/spec/minimap-spec.js b/spec/minimap-spec.js index ed58ae22..550f18d6 100644 --- a/spec/minimap-spec.js +++ b/spec/minimap-spec.js @@ -269,7 +269,7 @@ describe('Minimap', () => { editor.setText(largeSample) changeSpy = jasmine.createSpy('didChange') - minimap.onDidChange(changeSpy) + minimap.onDidChangeDecorationRange(changeSpy) marker = minimap.markBufferRange([[0,6], [1,11]]) decoration = minimap.decorateMarker(marker, {type: 'highlight', class: 'dummy'}) From 83d5746ba15256f5c9089ce6f404c1fac387dc33 Mon Sep 17 00:00:00 2001 From: abe33 Date: Wed, 9 Dec 2015 23:48:24 +0100 Subject: [PATCH 06/12] Separate decorations and tokens rendering between three layers The main point of this optimisation is to avoid rendering the line tokens whenever a decoration is added, removed or changed. The various profiling I made in the past weeks all led to the `line.tokens` getter being too costly. So now, decorations are rendered on two layers (back and front) and line tokens on the last one. --- lib/minimap-element.js | 8 +- lib/mixins/canvas-drawer.js | 269 ++++++++++++++++++++++++----------- spec/minimap-element-spec.js | 4 +- 3 files changed, 192 insertions(+), 89 deletions(-) diff --git a/lib/minimap-element.js b/lib/minimap-element.js index 1c98e5a8..96d898c3 100644 --- a/lib/minimap-element.js +++ b/lib/minimap-element.js @@ -755,9 +755,13 @@ export default class MinimapElement { } if (SPEC_MODE) { - this.applyStyles(canvas, {top: canvasTop + 'px'}) + this.applyStyles(this.backLayer.canvas, {top: canvasTop + 'px'}) + this.applyStyles(this.tokensLayer.canvas, {top: canvasTop + 'px'}) + this.applyStyles(this.frontLayer.canvas, {top: canvasTop + 'px'}) } else { - this.applyStyles(canvas, {transform: canvasTransform}) + this.applyStyles(this.backLayer.canvas, {transform: canvasTransform}) + this.applyStyles(this.tokensLayer.canvas, {transform: canvasTransform}) + this.applyStyles(this.frontLayer.canvas, {transform: canvasTransform}) } if (this.minimapScrollIndicator && minimap.canScroll() && !this.scrollIndicator) { diff --git a/lib/mixins/canvas-drawer.js b/lib/mixins/canvas-drawer.js index a21c0d63..ae729c73 100644 --- a/lib/mixins/canvas-drawer.js +++ b/lib/mixins/canvas-drawer.js @@ -17,26 +17,45 @@ export default class CanvasDrawer extends Mixin { */ initializeCanvas () { /** - * The main canvas layer. + * The main canvas layer where lines are rendered. * @type {CanvasLayer} */ - this.canvasLayer = new CanvasLayer() + this.tokensLayer = new CanvasLayer() + /** + * The canvas layer for decorations below the text. + * @type {CanvasLayer} + */ + this.backLayer = new CanvasLayer() + /** + * The canvas layer for decorations above the text. + * @type {CanvasLayer} + */ + this.frontLayer = new CanvasLayer() if (!this.pendingChanges) { /** - * Stores the changes from the text editor and the minimap. + * Stores the changes from the text editor. * @type {Array} * @access private */ this.pendingChanges = [] } + + if (!this.pendingDecorationChanges) { + /** + * Stores the changes from the minimap decorations. + * @type {Array} + * @access private + */ + this.pendingDecorationChanges = [] + } } /** * Returns the uppermost canvas in the MinimapElement. * @return {HTMLCanvasElement} the html canvas element */ - getFrontCanvas () { return this.canvasLayer.canvas } + getFrontCanvas () { return this.frontLayer.canvas } /** * Attaches the canvases into the specified container @@ -44,11 +63,15 @@ export default class CanvasDrawer extends Mixin { * @access private */ attachCanvases (parent) { - this.canvasLayer.attach(parent) + this.backLayer.attach(parent) + this.tokensLayer.attach(parent) + this.frontLayer.attach(parent) } setCanvasesSize (width, height) { - this.canvasLayer.setSize(width, height) + this.backLayer.setSize(width, height) + this.tokensLayer.setSize(width, height) + this.frontLayer.setSize(width, height) } /** @@ -58,42 +81,86 @@ export default class CanvasDrawer extends Mixin { updateCanvas () { let firstRow = this.minimap.getFirstVisibleScreenRow() let lastRow = this.minimap.getLastVisibleScreenRow() - let intactRanges = this.computeIntactRanges(firstRow, lastRow) + + this.updateTokensLayer(firstRow, lastRow) + this.updateDecorationsLayers(firstRow, lastRow) + + this.pendingChanges = [] + this.pendingDecorationChanges = [] + + /** + * The first row in the last render of the offscreen canvas. + * @type {number} + * @access private + */ + this.offscreenFirstRow = firstRow + /** + * The last row in the last render of the offscreen canvas. + * @type {number} + * @access private + */ + this.offscreenLastRow = lastRow + } + + updateTokensLayer (firstRow, lastRow) { + let intactRanges = this.computeIntactRanges(firstRow, lastRow, this.pendingChanges) + + this.redrawRangesOnLayer(this.tokensLayer, intactRanges, firstRow, lastRow, this.drawLines) + } + + updateDecorationsLayers (firstRow, lastRow) { + let intactRanges = this.computeIntactRanges(firstRow, lastRow, this.pendingChanges.concat(this.pendingDecorationChanges)) + + this.redrawRangesOnLayer(this.backLayer, intactRanges, firstRow, lastRow, this.drawBackDecorationsForLines) + this.redrawRangesOnLayer(this.frontLayer, intactRanges, firstRow, lastRow, this.drawFrontDecorationsForLines) + } + + redrawRangesOnLayer (layer, intactRanges, firstRow, lastRow, method) { let lineHeight = this.minimap.getLineHeight() * devicePixelRatio - let context = this.canvasLayer.context - this.canvasLayer.clearCanvas() + layer.clearCanvas() if (intactRanges.length === 0) { - this.drawLines(context, firstRow, lastRow, 0) + method.call(this, firstRow, lastRow, 0) } else { for (let j = 0, len = intactRanges.length; j < len; j++) { let intact = intactRanges[j] - this.canvasLayer.copyPartFromOffscreen( + layer.copyPartFromOffscreen( intact.offscreenRow * lineHeight, (intact.start - firstRow) * lineHeight, (intact.end - intact.start) * lineHeight ) } - this.fillGapsBetweenIntactRanges(context, intactRanges, firstRow, lastRow) + this.drawLinesForRanges(method, intactRanges, firstRow, lastRow) } - this.canvasLayer.resetOffscreenSize() - this.canvasLayer.copyToOffscreen() + layer.resetOffscreenSize() + layer.copyToOffscreen() + } - /** - * The first row in the last render of the offscreen canvas. - * @type {number} - * @access private - */ - this.offscreenFirstRow = firstRow - /** - * The last row in the last render of the offscreen canvas. - * @type {number} - * @access private - */ - this.offscreenLastRow = lastRow + /** + * Renders the lines between the intact ranges when an update has pending + * changes. + * + * @param {CanvasRenderingContext2D} context the target canvas context + * @param {Array} intactRanges the intact ranges in the minimap + * @param {number} firstRow the first row of the rendered region + * @param {number} lastRow the last row of the rendered region + * @access private + */ + drawLinesForRanges (method, ranges, firstRow, lastRow) { + let currentRow = firstRow + for (let i = 0, len = ranges.length; i < len; i++) { + let range = ranges[i] + + method.call(this, currentRow, range.start - 1, currentRow - firstRow) + + currentRow = range.end + } + if (currentRow <= lastRow) { + method.call(this, currentRow, lastRow, currentRow - firstRow) + } } // ###### ####### ## ####### ######## ###### @@ -178,6 +245,65 @@ export default class CanvasDrawer extends Mixin { // ## ## ## ## ## ## ## ## ## // ######## ## ## ## ## ### ### + drawBackDecorationsForLines (firstRow, lastRow, offsetRow) { + if (firstRow > lastRow) { return } + + let lineHeight = this.minimap.getLineHeight() * devicePixelRatio + let charHeight = this.minimap.getCharHeight() * devicePixelRatio + let charWidth = this.minimap.getCharWidth() * devicePixelRatio + let decorations = this.minimap.decorationsByTypeThenRows(firstRow, lastRow) + let {width: canvasWidth, height: canvasHeight} = this.tokensLayer.getSize() + let renderData = { + context: this.backLayer.context, + canvasWidth: canvasWidth, + canvasHeight: canvasHeight, + lineHeight: lineHeight, + charWidth: charWidth, + charHeight: charHeight + } + + for (let screenRow = firstRow; screenRow <= lastRow; screenRow++) { + renderData.row = offsetRow + (screenRow - firstRow) + renderData.yRow = renderData.row * lineHeight + renderData.screenRow = screenRow + + this.drawDecorations(screenRow, decorations, 'line', renderData, this.drawLineDecoration) + + this.drawDecorations(screenRow, decorations, 'highlight-under', renderData, this.drawHighlightDecoration) + } + + this.backLayer.context.fill() + } + + drawFrontDecorationsForLines (firstRow, lastRow, offsetRow) { + if (firstRow > lastRow) { return } + + let lineHeight = this.minimap.getLineHeight() * devicePixelRatio + let charHeight = this.minimap.getCharHeight() * devicePixelRatio + let charWidth = this.minimap.getCharWidth() * devicePixelRatio + let decorations = this.minimap.decorationsByTypeThenRows(firstRow, lastRow) + let {width: canvasWidth, height: canvasHeight} = this.tokensLayer.getSize() + let renderData = { + context: this.frontLayer.context, + canvasWidth: canvasWidth, + canvasHeight: canvasHeight, + lineHeight: lineHeight, + charWidth: charWidth, + charHeight: charHeight + } + + for (let screenRow = firstRow; screenRow <= lastRow; screenRow++) { + renderData.row = offsetRow + (screenRow - firstRow) + renderData.yRow = renderData.row * lineHeight + renderData.screenRow = screenRow + + this.drawDecorations(screenRow, decorations, 'highlight-over', renderData, this.drawHighlightDecoration) + + this.drawDecorations(screenRow, decorations, 'highlight-outline', renderData, this.drawHighlightOutlineDecoration) + } + + renderData.context.fill() + } /** * Draws lines on the passed-in `context`. * @@ -192,7 +318,7 @@ export default class CanvasDrawer extends Mixin { * rendering them * @access private */ - drawLines (context, firstRow, lastRow, offsetRow) { + drawLines (firstRow, lastRow, offsetRow) { if (firstRow > lastRow) { return } let lines = this.getTextEditor().tokenizedLinesForScreenRows(firstRow, lastRow) @@ -200,33 +326,17 @@ export default class CanvasDrawer extends Mixin { let charHeight = this.minimap.getCharHeight() * devicePixelRatio let charWidth = this.minimap.getCharWidth() * devicePixelRatio let displayCodeHighlights = this.displayCodeHighlights - let decorations = this.minimap.decorationsByTypeThenRows(firstRow, lastRow) - let {width: canvasWidth, height: canvasHeight} = this.canvasLayer.getSize() + let context = this.tokensLayer.context + let {width: canvasWidth, height: canvasHeight} = this.tokensLayer.getSize() let line = lines[0] let invisibleRegExp = this.getInvisibleRegExp(line) - let renderData = { - context: context, - canvasWidth: canvasWidth, - canvasHeight: canvasHeight, - lineHeight: lineHeight, - charWidth: charWidth, - charHeight: charHeight - } - for (let i = 0, len = lines.length; i < len; i++) { line = lines[i] let screenRow = firstRow + i let x = 0 - - renderData.row = offsetRow + i - renderData.yRow = renderData.row * lineHeight - renderData.screenRow = screenRow - - this.drawDecorations(screenRow, decorations, 'line', renderData, this.drawLineDecoration) - - this.drawDecorations(screenRow, decorations, 'highlight-under', renderData, this.drawHighlightDecoration) + let yRow = (offsetRow + i) * lineHeight if ((line != null ? line.tokens : void 0) != null) { let tokens = line.tokens @@ -240,7 +350,7 @@ export default class CanvasDrawer extends Mixin { if (invisibleRegExp != null) { value = value.replace(invisibleRegExp, ' ') } - x = this.drawToken(context, value, color, x, renderData.yRow, charWidth, charHeight) + x = this.drawToken(context, value, color, x, yRow, charWidth, charHeight) } else { x += w * charWidth } @@ -248,10 +358,6 @@ export default class CanvasDrawer extends Mixin { if (x > canvasWidth) { break } } } - - this.drawDecorations(screenRow, decorations, 'highlight-over', renderData, this.drawHighlightDecoration) - - this.drawDecorations(screenRow, decorations, 'highlight-outline', renderData, this.drawHighlightOutlineDecoration) } context.fill() @@ -470,30 +576,6 @@ export default class CanvasDrawer extends Mixin { // ## ## ## ## ## ### ## ## ## ## ## // ## ## ## ## ## ## ###### ######## ###### - /** - * Renders the lines between the intact ranges when an update has pending - * changes. - * - * @param {CanvasRenderingContext2D} context the target canvas context - * @param {Array} intactRanges the intact ranges in the minimap - * @param {number} firstRow the first row of the rendered region - * @param {number} lastRow the last row of the rendered region - * @access private - */ - fillGapsBetweenIntactRanges (context, intactRanges, firstRow, lastRow) { - let currentRow = firstRow - for (let i = 0, len = intactRanges.length; i < len; i++) { - let intact = intactRanges[i] - - this.drawLines(context, currentRow, intact.start - 1, currentRow - firstRow) - - currentRow = intact.end - } - if (currentRow <= lastRow) { - this.drawLines(context, currentRow, lastRow, currentRow - firstRow) - } - } - /** * Computes the ranges that are not affected by the current pending changes. * @@ -502,11 +584,12 @@ export default class CanvasDrawer extends Mixin { * @return {Array} the intact ranges in the rendered region * @access private */ - computeIntactRanges (firstRow, lastRow) { + computeIntactRanges (firstRow, lastRow, changes) { if ((this.offscreenFirstRow == null) && (this.offscreenLastRow == null)) { return [] } + // At first, the whole range is considered intact let intactRanges = [ { start: this.offscreenFirstRow, @@ -515,8 +598,6 @@ export default class CanvasDrawer extends Mixin { } ] - let changes = this.pendingChanges - for (let i = 0, len = changes.length; i < len; i++) { let change = changes[i] let newIntactRanges = [] @@ -525,14 +606,20 @@ export default class CanvasDrawer extends Mixin { let range = intactRanges[j] if (change.end < range.start && change.screenDelta !== 0) { + // The change is above of the range and lines are either + // added or removed newIntactRanges.push({ start: range.start + change.screenDelta, end: range.end + change.screenDelta, offscreenRow: range.offscreenRow }) } else if (change.end < range.start || change.start > range.end) { + // The change is outside the range but didn't added + // or removed lines newIntactRanges.push(range) } else { + // The change is within the range, there's one intact range + // from the range start to the change start if (change.start > range.start) { newIntactRanges.push({ start: range.start, @@ -540,19 +627,31 @@ export default class CanvasDrawer extends Mixin { offscreenRow: range.offscreenRow }) } - if (change.end < range.end && change.bufferDelta !== 0) { - newIntactRanges.push({ - start: change.end + change.screenDelta + 1, - end: range.end + change.screenDelta, - offscreenRow: range.offscreenRow + change.end + 1 - range.start - }) + if (change.end < range.end) { + // The change ends within the range + if (change.bufferDelta !== 0) { + // Lines are added or removed, the intact range starts in the + // next line after the change end plus the screen delta + newIntactRanges.push({ + start: change.end + change.screenDelta + 1, + end: range.end + change.screenDelta, + offscreenRow: range.offscreenRow + change.end + 1 - range.start + }) + } else { + // No lines are added, the intact range starts on the line after + // the change end + newIntactRanges.push({ + start: change.end + 1, + end: range.end, + offscreenRow: range.offscreenRow + change.end + 1 - range.start + }) + } } } } intactRanges = newIntactRanges } - this.pendingChanges = [] return this.truncateIntactRanges(intactRanges, firstRow, lastRow) } diff --git a/spec/minimap-element-spec.js b/spec/minimap-element-spec.js index 844434b9..3ca5b26d 100644 --- a/spec/minimap-element-spec.js +++ b/spec/minimap-element-spec.js @@ -358,8 +358,8 @@ describe('MinimapElement', () => { nextAnimationFrame() expect(minimapElement.drawLines).toHaveBeenCalled() - expect(minimapElement.drawLines.argsForCall[0][1]).toEqual(100) - expect(minimapElement.drawLines.argsForCall[0][2]).toEqual(101) + expect(minimapElement.drawLines.argsForCall[0][0]).toEqual(100) + expect(minimapElement.drawLines.argsForCall[0][1]).toEqual(101) }) }) }) From 3db1bf3ffd8e3519a8dff2345d0d48507b3b6871 Mon Sep 17 00:00:00 2001 From: abe33 Date: Thu, 10 Dec 2015 00:09:24 +0100 Subject: [PATCH 07/12] Fix linter issues --- lib/canvas-layer.js | 5 ++++- lib/mixins/decoration-management.js | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/canvas-layer.js b/lib/canvas-layer.js index 4b27543f..ceddd23e 100644 --- a/lib/canvas-layer.js +++ b/lib/canvas-layer.js @@ -1,7 +1,10 @@ 'use babel' +/** + * @access private + */ export default class CanvasLayer { - constructor () { + constructor () { /** * The onscreen canvas. * @type {HTMLCanvasElement} diff --git a/lib/mixins/decoration-management.js b/lib/mixins/decoration-management.js index faab2d6b..681e4267 100644 --- a/lib/mixins/decoration-management.js +++ b/lib/mixins/decoration-management.js @@ -152,7 +152,6 @@ export default class DecorationManagement extends Mixin { return this.emitter.on('did-change-decoration-range', callback) } - /** * Registers an event listener to the `did-update-decoration` event. * From 8a3305d87bce2b6d5ccc5ecde1461234a92cb91e Mon Sep 17 00:00:00 2001 From: abe33 Date: Thu, 10 Dec 2015 00:09:38 +0100 Subject: [PATCH 08/12] :memo: Add documentation on new canvas drawer methods --- lib/mixins/canvas-drawer.js | 71 ++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/lib/mixins/canvas-drawer.js b/lib/mixins/canvas-drawer.js index ae729c73..0ca1272f 100644 --- a/lib/mixins/canvas-drawer.js +++ b/lib/mixins/canvas-drawer.js @@ -53,12 +53,14 @@ export default class CanvasDrawer extends Mixin { /** * Returns the uppermost canvas in the MinimapElement. + * * @return {HTMLCanvasElement} the html canvas element */ getFrontCanvas () { return this.frontLayer.canvas } /** - * Attaches the canvases into the specified container + * Attaches the canvases into the specified container. + * * @param {HTMLElement} parent the canvases' container * @access private */ @@ -68,6 +70,13 @@ export default class CanvasDrawer extends Mixin { this.frontLayer.attach(parent) } + /** + * Changes the size of all the canvas layers at once. + * + * @param {number} width the new width for the three canvases + * @param {number} height the new height for the three canvases + * @access private + */ setCanvasesSize (width, height) { this.backLayer.setSize(width, height) this.tokensLayer.setSize(width, height) @@ -102,12 +111,27 @@ export default class CanvasDrawer extends Mixin { this.offscreenLastRow = lastRow } - updateTokensLayer (firstRow, lastRow) { + /** + * Performs an update of the tokens layer using the pending changes array. + * + * @param {number} firstRow firstRow the first row of the range to update + * @param {number} lastRow lastRow the last row of the range to update + * @access private + */ + updateTokensLayer (firstRow, lastRow) { let intactRanges = this.computeIntactRanges(firstRow, lastRow, this.pendingChanges) this.redrawRangesOnLayer(this.tokensLayer, intactRanges, firstRow, lastRow, this.drawLines) } + /** + * Performs an update of the decoration layers using the pending changes + * and the pending decoration changes arrays. + * + * @param {number} firstRow firstRow the first row of the range to update + * @param {number} lastRow lastRow the last row of the range to update + * @access private + */ updateDecorationsLayers (firstRow, lastRow) { let intactRanges = this.computeIntactRanges(firstRow, lastRow, this.pendingChanges.concat(this.pendingDecorationChanges)) @@ -115,6 +139,16 @@ export default class CanvasDrawer extends Mixin { this.redrawRangesOnLayer(this.frontLayer, intactRanges, firstRow, lastRow, this.drawFrontDecorationsForLines) } + /** + * Routine used to render changes in specific ranges for one layer. + * + * @param {CanvasLayer} layer the layer to redraw + * @param {Array} intactRanges an array of the ranges to leave intact + * @param {number} firstRow firstRow the first row of the range to update + * @param {number} lastRow lastRow the last row of the range to update + * @param {Function} method the render method to use for the lines drawing + * @access private + */ redrawRangesOnLayer (layer, intactRanges, firstRow, lastRow, method) { let lineHeight = this.minimap.getLineHeight() * devicePixelRatio @@ -143,7 +177,7 @@ export default class CanvasDrawer extends Mixin { * Renders the lines between the intact ranges when an update has pending * changes. * - * @param {CanvasRenderingContext2D} context the target canvas context + * @param {Function} method the render method to use for the lines drawing * @param {Array} intactRanges the intact ranges in the minimap * @param {number} firstRow the first row of the rendered region * @param {number} lastRow the last row of the rendered region @@ -245,6 +279,18 @@ export default class CanvasDrawer extends Mixin { // ## ## ## ## ## ## ## ## ## // ######## ## ## ## ## ### ### + /** + * Draws back decorations on the corresponding layer. + * + * The lines range to draw is specified by the `firstRow` and `lastRow` + * parameters. + * + * @param {number} firstRow the first row to render + * @param {number} lastRow the last row to render + * @param {number} offsetRow the relative offset to apply to rows when + * rendering them + * @access private + */ drawBackDecorationsForLines (firstRow, lastRow, offsetRow) { if (firstRow > lastRow) { return } @@ -275,6 +321,18 @@ export default class CanvasDrawer extends Mixin { this.backLayer.context.fill() } + /** + * Draws front decorations on the corresponding layer. + * + * The lines range to draw is specified by the `firstRow` and `lastRow` + * parameters. + * + * @param {number} firstRow the first row to render + * @param {number} lastRow the last row to render + * @param {number} offsetRow the relative offset to apply to rows when + * rendering them + * @access private + */ drawFrontDecorationsForLines (firstRow, lastRow, offsetRow) { if (firstRow > lastRow) { return } @@ -305,13 +363,11 @@ export default class CanvasDrawer extends Mixin { renderData.context.fill() } /** - * Draws lines on the passed-in `context`. + * Draws lines on the corresponding layer. * * The lines range to draw is specified by the `firstRow` and `lastRow` * parameters. * - * @param {CanvasRenderingContext2D} context the canvas's context where - * drawing the lines * @param {number} firstRow the first row to render * @param {number} lastRow the last row to render * @param {number} offsetRow the relative offset to apply to rows when @@ -327,14 +383,13 @@ export default class CanvasDrawer extends Mixin { let charWidth = this.minimap.getCharWidth() * devicePixelRatio let displayCodeHighlights = this.displayCodeHighlights let context = this.tokensLayer.context - let {width: canvasWidth, height: canvasHeight} = this.tokensLayer.getSize() + let {width: canvasWidth} = this.tokensLayer.getSize() let line = lines[0] let invisibleRegExp = this.getInvisibleRegExp(line) for (let i = 0, len = lines.length; i < len; i++) { line = lines[i] - let screenRow = firstRow + i let x = 0 let yRow = (offsetRow + i) * lineHeight From fc3e572e54fe65da6c42f44842a573a53bc9a695 Mon Sep 17 00:00:00 2001 From: abe33 Date: Thu, 10 Dec 2015 19:41:58 +0100 Subject: [PATCH 09/12] Use const in canvas drawer --- lib/mixins/canvas-drawer.js | 129 +++++++++++++++--------------------- 1 file changed, 52 insertions(+), 77 deletions(-) diff --git a/lib/mixins/canvas-drawer.js b/lib/mixins/canvas-drawer.js index b505338c..9c82b7ee 100644 --- a/lib/mixins/canvas-drawer.js +++ b/lib/mixins/canvas-drawer.js @@ -88,8 +88,8 @@ export default class CanvasDrawer extends Mixin { * registered in the instance. */ updateCanvas () { - let firstRow = this.minimap.getFirstVisibleScreenRow() - let lastRow = this.minimap.getLastVisibleScreenRow() + const firstRow = this.minimap.getFirstVisibleScreenRow() + const lastRow = this.minimap.getLastVisibleScreenRow() this.updateTokensLayer(firstRow, lastRow) this.updateDecorationsLayers(firstRow, lastRow) @@ -119,7 +119,7 @@ export default class CanvasDrawer extends Mixin { * @access private */ updateTokensLayer (firstRow, lastRow) { - let intactRanges = this.computeIntactRanges(firstRow, lastRow, this.pendingChanges) + const intactRanges = this.computeIntactRanges(firstRow, lastRow, this.pendingChanges) this.redrawRangesOnLayer(this.tokensLayer, intactRanges, firstRow, lastRow, this.drawLines) } @@ -133,7 +133,7 @@ export default class CanvasDrawer extends Mixin { * @access private */ updateDecorationsLayers (firstRow, lastRow) { - let intactRanges = this.computeIntactRanges(firstRow, lastRow, this.pendingChanges.concat(this.pendingDecorationChanges)) + const intactRanges = this.computeIntactRanges(firstRow, lastRow, this.pendingChanges.concat(this.pendingDecorationChanges)) this.redrawRangesOnLayer(this.backLayer, intactRanges, firstRow, lastRow, this.drawBackDecorationsForLines) this.redrawRangesOnLayer(this.frontLayer, intactRanges, firstRow, lastRow, this.drawFrontDecorationsForLines) @@ -150,7 +150,7 @@ export default class CanvasDrawer extends Mixin { * @access private */ redrawRangesOnLayer (layer, intactRanges, firstRow, lastRow, method) { - let lineHeight = this.minimap.getLineHeight() * devicePixelRatio + const lineHeight = this.minimap.getLineHeight() * devicePixelRatio layer.clearCanvas() @@ -158,7 +158,7 @@ export default class CanvasDrawer extends Mixin { method.call(this, firstRow, lastRow, 0) } else { for (let j = 0, len = intactRanges.length; j < len; j++) { - let intact = intactRanges[j] + const intact = intactRanges[j] layer.copyPartFromOffscreen( intact.offscreenRow * lineHeight, @@ -186,7 +186,7 @@ export default class CanvasDrawer extends Mixin { drawLinesForRanges (method, ranges, firstRow, lastRow) { let currentRow = firstRow for (let i = 0, len = ranges.length; i < len; i++) { - let range = ranges[i] + const range = ranges[i] method.call(this, currentRow, range.start - 1, currentRow - firstRow) @@ -220,7 +220,7 @@ export default class CanvasDrawer extends Mixin { * @return {string} a CSS color */ getDefaultColor () { - let color = this.retrieveStyleFromDom(['.editor'], 'color', false, true) + const color = this.retrieveStyleFromDom(['.editor'], 'color', false, true) return this.transparentize(color, this.getTextOpacity()) } @@ -234,8 +234,8 @@ export default class CanvasDrawer extends Mixin { * @return {string} the CSS color for the provided token */ getTokenColor (token) { - let scopes = token.scopeDescriptor || token.scopes - let color = this.retrieveStyleFromDom(scopes, 'color') + const scopes = token.scopeDescriptor || token.scopes + const color = this.retrieveStyleFromDom(scopes, 'color') return this.transparentize(color, this.getTextOpacity()) } @@ -251,10 +251,10 @@ export default class CanvasDrawer extends Mixin { * @return {string} the CSS color for the provided decoration */ getDecorationColor (decoration) { - let properties = decoration.getProperties() + const properties = decoration.getProperties() if (properties.color) { return properties.color } - let scopeString = properties.scope.split(/\s+/) + const scopeString = properties.scope.split(/\s+/) return this.retrieveStyleFromDom(scopeString, 'background-color', false) } @@ -295,12 +295,12 @@ export default class CanvasDrawer extends Mixin { if (firstRow > lastRow) { return } const devicePixelRatio = this.minimap.getDevicePixelRatio() - let lineHeight = this.minimap.getLineHeight() * devicePixelRatio - let charHeight = this.minimap.getCharHeight() * devicePixelRatio - let charWidth = this.minimap.getCharWidth() * devicePixelRatio - let decorations = this.minimap.decorationsByTypeThenRows(firstRow, lastRow) - let {width: canvasWidth, height: canvasHeight} = this.tokensLayer.getSize() - let renderData = { + const lineHeight = this.minimap.getLineHeight() * devicePixelRatio + const charHeight = this.minimap.getCharHeight() * devicePixelRatio + const charWidth = this.minimap.getCharWidth() * devicePixelRatio + const decorations = this.minimap.decorationsByTypeThenRows(firstRow, lastRow) + const {width: canvasWidth, height: canvasHeight} = this.tokensLayer.getSize() + const renderData = { context: this.backLayer.context, canvasWidth: canvasWidth, canvasHeight: canvasHeight, @@ -338,12 +338,12 @@ export default class CanvasDrawer extends Mixin { if (firstRow > lastRow) { return } const devicePixelRatio = this.minimap.getDevicePixelRatio() - let lineHeight = this.minimap.getLineHeight() * devicePixelRatio - let charHeight = this.minimap.getCharHeight() * devicePixelRatio - let charWidth = this.minimap.getCharWidth() * devicePixelRatio - let decorations = this.minimap.decorationsByTypeThenRows(firstRow, lastRow) - let {width: canvasWidth, height: canvasHeight} = this.tokensLayer.getSize() - let renderData = { + const lineHeight = this.minimap.getLineHeight() * devicePixelRatio + const charHeight = this.minimap.getCharHeight() * devicePixelRatio + const charWidth = this.minimap.getCharWidth() * devicePixelRatio + const decorations = this.minimap.decorationsByTypeThenRows(firstRow, lastRow) + const {width: canvasWidth, height: canvasHeight} = this.tokensLayer.getSize() + const renderData = { context: this.frontLayer.context, canvasWidth: canvasWidth, canvasHeight: canvasHeight, @@ -380,29 +380,29 @@ export default class CanvasDrawer extends Mixin { if (firstRow > lastRow) { return } const devicePixelRatio = this.minimap.getDevicePixelRatio() - let lines = this.getTextEditor().tokenizedLinesForScreenRows(firstRow, lastRow) - let lineHeight = this.minimap.getLineHeight() * devicePixelRatio - let charHeight = this.minimap.getCharHeight() * devicePixelRatio - let charWidth = this.minimap.getCharWidth() * devicePixelRatio - let displayCodeHighlights = this.displayCodeHighlights - let context = this.tokensLayer.context - let {width: canvasWidth} = this.tokensLayer.getSize() + const lines = this.getTextEditor().tokenizedLinesForScreenRows(firstRow, lastRow) + const lineHeight = this.minimap.getLineHeight() * devicePixelRatio + const charHeight = this.minimap.getCharHeight() * devicePixelRatio + const charWidth = this.minimap.getCharWidth() * devicePixelRatio + const displayCodeHighlights = this.displayCodeHighlights + const context = this.tokensLayer.context + const {width: canvasWidth} = this.tokensLayer.getSize() + const invisibleRegExp = this.getInvisibleRegExp(line) let line = lines[0] - let invisibleRegExp = this.getInvisibleRegExp(line) for (let i = 0, len = lines.length; i < len; i++) { line = lines[i] + const yRow = (offsetRow + i) * lineHeight let x = 0 - let yRow = (offsetRow + i) * lineHeight if ((line != null ? line.tokens : void 0) != null) { - let tokens = line.tokens + const tokens = line.tokens for (let j = 0, tokensCount = tokens.length; j < tokensCount; j++) { - let token = tokens[j] - let w = token.screenDelta + const token = tokens[j] + const w = token.screenDelta if (!token.isOnlyWhitespace()) { - let color = displayCodeHighlights ? this.getTokenColor(token) : this.getDefaultColor() + const color = displayCodeHighlights ? this.getTokenColor(token) : this.getDefaultColor() let value = token.value if (invisibleRegExp != null) { @@ -432,7 +432,7 @@ export default class CanvasDrawer extends Mixin { */ getInvisibleRegExp (line) { if ((line != null) && (line.invisibles != null)) { - let invisibles = [] + const invisibles = [] if (line.invisibles.cr != null) { invisibles.push(line.invisibles.cr) } if (line.invisibles.eol != null) { invisibles.push(line.invisibles.eol) } if (line.invisibles.space != null) { invisibles.push(line.invisibles.space) } @@ -462,7 +462,7 @@ export default class CanvasDrawer extends Mixin { let chars = 0 for (let j = 0, len = text.length; j < len; j++) { - let char = text[j] + const char = text[j] if (/\s/.test(char)) { if (chars > 0) { context.fillRect(x - (chars * charWidth), y, chars * charWidth, charHeight) @@ -528,16 +528,16 @@ export default class CanvasDrawer extends Mixin { * @access private */ drawHighlightDecoration (decoration, data) { - let range = decoration.getMarker().getScreenRange() - let rowSpan = range.end.row - range.start.row + const range = decoration.getMarker().getScreenRange() + const rowSpan = range.end.row - range.start.row data.context.fillStyle = this.getDecorationColor(decoration) if (rowSpan === 0) { - let colSpan = range.end.column - range.start.column + const colSpan = range.end.column - range.start.column data.context.fillRect(range.start.column * data.charWidth, data.yRow, colSpan * data.charWidth, data.lineHeight) } else if (data.screenRow === range.start.row) { - let x = range.start.column * data.charWidth + const x = range.start.column * data.charWidth data.context.fillRect(x, data.yRow, data.canvasWidth - x, data.lineHeight) } else if (data.screenRow === range.end.row) { data.context.fillRect(0, data.yRow, range.end.column * data.charWidth, data.lineHeight) @@ -558,11 +558,11 @@ export default class CanvasDrawer extends Mixin { */ drawHighlightOutlineDecoration (decoration, data) { let bottomWidth, colSpan, width, xBottomStart, xEnd, xStart - let {lineHeight, charWidth, canvasWidth, screenRow} = data - let range = decoration.getMarker().getScreenRange() - let rowSpan = range.end.row - range.start.row - let yStart = data.yRow - let yEnd = yStart + lineHeight + const {lineHeight, charWidth, canvasWidth, screenRow} = data + const range = decoration.getMarker().getScreenRange() + const rowSpan = range.end.row - range.start.row + const yStart = data.yRow + const yEnd = yStart + lineHeight data.context.fillStyle = this.getDecorationColor(decoration) @@ -626,31 +626,6 @@ export default class CanvasDrawer extends Mixin { } } -<<<<<<< HEAD -======= - /** - * Copy a part of the offscreen bitmap into the onscreen one to - * reduce the amount of rendered lines during scroll. - * - * @param {CanvasRenderingContext2D} context [description] - * @param {HTMLCanvasElement} bitmapCanvas [description] - * @param {number} srcRow the source row of the region to copy - * @param {number} destRow the destination row - * @param {number} rowCount the amount of lines to copy - * @access private - */ - copyBitmapPart (context, bitmapCanvas, srcRow, destRow, rowCount) { - const devicePixelRatio = this.minimap.getDevicePixelRatio() - let lineHeight = this.minimap.getLineHeight() * devicePixelRatio - - context.drawImage( - bitmapCanvas, - 0, srcRow * lineHeight, bitmapCanvas.width, rowCount * lineHeight, - 0, destRow * lineHeight, bitmapCanvas.width, rowCount * lineHeight - ) - } - ->>>>>>> 546c9ce626a3a6f8c597ed4af27eb8ecdc2124aa // ######## ### ## ## ###### ######## ###### // ## ## ## ## ### ## ## ## ## ## ## // ## ## ## ## #### ## ## ## ## @@ -682,11 +657,11 @@ export default class CanvasDrawer extends Mixin { ] for (let i = 0, len = changes.length; i < len; i++) { - let change = changes[i] - let newIntactRanges = [] + const change = changes[i] + const newIntactRanges = [] for (let j = 0, intactLen = intactRanges.length; j < intactLen; j++) { - let range = intactRanges[j] + const range = intactRanges[j] if (change.end < range.start && change.screenDelta !== 0) { // The change is above of the range and lines are either @@ -751,7 +726,7 @@ export default class CanvasDrawer extends Mixin { truncateIntactRanges (intactRanges, firstRow, lastRow) { let i = 0 while (i < intactRanges.length) { - let range = intactRanges[i] + const range = intactRanges[i] if (range.start < firstRow) { range.offscreenRow += firstRow - range.start From 1c294af3687bb7a7fa4addce63f9ea273eb29091 Mon Sep 17 00:00:00 2001 From: abe33 Date: Thu, 10 Dec 2015 19:42:06 +0100 Subject: [PATCH 10/12] Fix linter issues --- lib/minimap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/minimap.js b/lib/minimap.js index c23f0024..81da8379 100644 --- a/lib/minimap.js +++ b/lib/minimap.js @@ -710,7 +710,7 @@ export default class Minimap { * * @return {number} the devicePixelRatio in the Minimap */ - getDevicePixelRatio() { + getDevicePixelRatio () { return this.getDevicePixelRatioRounding() ? Math.floor(devicePixelRatio) : devicePixelRatio From 2c7edecc0422a01d9ccf23450e9cf74344035184 Mon Sep 17 00:00:00 2001 From: abe33 Date: Thu, 10 Dec 2015 20:49:53 +0100 Subject: [PATCH 11/12] :bug: Fix invalid invisible regxep due to declaration order --- lib/mixins/canvas-drawer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mixins/canvas-drawer.js b/lib/mixins/canvas-drawer.js index 9c82b7ee..e507c88d 100644 --- a/lib/mixins/canvas-drawer.js +++ b/lib/mixins/canvas-drawer.js @@ -387,9 +387,9 @@ export default class CanvasDrawer extends Mixin { const displayCodeHighlights = this.displayCodeHighlights const context = this.tokensLayer.context const {width: canvasWidth} = this.tokensLayer.getSize() - const invisibleRegExp = this.getInvisibleRegExp(line) let line = lines[0] + const invisibleRegExp = this.getInvisibleRegExp(line) for (let i = 0, len = lines.length; i < len; i++) { line = lines[i] From 06b93a3ea8c9746fd56cae0db204a12099da9de8 Mon Sep 17 00:00:00 2001 From: abe33 Date: Thu, 10 Dec 2015 20:52:04 +0100 Subject: [PATCH 12/12] Fix specs formatting --- spec/minimap-element-spec.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/minimap-element-spec.js b/spec/minimap-element-spec.js index bc6335e6..0f1f0b0a 100644 --- a/spec/minimap-element-spec.js +++ b/spec/minimap-element-spec.js @@ -512,10 +512,8 @@ describe('MinimapElement', () => { 'by 40 pixels from the arbitrary location', () => { let {top} = visibleArea.getBoundingClientRect() expect(top).toBeCloseTo(originalTop + 40, -1) - } - ) - } - ) + }) + }) }) })