mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Properly support blinking cursors
This commit is contained in:
+2
-2
@@ -34,8 +34,8 @@ export class Buffer implements IBuffer {
|
||||
/**
|
||||
* Create a new Buffer.
|
||||
* @param _terminal The terminal the Buffer will belong to.
|
||||
* @param _hasScrollback Whether the buffer should respecr the scrollback of
|
||||
* the terminal..
|
||||
* @param _hasScrollback Whether the buffer should respect the scrollback of
|
||||
* the terminal.
|
||||
*/
|
||||
constructor(
|
||||
private _terminal: ITerminal,
|
||||
|
||||
@@ -189,6 +189,9 @@ export class Parser {
|
||||
let code;
|
||||
let low;
|
||||
|
||||
const cursorStartX = this._terminal.buffer.x;
|
||||
const cursorStartY = this._terminal.buffer.y;
|
||||
|
||||
if (this._terminal.debug) {
|
||||
this._terminal.log('data: ' + data);
|
||||
}
|
||||
@@ -580,6 +583,13 @@ export class Parser {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire the cursormove event if it's moved. This is done inside the parser
|
||||
// as a render cannot happen in the middle of a parsing round.
|
||||
if (this._terminal.buffer.x !== cursorStartX || this._terminal.buffer.y !== cursorStartY) {
|
||||
this._terminal.emit('cursormove');
|
||||
}
|
||||
|
||||
return this._state;
|
||||
}
|
||||
|
||||
|
||||
@@ -754,6 +754,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
|
||||
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
|
||||
this.renderer = new Renderer(this);
|
||||
this.on('cursormove', () => {
|
||||
console.log('cursormove fired');
|
||||
this.renderer.onCursorMove();
|
||||
});
|
||||
this.on('resize', () => this.renderer.onResize(this.cols, this.rows));
|
||||
this.charMeasure.on('charsizechanged', () => {
|
||||
this.renderer.onCharSizeChanged(this.charMeasure.width, this.charMeasure.height);
|
||||
|
||||
@@ -27,9 +27,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
}
|
||||
}
|
||||
|
||||
public onOptionsChanged(options: ITerminal): void {
|
||||
// TODO: Should this do anything?
|
||||
}
|
||||
public onOptionsChanged(options: ITerminal): void {}
|
||||
public onCursorMove(options: ITerminal): void {}
|
||||
|
||||
public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void {
|
||||
this.scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio;
|
||||
|
||||
@@ -15,23 +15,11 @@ const BLINK_INTERVAL = 600;
|
||||
export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLayer {
|
||||
private _state: [number, number];
|
||||
private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void};
|
||||
private _animationFrame: number;
|
||||
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;
|
||||
this._isVisible = true;
|
||||
this._cursorRenderers = {
|
||||
'bar': this._renderBarCursor.bind(this),
|
||||
'block': this._renderBlockCursor.bind(this),
|
||||
@@ -42,7 +30,11 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay
|
||||
|
||||
public reset(terminal: ITerminal): void {
|
||||
this._clearCursor();
|
||||
this._isVisible = true;
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.dispose();
|
||||
this._cursorBlinkStateManager = null;
|
||||
this.onOptionsChanged(terminal);
|
||||
}
|
||||
}
|
||||
|
||||
public onOptionsChanged(terminal: ITerminal): void {
|
||||
@@ -64,6 +56,12 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay
|
||||
}
|
||||
}
|
||||
|
||||
public onCursorMove(terminal: ITerminal): void {
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.restartBlinkAnimation(terminal);
|
||||
}
|
||||
}
|
||||
|
||||
public render(terminal: ITerminal, startRow: number, endRow: number): void {
|
||||
// Only render if the animation frame is not active
|
||||
if (!this._cursorBlinkStateManager) {
|
||||
@@ -131,20 +129,22 @@ class CursorBlinkStateManager {
|
||||
public isCursorVisible: boolean;
|
||||
|
||||
private _animationFrame: number;
|
||||
private _blinkStartTimeout: number;
|
||||
private _blinkInterval: number;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
constructor(
|
||||
terminal: ITerminal,
|
||||
private renderCallback: () => void
|
||||
) {
|
||||
this.isCursorVisible = true;
|
||||
this._blinkInterval = <number><any>setInterval(() => {
|
||||
this.isCursorVisible = !this.isCursorVisible;
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this.renderCallback();
|
||||
this._animationFrame = null;
|
||||
});
|
||||
}, BLINK_INTERVAL);
|
||||
this._restartInterval();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -156,10 +156,71 @@ class CursorBlinkStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
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?
|
||||
public restartBlinkAnimation(terminal: ITerminal): void {
|
||||
console.log('restartBlinkAnimation');
|
||||
// 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 = window.requestAnimationFrame(() => {
|
||||
this.renderCallback();
|
||||
this._animationFrame = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _restartInterval(timeToStart: number = BLINK_INTERVAL): void {
|
||||
// Clear any existing interval
|
||||
if (this._blinkInterval) {
|
||||
window.clearInterval(this._blinkInterval);
|
||||
}
|
||||
|
||||
console.log('restartInterval');
|
||||
// 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 = <number><any>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 = null;
|
||||
this._restartInterval(time);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('timeout');
|
||||
// Hide the cursor
|
||||
this.isCursorVisible = false;
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this.renderCallback();
|
||||
this._animationFrame = null;
|
||||
});
|
||||
|
||||
// Setup the blink interval
|
||||
this._blinkInterval = <number><any>setInterval(() => {
|
||||
console.log('interval');
|
||||
// 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 = null;
|
||||
console.log(' restart in ', time);
|
||||
this._restartInterval(time);
|
||||
return;
|
||||
}
|
||||
|
||||
// Invert visibility and render
|
||||
this.isCursorVisible = !this.isCursorVisible;
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this.renderCallback();
|
||||
this._animationFrame = null;
|
||||
});
|
||||
}, BLINK_INTERVAL);
|
||||
}, timeToStart);
|
||||
}
|
||||
|
||||
private _pauseBlinkAnimation(): void {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ITerminal, ITerminalOptions } from '../Interfaces';
|
||||
|
||||
export interface IRenderLayer {
|
||||
onCursorMove(options: ITerminal): void;
|
||||
onOptionsChanged(options: ITerminal): void;
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,6 +56,15 @@ export class Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
public onCursorMove(): void {
|
||||
for (let i = 0; i < this._dataRenderLayers.length; i++) {
|
||||
this._dataRenderLayers[i].onCursorMove(this._terminal);
|
||||
}
|
||||
for (let i = 0; i < this._selectionRenderLayers.length; i++) {
|
||||
this._selectionRenderLayers[i].onCursorMove(this._terminal);
|
||||
}
|
||||
}
|
||||
|
||||
public onOptionsChanged(): void {
|
||||
for (let i = 0; i < this._dataRenderLayers.length; i++) {
|
||||
this._dataRenderLayers[i].onOptionsChanged(this._terminal);
|
||||
|
||||
Reference in New Issue
Block a user