mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Add basic support for cursor blinking
This commit is contained in:
@@ -497,6 +497,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
case 'bellSound':
|
||||
case 'bellStyle': this.syncBellSound(); break;
|
||||
}
|
||||
this.renderer.onOptionsChanged();
|
||||
}
|
||||
|
||||
private restartCursorBlinking(): void {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IRenderLayer } from './Interfaces';
|
||||
import { ITerminal } from '../Interfaces';
|
||||
import { ITerminal, ITerminalOptions } from '../Interfaces';
|
||||
import { COLORS } from './Color';
|
||||
|
||||
export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
@@ -27,6 +27,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
}
|
||||
}
|
||||
|
||||
public onOptionsChanged(options: ITerminal): void {
|
||||
// TODO: Should this do anything?
|
||||
}
|
||||
|
||||
public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void {
|
||||
this.scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio;
|
||||
this.scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio;
|
||||
@@ -56,6 +60,22 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this._ctx.fillRect(startCol * this.scaledCharWidth, startRow * this.scaledCharHeight, colWidth * this.scaledCharWidth, colHeight * this.scaledCharHeight);
|
||||
}
|
||||
|
||||
protected fillBottomLineAtCell(x: number, y: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this.scaledCharWidth,
|
||||
(y + 1) * this.scaledCharHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
|
||||
this.scaledCharWidth,
|
||||
window.devicePixelRatio);
|
||||
}
|
||||
|
||||
protected fillLeftLineAtCell(x: number, y: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this.scaledCharWidth,
|
||||
y * this.scaledCharHeight,
|
||||
window.devicePixelRatio,
|
||||
this.scaledCharHeight);
|
||||
}
|
||||
|
||||
protected clearAll(): void {
|
||||
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,57 @@
|
||||
import { IDataRenderLayer } from './Interfaces';
|
||||
import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces';
|
||||
import { IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces';
|
||||
import { CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
|
||||
import { COLORS, COLOR_CODES } from './Color';
|
||||
import { GridCache } from './GridCache';
|
||||
import { FLAGS } from './Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { CharData } from '../Types';
|
||||
|
||||
/**
|
||||
* The time between cursor blinks.
|
||||
*/
|
||||
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;
|
||||
|
||||
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),
|
||||
'underline': this._renderUnderlineCursor.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
public reset(terminal: ITerminal): void {
|
||||
this._clearCursor();
|
||||
this._isVisible = true;
|
||||
}
|
||||
|
||||
public onOptionsChanged(terminal: ITerminal): void {
|
||||
super.onOptionsChanged(terminal);
|
||||
this._refreshBlinkState(terminal);
|
||||
}
|
||||
|
||||
public render(terminal: ITerminal, startRow: number, endRow: number): void {
|
||||
// Only render if the animation frame is not active
|
||||
if (!this._blinkInterval) {
|
||||
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) {
|
||||
if (!terminal.cursorState || terminal.cursorHidden || !this._isVisible) {
|
||||
this._clearCursor();
|
||||
return;
|
||||
}
|
||||
@@ -44,14 +73,11 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay
|
||||
this._clearCursor();
|
||||
}
|
||||
|
||||
const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x];
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = COLORS[COLOR_CODES.WHITE];
|
||||
this.fillCells(terminal.buffer.x, viewportRelativeCursorY, 1, 1);
|
||||
this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData);
|
||||
this._ctx.restore();
|
||||
|
||||
const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x];
|
||||
this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], <number>charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, terminal.buffer.x, viewportRelativeCursorY);
|
||||
|
||||
this._state = [terminal.buffer.x, viewportRelativeCursorY];
|
||||
}
|
||||
|
||||
@@ -61,4 +87,52 @@ export class CursorRenderLayer extends BaseRenderLayer implements IDataRenderLay
|
||||
this._state = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {
|
||||
this.fillLeftLineAtCell(x, y);
|
||||
}
|
||||
|
||||
private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {
|
||||
this.fillCells(x, y, 1, 1);
|
||||
this.drawChar(terminal, charData[CHAR_DATA_CHAR_INDEX], <number>charData[CHAR_DATA_CODE_INDEX], COLOR_CODES.BLACK, x, y);
|
||||
}
|
||||
|
||||
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 = <number><any>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);
|
||||
this._animationFrame = null;
|
||||
this._isVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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?
|
||||
}
|
||||
|
||||
private _pauseBlinkAnimation(): void {
|
||||
// TODO: Pause the blink animation on blur
|
||||
}
|
||||
|
||||
private _resumeBlinkAnimation(): void {
|
||||
// TODO: Resume the blink animation on focus
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ITerminal } from '../Interfaces';
|
||||
import { ITerminal, ITerminalOptions } from '../Interfaces';
|
||||
|
||||
export interface IRenderLayer {
|
||||
onOptionsChanged(options: ITerminal): void;
|
||||
|
||||
/**
|
||||
* Resize the render layer.
|
||||
*/
|
||||
@@ -12,10 +14,16 @@ export interface IRenderLayer {
|
||||
reset(terminal: ITerminal): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A render layer that renders when there is a data change.
|
||||
*/
|
||||
export interface IDataRenderLayer extends IRenderLayer {
|
||||
render(terminal: ITerminal, startRow: number, endRow: number): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A render layer that renders when there is a selection change.
|
||||
*/
|
||||
export interface ISelectionRenderLayer extends IRenderLayer {
|
||||
render(terminal: ITerminal, start: [number, number], end: [number, number]): void;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,15 @@ export class Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
public onOptionsChanged(): void {
|
||||
for (let i = 0; i < this._dataRenderLayers.length; i++) {
|
||||
this._dataRenderLayers[i].onOptionsChanged(this._terminal);
|
||||
}
|
||||
for (let i = 0; i < this._selectionRenderLayers.length; i++) {
|
||||
this._selectionRenderLayers[i].onOptionsChanged(this._terminal);
|
||||
}
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
for (let i = 0; i < this._dataRenderLayers.length; i++) {
|
||||
this._dataRenderLayers[i].reset(this._terminal);
|
||||
|
||||
Reference in New Issue
Block a user