From 3fc500b291de87731e160e3323b6de5070faffde Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 12:11:07 -0700 Subject: [PATCH] Pull cursor animation state management into a helper class --- src/Interfaces.ts | 1 + src/renderer/CursorRenderLayer.ts | 78 ++++++++++++++++++++++--------- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 1b1625bc..afa2de58 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -47,6 +47,7 @@ export interface ITerminal extends IEventEmitter { reset(): void; showCursor(): void; blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData; + refresh(start: number, end: number): void; } /** diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 89707590..11e56347 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -19,6 +19,15 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay private _blinkInterval: number; private _isVisible: boolean; + /** + * 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; + + private _cursorBlinkStateManager: CursorBlinkStateManager; + constructor(container: HTMLElement, zIndex: number) { super(container, 'cursor', zIndex); this._state = null; @@ -28,6 +37,7 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay 'block': this._renderBlockCursor.bind(this), 'underline': this._renderUnderlineCursor.bind(this) }; + // TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open? } public reset(terminal: ITerminal): void { @@ -37,21 +47,36 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay public onOptionsChanged(terminal: ITerminal): void { super.onOptionsChanged(terminal); - this._refreshBlinkState(terminal); + if (terminal.options.cursorBlink) { + if (!this._cursorBlinkStateManager) { + this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => { + this._render(terminal, true); + }); + } + } else { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + } + // Request a refresh from the terminal as management of rendering is being + // moved back to the terminal + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } } public render(terminal: ITerminal, startRow: number, endRow: number): void { // Only render if the animation frame is not active - if (!this._blinkInterval) { + if (!this._cursorBlinkStateManager) { this._render(terminal, false); } } private _render(terminal: ITerminal, triggeredByAnimationFrame: boolean): void { // TODO: Track blur/focus somehow, support unfocused cursor - // Don't draw the cursor if it's hidden - if (!terminal.cursorState || terminal.cursorHidden || !this._isVisible) { + if (!terminal.cursorState || + terminal.cursorHidden || + (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible)) { this._clearCursor(); return; } @@ -100,32 +125,41 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this.fillBottomLineAtCell(x, y); } +} - private _refreshBlinkState(terminal: ITerminal): void { - if (terminal.options.cursorBlink) { - if (!this._blinkInterval) { - this._blinkInterval = setInterval(() => { - this._isVisible = !this._isVisible; - this._animationFrame = window.requestAnimationFrame(() => { - this._render(terminal, true); - this._animationFrame = null; - }); - }, BLINK_INTERVAL); - } - } else { - if (this._animationFrame) { - window.clearInterval(this._blinkInterval); - this._blinkInterval = null; - window.cancelAnimationFrame(this._animationFrame); +class CursorBlinkStateManager { + public isCursorVisible: boolean; + + private _animationFrame: number; + private _blinkInterval: number; + + constructor( + terminal: ITerminal, + private renderCallback: () => void + ) { + this.isCursorVisible = true; + this._blinkInterval = setInterval(() => { + this.isCursorVisible = !this.isCursorVisible; + this._animationFrame = window.requestAnimationFrame(() => { + this.renderCallback(); this._animationFrame = null; - this._isVisible = true; - } + }); + }, BLINK_INTERVAL); + } + + public dispose(): void { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; } } private _restartBlinkAnimation(terminal: ITerminal): void { // TODO: Restart the blink animation when input is received // How can this be done efficiently, without thrashing with restarting the timers? + // Could record the time it was restarted and diff that on next render? } private _pauseBlinkAnimation(): void {