mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Pull render animation frame logic into a helper class
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
import { ITerminal, IBuffer, IDisposable } from './Interfaces';
|
||||
import { isMac } from './utils/Browser';
|
||||
import { RenderDebouncer } from './utils/RenderDebouncer';
|
||||
|
||||
const MAX_ROWS_TO_READ = 20;
|
||||
|
||||
@@ -15,9 +16,7 @@ export class AccessibilityManager implements IDisposable {
|
||||
private _liveRegion: HTMLElement;
|
||||
private _liveRegionLineCount: number = 0;
|
||||
|
||||
private _refreshRowStart: number;
|
||||
private _refreshRowEnd: number;
|
||||
private _refreshAnimationFrame: number = null;
|
||||
private _renderRowsDebouncer: RenderDebouncer;
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
@@ -44,6 +43,8 @@ export class AccessibilityManager implements IDisposable {
|
||||
this._refreshRowsDimensions();
|
||||
this._accessibilityTreeRoot.appendChild(this._rowContainer);
|
||||
|
||||
this._renderRowsDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this));
|
||||
|
||||
this._liveRegion = document.createElement('div');
|
||||
this._liveRegion.classList.add('live-region');
|
||||
this._liveRegion.setAttribute('aria-live', 'assertive');
|
||||
@@ -80,10 +81,7 @@ export class AccessibilityManager implements IDisposable {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._refreshAnimationFrame) {
|
||||
window.cancelAnimationFrame(this._refreshAnimationFrame);
|
||||
this._refreshAnimationFrame = null;
|
||||
}
|
||||
this._renderRowsDebouncer.dispose();
|
||||
this._terminal.element.removeChild(this._accessibilityTreeRoot);
|
||||
this._accessibilityTreeRoot = null;
|
||||
this._rowContainer = null;
|
||||
@@ -169,27 +167,15 @@ export class AccessibilityManager implements IDisposable {
|
||||
}
|
||||
|
||||
private _refreshRows(start?: number, end?: number): void {
|
||||
start = start || 0;
|
||||
end = end || this._terminal.rows - 1;
|
||||
this._refreshRowStart = this._refreshRowStart ? Math.min(this._refreshRowStart, start) : start;
|
||||
this._refreshRowEnd = this._refreshRowEnd ? Math.max(this._refreshRowEnd, end) : end;
|
||||
|
||||
if (this._refreshAnimationFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._refreshAnimationFrame = window.requestAnimationFrame(() => this._innerRefreshRows());
|
||||
this._renderRowsDebouncer.refresh(start, end);
|
||||
}
|
||||
|
||||
private _innerRefreshRows(): void {
|
||||
private _renderRows(start: number, end: number): void {
|
||||
const buffer: IBuffer = (<any>this._terminal.buffer);
|
||||
for (let i = this._refreshRowStart; i <= this._refreshRowEnd; i++) {
|
||||
for (let i = start; i <= end; i++) {
|
||||
const lineData = buffer.translateBufferLineToString(buffer.ybase + i, true);
|
||||
this._rowElements[i].textContent = lineData;
|
||||
}
|
||||
this._refreshRowStart = null;
|
||||
this._refreshRowEnd = null;
|
||||
this._refreshAnimationFrame = null;
|
||||
}
|
||||
|
||||
private _refreshRowsDimensions(): void {
|
||||
|
||||
@@ -13,11 +13,10 @@ import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Interfaces';
|
||||
import { LinkRenderLayer } from './LinkRenderLayer';
|
||||
import { EventEmitter } from '../EventEmitter';
|
||||
import { RenderDebouncer } from '../utils/RenderDebouncer';
|
||||
|
||||
export class Renderer extends EventEmitter implements IRenderer {
|
||||
/** A queue of the rows to be refreshed */
|
||||
private _refreshRowsQueue: {start: number, end: number}[] = [];
|
||||
private _refreshAnimationFrame: number = null;
|
||||
private _renderDebouncer: RenderDebouncer;
|
||||
|
||||
private _renderLayers: IRenderLayer[];
|
||||
private _devicePixelRatio: number;
|
||||
@@ -53,6 +52,7 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
};
|
||||
this._devicePixelRatio = window.devicePixelRatio;
|
||||
this._updateDimensions();
|
||||
this._renderDebouncer = new RenderDebouncer(this._terminal, this._refreshLoop.bind(this));
|
||||
}
|
||||
|
||||
public onWindowResize(devicePixelRatio: number): void {
|
||||
@@ -129,42 +129,14 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
* @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));
|
||||
}
|
||||
this._renderDebouncer.refresh(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
start = Math.max(start, 0);
|
||||
end = Math.min(end, this._terminal.rows - 1);
|
||||
private _refreshLoop(start: number, end: number): void {
|
||||
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
|
||||
this._terminal.emit('refresh', {start, end});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { ITerminal, IDisposable } from '../Interfaces';
|
||||
|
||||
/**
|
||||
* Debounces calls to render terminal rows using animation frames.
|
||||
*/
|
||||
export class RenderDebouncer implements IDisposable {
|
||||
private _rowStart: number;
|
||||
private _rowEnd: number;
|
||||
private _animationFrame: number = null;
|
||||
|
||||
constructor(
|
||||
private _terminal: ITerminal,
|
||||
private _callback: (start: number, end: number) => void
|
||||
) {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._animationFrame) {
|
||||
window.cancelAnimationFrame(this._animationFrame);
|
||||
this._animationFrame = null;
|
||||
}
|
||||
}
|
||||
|
||||
public refresh(rowStart?: number, rowEnd?: number): void {
|
||||
rowStart = rowStart || 0;
|
||||
rowEnd = rowEnd || this._terminal.rows - 1;
|
||||
this._rowStart = this._rowStart ? Math.min(this._rowStart, rowStart) : rowStart;
|
||||
this._rowEnd = this._rowEnd ? Math.max(this._rowEnd, rowEnd) : rowEnd;
|
||||
|
||||
if (this._animationFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh());
|
||||
}
|
||||
|
||||
private _innerRefresh(): void {
|
||||
// Clamp values
|
||||
this._rowStart = Math.max(this._rowStart, 0);
|
||||
this._rowEnd = Math.min(this._rowEnd, this._terminal.rows - 1);
|
||||
|
||||
// Run render callback
|
||||
this._callback(this._rowStart, this._rowEnd);
|
||||
|
||||
// Reset debouncer
|
||||
this._rowStart = null;
|
||||
this._rowEnd = null;
|
||||
this._animationFrame = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user