From f43afde8df4f4e2b40ba6aff9cd3c1eaa1decf79 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:42:26 -0700 Subject: [PATCH 1/6] Respect device pixel ratio in overview ruler --- .../Decorations/OverviewRulerRenderer.ts | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 1998d18a76..af894ebf48 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -18,6 +18,13 @@ const enum SizeIndex { INNER_SIZE = 1 } +const positionHeights = { + full: 2, + left: 6, + center: 6, + right: 6 +}; + export class OverviewRulerRenderer extends Disposable { private readonly _canvas: HTMLCanvasElement; private readonly _ctx: CanvasRenderingContext2D; @@ -38,6 +45,7 @@ export class OverviewRulerRenderer extends Disposable { super(); this._canvas = document.createElement('canvas'); this._canvas.classList.add('xterm-decoration-overview-ruler'); + this._refreshCanvasDimensions(); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); const ctx = this._canvas.getContext('2d'); if (!ctx) { @@ -56,13 +64,14 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { if (o === 'overviewRulerWidth') { - renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); - renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); + renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); + renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); this._queueRefresh(); } })); - renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); - renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); + console.log('width', this._canvas.width); + renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); + renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); } public override dispose(): void { @@ -89,19 +98,31 @@ export class OverviewRulerRenderer extends Disposable { this._ctx.lineWidth = 1; this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; this._ctx.fillRect( - decoration.options.overviewRulerOptions.position === 'full' || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], - Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - decoration.options.overviewRulerOptions.position === 'full' ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], - window.devicePixelRatio * (decoration.options.overviewRulerOptions.position === 'full' ? 2 : 6) + /* x */ decoration.options.overviewRulerOptions.position === 'full' || decoration.options.overviewRulerOptions.position === 'left' + ? 0 + : decoration.options.overviewRulerOptions.position === 'right' + ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE] + : renderSizes[SizeIndex.OUTER_SIZE], + /* y */ Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), + /* w */ decoration.options.overviewRulerOptions.position === 'full' + ? this._canvas.width + : decoration.options.overviewRulerOptions.position === 'center' + ? renderSizes[SizeIndex.INNER_SIZE] + : renderSizes[SizeIndex.OUTER_SIZE], + /* h */ window.devicePixelRatio * positionHeights[decoration.options.overviewRulerOptions.position!] ); } + private _refreshCanvasDimensions(): void { + this._canvas.style.width = `${this._width}px`; + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.width = Math.floor(this._width * window.devicePixelRatio); + this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + } + private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { if (updateCanvasDimensions) { - this._canvas.style.width = `${this._width}px`; - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); - this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + this._refreshCanvasDimensions(); } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { From 4a661da57bd61417f49c030490d417bad30c7e11 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:50:28 -0700 Subject: [PATCH 2/6] Use object properties for fast access to precalculated draw args --- .../Decorations/OverviewRulerRenderer.ts | 69 +++++++++++++------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index af894ebf48..3a1c127ae5 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -18,11 +18,25 @@ const enum SizeIndex { INNER_SIZE = 1 } -const positionHeights = { - full: 2, - left: 6, - center: 6, - right: 6 +const drawHeight = { + full: 0, + left: 0, + center: 0, + right: 0 +}; + +const drawWidth = { + full: 0, + left: 0, + center: 0, + right: 0 +}; + +const drawX = { + full: 0, + left: 0, + center: 0, + right: 0 }; export class OverviewRulerRenderer extends Disposable { @@ -64,14 +78,16 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { if (o === 'overviewRulerWidth') { - renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); - renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); + // renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); + // renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); + this._refreshDrawConstants(); this._queueRefresh(); } })); console.log('width', this._canvas.width); - renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); - renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); + this._refreshDrawConstants(); + // renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); + // renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); } public override dispose(): void { @@ -83,6 +99,26 @@ export class OverviewRulerRenderer extends Disposable { super.dispose(); } + private _refreshDrawConstants(): void { + // width + const outerWidth = Math.floor(this._canvas.width / 3); + const innerWidth = Math.ceil(this._canvas.width / 3); + drawWidth.full = this._canvas.width; + drawWidth.left = outerWidth; + drawWidth.center = innerWidth; + drawWidth.right = outerWidth; + // height + drawHeight.full = Math.round(2 * window.devicePixelRatio); + drawHeight.left = Math.round(6 * window.devicePixelRatio); + drawHeight.center = Math.round(6 * window.devicePixelRatio); + drawHeight.right = Math.round(6 * window.devicePixelRatio); + // x + drawX.full = 0; + drawX.left = 0; + drawX.center = drawWidth.left; + drawX.right = drawWidth.left + drawWidth.center; + } + private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void { if (updateAnchor) { if (decoration.options.anchor === 'right') { @@ -98,18 +134,10 @@ export class OverviewRulerRenderer extends Disposable { this._ctx.lineWidth = 1; this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; this._ctx.fillRect( - /* x */ decoration.options.overviewRulerOptions.position === 'full' || decoration.options.overviewRulerOptions.position === 'left' - ? 0 - : decoration.options.overviewRulerOptions.position === 'right' - ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE] - : renderSizes[SizeIndex.OUTER_SIZE], + /* x */ drawX[decoration.options.overviewRulerOptions.position!], /* y */ Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - /* w */ decoration.options.overviewRulerOptions.position === 'full' - ? this._canvas.width - : decoration.options.overviewRulerOptions.position === 'center' - ? renderSizes[SizeIndex.INNER_SIZE] - : renderSizes[SizeIndex.OUTER_SIZE], - /* h */ window.devicePixelRatio * positionHeights[decoration.options.overviewRulerOptions.position!] + /* w */ drawWidth[decoration.options.overviewRulerOptions.position!], + /* h */ drawHeight[decoration.options.overviewRulerOptions.position!] ); } @@ -118,6 +146,7 @@ export class OverviewRulerRenderer extends Disposable { this._canvas.style.height = `${this._screenElement.clientHeight}px`; this._canvas.width = Math.floor(this._width * window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + this._refreshDrawConstants(); } private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { From 816220ebd2195c72240a3683e6f2a1e079b57824 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:51:02 -0700 Subject: [PATCH 3/6] Round dpr for more crisp decorations --- src/browser/Decorations/OverviewRulerRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 3a1c127ae5..c38b5377ad 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -144,8 +144,8 @@ export class OverviewRulerRenderer extends Disposable { private _refreshCanvasDimensions(): void { this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor(this._width * window.devicePixelRatio); - this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + this._canvas.width = Math.round(this._width * window.devicePixelRatio); + this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); this._refreshDrawConstants(); } From 77622f8919fe5be92abe767784f08d133ebbe1d2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:54:11 -0700 Subject: [PATCH 4/6] Align full with other decorations Fixes #3692 --- demo/client.ts | 2 ++ src/browser/Decorations/OverviewRulerRenderer.ts | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index 67e277592c..2b62237221 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -559,5 +559,7 @@ function addOverviewRuler() { term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); + term.registerDecoration({marker: term.addMarker(10), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); + term.registerDecoration({marker: term.addMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' }}); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index c38b5377ad..0344fe5f69 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -135,7 +135,10 @@ export class OverviewRulerRenderer extends Disposable { this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; this._ctx.fillRect( /* x */ drawX[decoration.options.overviewRulerOptions.position!], - /* y */ Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), + /* y */ Math.round( + (this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line + (decoration.options.marker.line / this._bufferService.buffers.active.lines.length) - drawHeight[decoration.options.overviewRulerOptions.position!] / 2 + ), /* w */ drawWidth[decoration.options.overviewRulerOptions.position!], /* h */ drawHeight[decoration.options.overviewRulerOptions.position!] ); From b5f208294d45f2ffee02181437360432be4e940d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:56:49 -0700 Subject: [PATCH 5/6] Clean up --- .../Decorations/OverviewRulerRenderer.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 0344fe5f69..87f10181d5 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -8,30 +8,20 @@ import { IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services'; -// This is used to reduce memory usage -// when refreshStyle is called -// by storing and updating -// the sizes of the decorations to be drawn -const renderSizes = new Uint16Array(3); -const enum SizeIndex { - OUTER_SIZE = 0, - INNER_SIZE = 1 -} - +// Helper objects to avoid excessive calculation and garbage collection during rendering. These are +// static values for each render and can be accessed using the decoration position as the key. const drawHeight = { full: 0, left: 0, center: 0, right: 0 }; - const drawWidth = { full: 0, left: 0, center: 0, right: 0 }; - const drawX = { full: 0, left: 0, @@ -78,16 +68,11 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { if (o === 'overviewRulerWidth') { - // renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); - // renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); this._refreshDrawConstants(); this._queueRefresh(); } })); - console.log('width', this._canvas.width); this._refreshDrawConstants(); - // renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); - // renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); } public override dispose(): void { From 39d1546753e18615a20a41f50b9e06434db33f29 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:59:10 -0700 Subject: [PATCH 6/6] Draw full decorations on top Fixes #3696 --- src/browser/Decorations/OverviewRulerRenderer.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 87f10181d5..aa09db568e 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -143,7 +143,14 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { - this._renderDecoration(decoration, updateAnchor); + if (decoration.options.overviewRulerOptions!.position !== 'full') { + this._renderDecoration(decoration, updateAnchor); + } + } + for (const decoration of this._decorationService.decorations) { + if (decoration.options.overviewRulerOptions!.position === 'full') { + this._renderDecoration(decoration, updateAnchor); + } } }