From 6b33ac1e85fb473b99a4f1c0ca3126b44256c078 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 1 Aug 2023 06:13:21 -0700 Subject: [PATCH] Share cursor blink state manager between canvas and webgl This also focused the manager's creation on renderer start up, to ensure blink works immediately. --- .../src/CursorRenderLayer.ts | 142 +----------------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 11 +- .../shared}/CursorBlinkStateManager.ts | 7 +- 3 files changed, 13 insertions(+), 147 deletions(-) rename {addons/xterm-addon-webgl/src => src/browser/renderer/shared}/CursorBlinkStateManager.ts (96%) diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 7efcd74e..19b07b5f 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -14,6 +14,7 @@ import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { Terminal } from 'xterm'; import { toDisposable } from 'common/Lifecycle'; import { isFirefox } from 'common/Platform'; +import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStateManager'; interface ICursorState { x: number; @@ -60,6 +61,7 @@ export class CursorRenderLayer extends BaseRenderLayer { 'underline': this._renderUnderlineCursor.bind(this) }; this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); + this._handleOptionsChanged(); this.register(toDisposable(() => { this._cursorBlinkStateManager?.dispose(); this._cursorBlinkStateManager = undefined; @@ -97,9 +99,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _handleOptionsChanged(): void { if (this._optionsService.rawOptions.cursorBlink) { if (!this._cursorBlinkStateManager) { - this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => { - this._render(true); - }, this._coreBrowserService); + this._cursorBlinkStateManager = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService); } } else { this._cursorBlinkStateManager?.dispose(); @@ -238,139 +238,3 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.restore(); } } - -class CursorBlinkStateManager { - public isCursorVisible: boolean; - - private _animationFrame: number | undefined; - private _blinkStartTimeout: number | undefined; - private _blinkInterval: number | undefined; - - /** - * The time at which the animation frame was restarted, this is used on the - * next render to restart the timers so they don't need to restart the timers - * multiple times over a short period. - */ - private _animationTimeRestarted: number | undefined; - - constructor( - isFocused: boolean, - private _renderCallback: () => void, - private _coreBrowserService: ICoreBrowserService - ) { - this.isCursorVisible = true; - if (isFocused) { - this._restartInterval(); - } - } - - public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); } - - public dispose(): void { - if (this._blinkInterval) { - this._coreBrowserService.window.clearInterval(this._blinkInterval); - this._blinkInterval = undefined; - } - if (this._blinkStartTimeout) { - this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout); - this._blinkStartTimeout = undefined; - } - if (this._animationFrame) { - this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame); - this._animationFrame = undefined; - } - } - - public restartBlinkAnimation(): void { - if (this.isPaused) { - return; - } - // Save a timestamp so that the restart can be done on the next interval - this._animationTimeRestarted = Date.now(); - // Force a cursor render to ensure it's visible and in the correct position - this.isCursorVisible = true; - if (!this._animationFrame) { - this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { - this._renderCallback(); - this._animationFrame = undefined; - }); - } - } - - private _restartInterval(timeToStart: number = BLINK_INTERVAL): void { - // Clear any existing interval - if (this._blinkInterval) { - this._coreBrowserService.window.clearInterval(this._blinkInterval); - this._blinkInterval = undefined; - } - - // Setup the initial timeout which will hide the cursor, this is done before - // the regular interval is setup in order to support restarting the blink - // animation in a lightweight way (without thrashing clearInterval and - // setInterval). - this._blinkStartTimeout = this._coreBrowserService.window.setTimeout(() => { - // Check if another animation restart was requested while this was being - // started - if (this._animationTimeRestarted) { - const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); - this._animationTimeRestarted = undefined; - if (time > 0) { - this._restartInterval(time); - return; - } - } - - // Hide the cursor - this.isCursorVisible = false; - this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { - this._renderCallback(); - this._animationFrame = undefined; - }); - - // Setup the blink interval - this._blinkInterval = this._coreBrowserService.window.setInterval(() => { - // Adjust the animation time if it was restarted - if (this._animationTimeRestarted) { - // calc time diff - // Make restart interval do a setTimeout initially? - const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); - this._animationTimeRestarted = undefined; - this._restartInterval(time); - return; - } - - // Invert visibility and render - this.isCursorVisible = !this.isCursorVisible; - this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { - this._renderCallback(); - this._animationFrame = undefined; - }); - }, BLINK_INTERVAL); - }, timeToStart); - } - - public pause(): void { - this.isCursorVisible = true; - if (this._blinkInterval) { - this._coreBrowserService.window.clearInterval(this._blinkInterval); - this._blinkInterval = undefined; - } - if (this._blinkStartTimeout) { - this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout); - this._blinkStartTimeout = undefined; - } - if (this._animationFrame) { - this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame); - this._animationFrame = undefined; - } - } - - public resume(): void { - // Clear out any existing timers just in case - this.pause(); - - this._animationTimeRestarted = undefined; - this._restartInterval(); - this.restartBlinkAnimation(); - } -} diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index e110c5cc..d299ad8c 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -22,7 +22,7 @@ import { CharData, IBufferLine, ICellData } from 'common/Types'; import { IDisposable, Terminal } from 'xterm'; import { GlyphRenderer } from './GlyphRenderer'; import { RectangleRenderer } from './RectangleRenderer'; -import { CursorBlinkStateManager } from './CursorBlinkStateManager'; +import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStateManager'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { IRenderLayer } from './renderLayer/Types'; import { COMBINED_CHAR_BIT_MASK, RenderModel, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; @@ -86,6 +86,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this.dimensions = createRenderDimensions(); this._devicePixelRatio = this._coreBrowserService.dpr; this._updateDimensions(); + this._updateCursorBlink(); this.register(_optionsService.onOptionChange(() => this._handleOptionsChanged())); this._canvas = document.createElement('canvas'); @@ -201,6 +202,7 @@ export class WebglRenderer extends Disposable implements IRenderer { for (const l of this._renderLayers) { l.handleBlur(this._terminal); } + console.log('blur'); this._cursorBlinkStateManager?.pause(); // Request a redraw for active/inactive selection background this._requestRedrawViewport(); @@ -210,7 +212,8 @@ export class WebglRenderer extends Disposable implements IRenderer { for (const l of this._renderLayers) { l.handleFocus(this._terminal); } - this._cursorBlinkStateManager?.resume(this._terminal); + this._cursorBlinkStateManager?.resume(); + console.log('focus'); // Request a redraw for active/inactive selection background this._requestRedrawViewport(); } @@ -227,7 +230,7 @@ export class WebglRenderer extends Disposable implements IRenderer { for (const l of this._renderLayers) { l.handleCursorMove(this._terminal); } - this._cursorBlinkStateManager?.restartBlinkAnimation(this._terminal); + this._cursorBlinkStateManager?.restartBlinkAnimation(); } private _handleOptionsChanged(): void { @@ -310,7 +313,7 @@ export class WebglRenderer extends Disposable implements IRenderer { l.reset(this._terminal); } - this._cursorBlinkStateManager?.restartBlinkAnimation(this._terminal); + this._cursorBlinkStateManager?.restartBlinkAnimation(); this._updateCursorBlink(); } diff --git a/addons/xterm-addon-webgl/src/CursorBlinkStateManager.ts b/src/browser/renderer/shared/CursorBlinkStateManager.ts similarity index 96% rename from addons/xterm-addon-webgl/src/CursorBlinkStateManager.ts rename to src/browser/renderer/shared/CursorBlinkStateManager.ts index 3823f9f4..c5bb0870 100644 --- a/addons/xterm-addon-webgl/src/CursorBlinkStateManager.ts +++ b/src/browser/renderer/shared/CursorBlinkStateManager.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { Terminal } from 'xterm'; import { ICoreBrowserService } from 'browser/services/Services'; /** @@ -52,7 +51,7 @@ export class CursorBlinkStateManager { } } - public restartBlinkAnimation(terminal: Terminal): void { + public restartBlinkAnimation(): void { if (this.isPaused) { return; } @@ -136,12 +135,12 @@ export class CursorBlinkStateManager { } } - public resume(terminal: Terminal): void { + public resume(): void { // Clear out any existing timers just in case this.pause(); this._animationTimeRestarted = undefined; this._restartInterval(); - this.restartBlinkAnimation(terminal); + this.restartBlinkAnimation(); } }