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.
This commit is contained in:
Daniel Imms
2023-08-01 06:13:21 -07:00
parent 18e578bd09
commit 6b33ac1e85
3 changed files with 13 additions and 147 deletions
@@ -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();
}
}
@@ -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();
}
@@ -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();
}
}