From 21e1c5ef584737a5a27d517fbd41a5f1f2df270a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 22 Mar 2022 01:53:10 -0400 Subject: [PATCH 1/4] refresh canvas dimensions on refresh --- src/browser/Decorations/OverviewRulerRenderer.ts | 6 +++--- src/browser/Terminal.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 9cdf99ca..8c63f5e3 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -49,7 +49,7 @@ export class OverviewRulerRenderer extends Disposable { super(); this._canvas = document.createElement('canvas'); this._canvas.classList.add('xterm-decoration-overview-ruler'); - this._refreshCanvasDimensions(); + this.refreshCanvasDimensions(); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); const ctx = this._canvas.getContext('2d'); if (!ctx) { @@ -129,7 +129,7 @@ export class OverviewRulerRenderer extends Disposable { ); } - private _refreshCanvasDimensions(): void { + public refreshCanvasDimensions(): void { this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; this._canvas.width = Math.round(this._width * window.devicePixelRatio); @@ -139,7 +139,7 @@ export class OverviewRulerRenderer extends Disposable { private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { if (updateCanvasDimensions) { - this._refreshCanvasDimensions(); + this.refreshCanvasDimensions(); } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 8cb116ae..6560a976 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -175,7 +175,9 @@ export class Terminal extends CoreTerminal implements ITerminal { // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); - this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); + this.register(this._inputHandler.onRequestRefreshRows((start, end) => { + this.refresh(start, end); + })); this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus())); this.register(this._inputHandler.onRequestReset(() => this.reset())); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); @@ -613,7 +615,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this.optionsService.onOptionChange(() => { if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) { this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); - }}); + } + }); // Measure the character size this._charSizeService.measure(); @@ -906,6 +909,7 @@ export class Terminal extends CoreTerminal implements ITerminal { */ public refresh(start: number, end: number): void { this._renderService?.refreshRows(start, end); + this._overviewRulerRenderer?.refreshCanvasDimensions(); } /** From 1f60a398b277f2daaac994285db081730a6ae2c1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 22 Mar 2022 15:04:22 -0400 Subject: [PATCH 2/4] on render, fire when height has changed (was 0) --- .../Decorations/OverviewRulerRenderer.ts | 46 ++++++++++++++----- src/browser/Terminal.ts | 5 +- src/browser/TestUtils.test.ts | 1 + src/browser/services/RenderService.ts | 7 ++- src/browser/services/Services.ts | 4 ++ 5 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 8c63f5e3..9d6e4904 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -38,6 +38,10 @@ export class OverviewRulerRenderer extends Disposable { } private _animationFrame: number | undefined; + private _canvasHeight: number | undefined; + private _canvasWidth: number | undefined; + private _shouldUpdateDimensions: boolean | undefined = true; + constructor( private readonly _viewportElement: HTMLElement, private readonly _screenElement: HTMLElement, @@ -49,7 +53,7 @@ export class OverviewRulerRenderer extends Disposable { super(); this._canvas = document.createElement('canvas'); this._canvas.classList.add('xterm-decoration-overview-ruler'); - this.refreshCanvasDimensions(); + this._refreshCanvasDimensions(); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); const ctx = this._canvas.getContext('2d'); if (!ctx) { @@ -62,6 +66,11 @@ export class OverviewRulerRenderer extends Disposable { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); + this.register(this._renderService.onRender(() => { + if (this._canvasHeight !== this._screenElement.clientHeight) { + this._queueRefresh(true); + } + })); this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true, true))); this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh(true))); this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); @@ -102,6 +111,7 @@ export class OverviewRulerRenderer extends Disposable { drawX.left = 0; drawX.center = drawWidth.left; drawX.right = drawWidth.left + drawWidth.center; + this._shouldUpdateDimensions = false; } private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void { @@ -122,24 +132,35 @@ export class OverviewRulerRenderer extends Disposable { /* x */ drawX[decoration.options.overviewRulerOptions.position!], /* 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 + (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!] ); } - public refreshCanvasDimensions(): void { - this._canvas.style.width = `${this._width}px`; - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.round(this._width * window.devicePixelRatio); - this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); - this._refreshDrawConstants(); + private _refreshCanvasDimensions(): void { + let updated = false; + if (this._canvasWidth !== this._width) { + this._canvas.style.width = `${this._width}px`; + this._canvas.width = Math.round(this._width * window.devicePixelRatio); + this._canvasWidth = this._canvas.width; + updated = true; + } + if (this._canvasHeight !== Math.round(this._screenElement.clientHeight * window.devicePixelRatio)) { + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); + this._canvasHeight = this._canvas.height; + updated = true; + } + if (updated) { + this._refreshDrawConstants(); + } } - private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { - if (updateCanvasDimensions) { - this.refreshCanvasDimensions(); + private _refreshDecorations(updateAnchor?: boolean): void { + if (this._shouldUpdateDimensions) { + this._refreshCanvasDimensions(); } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { @@ -165,10 +186,11 @@ export class OverviewRulerRenderer extends Disposable { private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { if (this._animationFrame !== undefined) { + this._shouldUpdateDimensions = updateCanvasDimensions || this._shouldUpdateDimensions; return; } this._animationFrame = window.requestAnimationFrame(() => { - this._refreshDecorations(updateCanvasDimensions, updateAnchor); + this._refreshDecorations(updateAnchor); this._animationFrame = undefined; }); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 6560a976..f14b5217 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -175,9 +175,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); - this.register(this._inputHandler.onRequestRefreshRows((start, end) => { - this.refresh(start, end); - })); + this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus())); this.register(this._inputHandler.onRequestReset(() => this.reset())); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); @@ -909,7 +907,6 @@ export class Terminal extends CoreTerminal implements ITerminal { */ public refresh(start: number, end: number): void { this._renderService?.refreshRows(start, end); - this._overviewRulerRenderer?.refreshCanvasDimensions(); } /** diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index ac048be6..85e2bb55 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -371,6 +371,7 @@ export class MockRenderService implements IRenderService { public serviceBrand: undefined; public onDimensionsChange: IEvent = new EventEmitter().event; public onRenderedBufferChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; + public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event; public dimensions: IRenderDimensions = { scaledCharWidth: 0, diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index da458abc..91b510a3 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -39,8 +39,10 @@ export class RenderService extends Disposable implements IRenderService { private _onDimensionsChange = new EventEmitter(); public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; } + private _onRenderedBufferChange = new EventEmitter<{ start: number, end: number }>(); + public get onRenderedBufferChange(): IEvent<{ start: number, end: number }> { return this._onRenderedBufferChange.event; } private _onRender = new EventEmitter<{ start: number, end: number }>(); - public get onRenderedBufferChange(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; } @@ -122,8 +124,9 @@ export class RenderService extends Disposable implements IRenderService { // Fire render event only if it was not a redraw if (!this._isNextRenderRedrawOnly) { - this._onRender.fire({ start, end }); + this._onRenderedBufferChange.fire({ start, end }); } + this._onRender.fire({ start, end }); this._isNextRenderRedrawOnly = true; } diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 1598ef02..7191d0ed 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -50,6 +50,10 @@ export interface IRenderService extends IDisposable { * or selections are rendered. */ onRenderedBufferChange: IEvent<{ start: number, end: number }>; + /** + * Fires on render + */ + onRender: IEvent<{ start: number, end: number }>; onRefreshRequest: IEvent<{ start: number, end: number }>; dimensions: IRenderDimensions; From 74409da947c59088c45901d35d0c233f28773203 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 22 Mar 2022 15:09:54 -0400 Subject: [PATCH 3/4] move resetting of var --- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 9d6e4904..07d3546a 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -111,7 +111,6 @@ export class OverviewRulerRenderer extends Disposable { drawX.left = 0; drawX.center = drawWidth.left; drawX.right = drawWidth.left + drawWidth.center; - this._shouldUpdateDimensions = false; } private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void { @@ -161,6 +160,7 @@ export class OverviewRulerRenderer extends Disposable { private _refreshDecorations(updateAnchor?: boolean): void { if (this._shouldUpdateDimensions) { this._refreshCanvasDimensions(); + this._shouldUpdateDimensions = false; } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { From 550b19f334f672e5367383ce0784ce4c5e4e8b15 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 22 Mar 2022 16:41:50 -0400 Subject: [PATCH 4/4] refactor --- .../Decorations/OverviewRulerRenderer.ts | 102 ++++++++++-------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 07d3546a..950e2ac8 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -38,9 +38,10 @@ export class OverviewRulerRenderer extends Disposable { } private _animationFrame: number | undefined; - private _canvasHeight: number | undefined; - private _canvasWidth: number | undefined; private _shouldUpdateDimensions: boolean | undefined = true; + private _shouldUpdateAnchor: boolean | undefined = true; + + private _containerHeight: number | undefined; constructor( private readonly _viewportElement: HTMLElement, @@ -61,27 +62,53 @@ export class OverviewRulerRenderer extends Disposable { } else { this._ctx = ctx; } - this._queueRefresh(true); + this._registerDecorationListeners(); + this._registerBufferChangeListeners(); + this._registerDimensionChangeListeners(); + } + + /** + * On decoration add or remove, redraw + */ + private _registerDecorationListeners(): void { + this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); + this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); + } + + /** + * On buffer change, redraw + * and hide the canvas if the alt buffer is active + */ + private _registerBufferChangeListeners(): void { + this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); - this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); - this.register(this._renderService.onRender(() => { - if (this._canvasHeight !== this._screenElement.clientHeight) { + } + /** + * On dimension change, update canvas dimensions + * and then redraw + */ + private _registerDimensionChangeListeners(): void { + // container height changed + this.register(this._renderService.onRender((): void => { + if (!this._containerHeight || this._containerHeight !== this._screenElement.clientHeight) { + this._queueRefresh(true); + this._containerHeight = this._screenElement.clientHeight; + } + })); + // overview ruler width changed + this.register(this._optionsService.onOptionChange(o => { + if (o === 'overviewRulerWidth') { this._queueRefresh(true); } })); - this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true, true))); - this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh(true))); - this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); - this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); - this.register(this._optionsService.onOptionChange(o => { - if (o === 'overviewRulerWidth') { - this._refreshDrawConstants(); - this._queueRefresh(); - } + // device pixel ratio changed + this.register(addDisposableDomListener(window, 'resize', () => { + this._queueRefresh(true); })); - this._refreshDrawConstants(); + // set the canvas dimensions + this._queueRefresh(true); } public override dispose(): void { @@ -113,8 +140,8 @@ export class OverviewRulerRenderer extends Disposable { drawX.right = drawWidth.left + drawWidth.center; } - private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void { - if (updateAnchor) { + private _refreshStyle(decoration: IInternalDecoration): void { + if (this._shouldUpdateAnchor) { if (decoration.options.anchor === 'right') { this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; } else { @@ -139,58 +166,49 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshCanvasDimensions(): void { - let updated = false; - if (this._canvasWidth !== this._width) { - this._canvas.style.width = `${this._width}px`; - this._canvas.width = Math.round(this._width * window.devicePixelRatio); - this._canvasWidth = this._canvas.width; - updated = true; - } - if (this._canvasHeight !== Math.round(this._screenElement.clientHeight * window.devicePixelRatio)) { - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); - this._canvasHeight = this._canvas.height; - updated = true; - } - if (updated) { - this._refreshDrawConstants(); - } + this._canvas.style.width = `${this._width}px`; + this._canvas.width = Math.round(this._width * window.devicePixelRatio); + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); + this._refreshDrawConstants(); } - private _refreshDecorations(updateAnchor?: boolean): void { + private _refreshDecorations(): void { if (this._shouldUpdateDimensions) { this._refreshCanvasDimensions(); - this._shouldUpdateDimensions = false; } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { if (decoration.options.overviewRulerOptions && decoration.options.overviewRulerOptions.position !== 'full') { - this._renderDecoration(decoration, updateAnchor); + this._renderDecoration(decoration); } } for (const decoration of this._decorationService.decorations) { if (decoration.options.overviewRulerOptions && decoration.options.overviewRulerOptions.position === 'full') { - this._renderDecoration(decoration, updateAnchor); + this._renderDecoration(decoration); } } + this._shouldUpdateDimensions = false; + this._shouldUpdateAnchor = false; } - private _renderDecoration(decoration: IInternalDecoration, updateAnchor?: boolean): void { + private _renderDecoration(decoration: IInternalDecoration): void { const element = this._decorationElements.get(decoration); if (!element) { this._decorationElements.set(decoration, this._canvas); decoration.onDispose(() => this._queueRefresh()); } - this._refreshStyle(decoration, updateAnchor); + this._refreshStyle(decoration); } private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { + this._shouldUpdateDimensions = updateCanvasDimensions || this._shouldUpdateDimensions; + this._shouldUpdateAnchor = updateAnchor || this._shouldUpdateAnchor; if (this._animationFrame !== undefined) { - this._shouldUpdateDimensions = updateCanvasDimensions || this._shouldUpdateDimensions; return; } this._animationFrame = window.requestAnimationFrame(() => { - this._refreshDecorations(updateAnchor); + this._refreshDecorations(); this._animationFrame = undefined; }); }