Files
xterm.js/src/renderer/Renderer.ts
T

115 lines
4.0 KiB
TypeScript

/**
* @license MIT
*/
import { ITerminal } from '../Interfaces';
import { DomElementObjectPool } from '../utils/DomElementObjectPool';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
import { createBackgroundFillData } from './Canvas';
import { IDataRenderLayer, ISelectionRenderLayer } from './Interfaces';
import { BackgroundRenderLayer } from './BackgroundRenderLayer';
import { ForegroundRenderLayer } from './ForegroundRenderLayer';
import { SelectionRenderLayer } from './SelectionRenderLayer';
import { CursorRenderLayer } from './CursorRenderLayer';
export class Renderer {
/** A queue of the rows to be refreshed */
private _refreshRowsQueue: {start: number, end: number}[] = [];
private _refreshAnimationFrame = null;
private _dataRenderLayers: IDataRenderLayer[];
private _selectionRenderLayers: ISelectionRenderLayer[];
constructor(private _terminal: ITerminal) {
this._dataRenderLayers = [
new BackgroundRenderLayer(this._terminal.element, 0),
new ForegroundRenderLayer(this._terminal.element, 2),
new CursorRenderLayer(this._terminal.element, 3)
];
this._selectionRenderLayers = [
new SelectionRenderLayer(this._terminal.element, 1)
];
}
public onResize(cols: number, rows: number): void {
const width = this._terminal.charMeasure.width * this._terminal.cols;
const height = this._terminal.charMeasure.height * this._terminal.rows;
for (let i = 0; i < this._dataRenderLayers.length; i++) {
this._dataRenderLayers[i].resize(this._terminal, width, height, false);
}
}
public onCharSizeChanged(charWidth: number, charHeight: number): void {
const width = charWidth * this._terminal.cols;
const height = charHeight * this._terminal.rows;
for (let i = 0; i < this._dataRenderLayers.length; i++) {
this._dataRenderLayers[i].resize(this._terminal, width, height, true);
}
for (let i = 0; i < this._selectionRenderLayers.length; i++) {
this._selectionRenderLayers[i].resize(this._terminal, width, height, true);
}
}
public onSelectionChanged(start: [number, number], end: [number, number]): void {
for (let i = 0; i < this._selectionRenderLayers.length; i++) {
this._selectionRenderLayers[i].render(this._terminal, start, end);
}
}
public clear(): void {
for (let i = 0; i < this._dataRenderLayers.length; i++) {
this._dataRenderLayers[i].reset(this._terminal);
}
for (let i = 0; i < this._selectionRenderLayers.length; i++) {
this._selectionRenderLayers[i].reset(this._terminal);
}
}
/**
* Queues a refresh between two rows (inclusive), to be done on next animation
* frame.
* @param {number} start The start row.
* @param {number} end The end row.
*/
public queueRefresh(start: number, end: number): void {
this._refreshRowsQueue.push({ start: start, end: end });
if (!this._refreshAnimationFrame) {
this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));
}
}
/**
* Performs the refresh loop callback, calling refresh only if a refresh is
* necessary before queueing up the next one.
*/
private _refreshLoop(): void {
let start;
let end;
if (this._refreshRowsQueue.length > 4) {
// Just do a full refresh when 5+ refreshes are queued
start = 0;
end = this._terminal.rows - 1;
} else {
// Get start and end rows that need refreshing
start = this._refreshRowsQueue[0].start;
end = this._refreshRowsQueue[0].end;
for (let i = 1; i < this._refreshRowsQueue.length; i++) {
if (this._refreshRowsQueue[i].start < start) {
start = this._refreshRowsQueue[i].start;
}
if (this._refreshRowsQueue[i].end > end) {
end = this._refreshRowsQueue[i].end;
}
}
}
this._refreshRowsQueue = [];
this._refreshAnimationFrame = null;
// Render
for (let i = 0; i < this._dataRenderLayers.length; i++) {
this._dataRenderLayers[i].render(this._terminal, start, end);
}
this._terminal.emit('refresh', {start, end});
}
}