From 8792bb5f363033c612c732b0bd34db7dee9e9390 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 18:47:44 -0700 Subject: [PATCH 01/12] Introduce a RenderCoordinator that owns common Renderer logic Part of #2005 --- src/Terminal.ts | 13 +++++++++++-- src/TestUtils.test.ts | 2 +- src/renderer/RenderCoordinator.ts | 30 ++++++++++++++++++++++++++++++ src/renderer/Renderer.ts | 20 ++------------------ src/renderer/Types.ts | 2 +- src/renderer/dom/DomRenderer.ts | 10 ++-------- 6 files changed, 47 insertions(+), 30 deletions(-) create mode 100644 src/renderer/RenderCoordinator.ts diff --git a/src/Terminal.ts b/src/Terminal.ts index 60409859..b241cc23 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -54,6 +54,7 @@ import { EventEmitter2, IEvent } from './common/EventEmitter2'; import { Attributes, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; import { ColorManager } from './ui/ColorManager'; +import { RenderCoordinator } from './renderer/RenderCoordinator'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -203,6 +204,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _inputHandler: InputHandler; public soundManager: SoundManager; + private _renderCoordinator: RenderCoordinator; public renderer: IRenderer; public selectionManager: SelectionManager; public linkifier: ILinkifier; @@ -770,6 +772,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._colorManager.setTheme(this._theme); this._setupRenderer(); + this._renderCoordinator = new RenderCoordinator(this.renderer, this.rows); + this.onResize(e => this._renderCoordinator.resize(e.cols, e.rows)); + this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure); this.viewport.onThemeChange(this._colorManager.colors); this.register(this.viewport); @@ -839,6 +844,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II case 'dom': this.renderer = new DomRenderer(this, this._colorManager.colors); break; default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } + // TODO: Setting of renderer should be owned by RenderCoordinator + if (this._renderCoordinator) { + this._renderCoordinator.setRenderer(this.renderer); + } this.renderer.onRender(e => this._onRender.fire(e)); this.register(this.renderer); } @@ -1207,8 +1216,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param end The row to end at (between start and this.rows - 1). */ public refresh(start: number, end: number): void { - if (this.renderer) { - this.renderer.refreshRows(start, end); + if (this._renderCoordinator) { + this._renderCoordinator.refreshRows(start, end); } } diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index d3ed93a3..86745fc7 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -396,7 +396,7 @@ export class MockRenderer implements IRenderer { onOptionsChanged(): void {} onWindowResize(devicePixelRatio: number): void {} clear(): void {} - refreshRows(start: number, end: number): void {} + renderRows(start: number, end: number): void {} registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; } deregisterCharacterJoiner(): boolean { return true; } } diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderCoordinator.ts new file mode 100644 index 00000000..0a4d4b99 --- /dev/null +++ b/src/renderer/RenderCoordinator.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IRenderer } from './Types'; +import { RenderDebouncer } from '../ui/RenderDebouncer'; + +export class RenderCoordinator { + private _renderDebouncer: RenderDebouncer; + + constructor( + private _renderer: IRenderer, + private _rowCount: number + ) { + this._renderDebouncer = new RenderDebouncer((start, end) => this._renderer.renderRows(start, end)); + } + + public refreshRows(start: number, end: number): void { + this._renderDebouncer.refresh(start, end, this._rowCount); + } + + public resize(cols: number, rows: number): void { + this._rowCount = rows; + } + + public setRenderer(renderer: IRenderer): void { + this._renderer = renderer; + } +} diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 527c5966..98fc50fc 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -9,7 +9,6 @@ import { CursorRenderLayer } from './CursorRenderLayer'; import { IRenderLayer, IRenderer, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { ITerminal, CharacterJoinerHandler } from '../Types'; import { LinkRenderLayer } from './LinkRenderLayer'; -import { RenderDebouncer } from '../ui/RenderDebouncer'; import { ScreenDprMonitor } from '../ui/ScreenDprMonitor'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; import { EventEmitter2, IEvent } from '../common/EventEmitter2'; @@ -17,7 +16,6 @@ import { Disposable } from '../common/Lifecycle'; import { IColorSet } from '../ui/Types'; export class Renderer extends Disposable implements IRenderer { - private _renderDebouncer: RenderDebouncer; private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; @@ -65,7 +63,7 @@ export class Renderer extends Disposable implements IRenderer { this._updateDimensions(); this.onOptionsChanged(); - this._renderDebouncer = new RenderDebouncer(this._renderRows.bind(this)); + // TODO: Move dpr monitor to RenderCoordinator this._screenDprMonitor = new ScreenDprMonitor(); this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); this.register(this._screenDprMonitor); @@ -175,25 +173,11 @@ export class Renderer extends Disposable implements IRenderer { } } - /** - * Queues a refresh between two rows (inclusive), to be done on next animation - * frame. - * @param start The start row. - * @param end The end row. - */ - public refreshRows(start: number, end: number): void { - if (this._isPaused) { - this._needsFullRefresh = true; - return; - } - this._renderDebouncer.refresh(start, end, this._terminal.rows); - } - /** * Performs the refresh loop callback, calling refresh only if a refresh is * necessary before queueing up the next one. */ - private _renderRows(start: number, end: number): void { + public renderRows(start: number, end: number): void { this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); this._onRender.fire({ start, end }); } diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 590df0e0..3b00820a 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -42,7 +42,7 @@ export interface IRenderer extends IDisposable { onCursorMove(): void; onOptionsChanged(): void; clear(): void; - refreshRows(start: number, end: number): void; + renderRows(start: number, end: number): void; registerCharacterJoiner(handler: CharacterJoinerHandler): number; deregisterCharacterJoiner(joinerId: number): boolean; } diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 6f46f0ec..d0c4e20c 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -5,7 +5,6 @@ import { IRenderer, IRenderDimensions } from '../Types'; import { ILinkifierEvent, ITerminal, CharacterJoinerHandler } from '../../Types'; -import { RenderDebouncer } from '../../ui/RenderDebouncer'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; @@ -30,7 +29,6 @@ let nextTerminalId = 1; * canvas is not an option. */ export class DomRenderer extends Disposable implements IRenderer { - private _renderDebouncer: RenderDebouncer; private _rowFactory: DomRendererRowFactory; private _terminalClass: number = nextTerminalId++; @@ -42,6 +40,7 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; + // TODO: These events should be owned by RenderCoordinator private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } private _onRender = new EventEmitter2<{ start: number, end: number }>(); @@ -78,7 +77,6 @@ export class DomRenderer extends Disposable implements IRenderer { }; this._updateDimensions(); - this._renderDebouncer = new RenderDebouncer(this._renderRows.bind(this)); this._rowFactory = new DomRendererRowFactory(_terminal.options, document); this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); @@ -337,11 +335,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._rowElements.forEach(e => e.innerHTML = ''); } - public refreshRows(start: number, end: number): void { - this._renderDebouncer.refresh(start, end, this._terminal.rows); - } - - private _renderRows(start: number, end: number): void { + public renderRows(start: number, end: number): void { const terminal = this._terminal; const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y; From f698cafccb9907c82c32dbe0613ed40965cfc357 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 19:02:42 -0700 Subject: [PATCH 02/12] Move the renderer events to RenderCoordinator --- src/AccessibilityManager.ts | 11 +++++----- src/Terminal.ts | 5 +++-- src/renderer/RenderCoordinator.ts | 35 ++++++++++++++++++++++++++++++- src/renderer/Renderer.ts | 13 ------------ src/renderer/Types.ts | 4 ---- src/renderer/dom/DomRenderer.ts | 13 ------------ 6 files changed, 42 insertions(+), 39 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 4351a336..aabb5b6f 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -58,7 +58,7 @@ export class AccessibilityManager extends Disposable { this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); - this._refreshRowsDimensions(); + this.refreshRowsDimensions(); this._accessibilityTreeRoot.appendChild(this._rowContainer); this._renderRowsDebouncer = new RenderDebouncer(this._renderRows.bind(this)); @@ -84,11 +84,10 @@ export class AccessibilityManager extends Disposable { // TODO: Maybe renderer should fire an event on terminal when the characters change and that // should be listened to instead? That would mean that the order of events are always // guarenteed - this.register(this._terminal.addDisposableListener('dprchange', () => this._refreshRowsDimensions())); - this.register(this._terminal.renderer.onCanvasResize(() => this._refreshRowsDimensions())); + this.register(this._terminal.addDisposableListener('dprchange', () => this.refreshRowsDimensions())); // This shouldn't be needed on modern browsers but is present in case the // media query that drives the dprchange event isn't supported - this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions())); + this.register(addDisposableDomListener(window, 'resize', () => this.refreshRowsDimensions())); } public dispose(): void { @@ -174,7 +173,7 @@ export class AccessibilityManager extends Disposable { // Add bottom boundary listener this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); - this._refreshRowsDimensions(); + this.refreshRowsDimensions(); } private _createAccessibilityTreeNode(): HTMLElement { @@ -257,7 +256,7 @@ export class AccessibilityManager extends Disposable { } } - private _refreshRowsDimensions(): void { + public refreshRowsDimensions(): void { if (!this._terminal.renderer.dimensions.actualCellHeight) { return; } diff --git a/src/Terminal.ts b/src/Terminal.ts index b241cc23..4f91d27e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -773,6 +773,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._setupRenderer(); this._renderCoordinator = new RenderCoordinator(this.renderer, this.rows); + this._renderCoordinator.onRender(e => this._onRender.fire(e)); this.onResize(e => this._renderCoordinator.resize(e.cols, e.rows)); this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure); @@ -788,7 +789,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // matchMedia query. this.register(addDisposableDomListener(window, 'resize', () => this.renderer.onWindowResize(window.devicePixelRatio))); this.register(this.charMeasure.onCharSizeChanged(() => this.renderer.onCharSizeChanged())); - this.register(this.renderer.onCanvasResize(() => this.viewport.syncScrollArea())); + this.register(this._renderCoordinator.onCanvasResize(() => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this.charMeasure); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); @@ -821,6 +822,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // Note that this must be done *after* the renderer is created in order to // ensure the correct order of the dprchange event this._accessibilityManager = new AccessibilityManager(this); + this._accessibilityManager.register(this._renderCoordinator.onCanvasResize(() => this._accessibilityManager.refreshRowsDimensions())); } // Measure the character size @@ -848,7 +850,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (this._renderCoordinator) { this._renderCoordinator.setRenderer(this.renderer); } - this.renderer.onRender(e => this._onRender.fire(e)); this.register(this.renderer); } diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderCoordinator.ts index 0a4d4b99..8ee3fb69 100644 --- a/src/renderer/RenderCoordinator.ts +++ b/src/renderer/RenderCoordinator.ts @@ -5,23 +5,56 @@ import { IRenderer } from './Types'; import { RenderDebouncer } from '../ui/RenderDebouncer'; +import { EventEmitter2, IEvent } from '../common/EventEmitter2'; export class RenderCoordinator { private _renderDebouncer: RenderDebouncer; + private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); + public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } + private _onRender = new EventEmitter2<{ start: number, end: number }>(); + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + + private _canvasWidth: number = 0; + private _canvasHeight: number = 0; + constructor( private _renderer: IRenderer, private _rowCount: number ) { - this._renderDebouncer = new RenderDebouncer((start, end) => this._renderer.renderRows(start, end)); + this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end)); } public refreshRows(start: number, end: number): void { this._renderDebouncer.refresh(start, end, this._rowCount); } + private _renderRows(start: number, end: number): void { + this._renderer.renderRows(start, end); + this._onRender.fire({ start, end }); + } + public resize(cols: number, rows: number): void { this._rowCount = rows; + this._fireOnCanvasResize(); + } + + public changeOptions(): void { + this._renderer.onOptionsChanged(); + this._fireOnCanvasResize(); + } + + private _fireOnCanvasResize(): void { + // Don't fire the event if the dimensions haven't changed + if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) { + return; + } + this._canvasWidth = this._renderer.dimensions.canvasWidth; + this._canvasHeight = this._renderer.dimensions.canvasHeight; + this._onCanvasResize.fire({ + width: this._canvasWidth, + height: this._canvasHeight + }); } public setRenderer(renderer: IRenderer): void { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 98fc50fc..240e04ec 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -11,12 +11,10 @@ import { ITerminal, CharacterJoinerHandler } from '../Types'; import { LinkRenderLayer } from './LinkRenderLayer'; import { ScreenDprMonitor } from '../ui/ScreenDprMonitor'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; -import { EventEmitter2, IEvent } from '../common/EventEmitter2'; import { Disposable } from '../common/Lifecycle'; import { IColorSet } from '../ui/Types'; export class Renderer extends Disposable implements IRenderer { - private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; private _screenDprMonitor: ScreenDprMonitor; @@ -26,11 +24,6 @@ export class Renderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); - public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } - private _onRender = new EventEmitter2<{ start: number, end: number }>(); - public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } - constructor( private _terminal: ITerminal, private _colors: IColorSet @@ -130,11 +123,6 @@ export class Renderer extends Disposable implements IRenderer { // Resize the screen this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; - - this._onCanvasResize.fire({ - width: this.dimensions.canvasWidth, - height: this.dimensions.canvasHeight - }); } public onCharSizeChanged(): void { @@ -179,7 +167,6 @@ export class Renderer extends Disposable implements IRenderer { */ public renderRows(start: number, end: number): void { this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); - this._onRender.fire({ start, end }); } /** diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 3b00820a..d08360ef 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -5,7 +5,6 @@ import { ITerminal, CharacterJoinerHandler } from '../Types'; import { IDisposable } from 'xterm'; -import { IEvent } from '../common/EventEmitter2'; import { IColorSet } from '../ui/Types'; /** @@ -28,9 +27,6 @@ export const enum FLAGS { export interface IRenderer extends IDisposable { dimensions: IRenderDimensions; - onCanvasResize: IEvent<{ width: number, height: number }>; - onRender: IEvent<{ start: number, end: number }>; - dispose(): void; onThemeChange(colors: IColorSet): void; onWindowResize(devicePixelRatio: number): void; diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index d0c4e20c..d1f877e5 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -7,7 +7,6 @@ import { IRenderer, IRenderDimensions } from '../Types'; import { ILinkifierEvent, ITerminal, CharacterJoinerHandler } from '../../Types'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; -import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; import { Disposable } from '../../common/Lifecycle'; import { IColorSet } from '../../ui/Types'; @@ -40,12 +39,6 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - // TODO: These events should be owned by RenderCoordinator - private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); - public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } - private _onRender = new EventEmitter2<{ start: number, end: number }>(); - public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } - constructor( private _terminal: ITerminal, private _colors: IColorSet @@ -240,10 +233,6 @@ export class DomRenderer extends Disposable implements IRenderer { public onResize(cols: number, rows: number): void { this._refreshRowElements(cols, rows); this._updateDimensions(); - this._onCanvasResize.fire({ - width: this.dimensions.canvasWidth, - height: this.dimensions.canvasHeight - }); } public onCharSizeChanged(): void { @@ -351,8 +340,6 @@ export class DomRenderer extends Disposable implements IRenderer { const cursorStyle = terminal.options.cursorStyle; rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols)); } - - this._onRender.fire({ start, end }); } private get _terminalSelector(): string { From 3e4c1b5a21ea4f3d783e705e13133eaba6384f75 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 19:12:37 -0700 Subject: [PATCH 03/12] Move ScreenDprMonitor to be owned by RenderCoordinator (and a11y manager) --- src/AccessibilityManager.ts | 12 +++++++----- src/Terminal.ts | 7 ------- src/renderer/RenderCoordinator.ts | 11 ++++++++++- src/renderer/Renderer.ts | 7 ------- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index aabb5b6f..bc1dcdd0 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -9,6 +9,7 @@ import { isMac } from './common/Platform'; import { RenderDebouncer } from './ui/RenderDebouncer'; import { addDisposableDomListener } from './ui/Lifecycle'; import { Disposable } from './common/Lifecycle'; +import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; const MAX_ROWS_TO_READ = 20; @@ -25,6 +26,7 @@ export class AccessibilityManager extends Disposable { private _liveRegionLineCount: number = 0; private _renderRowsDebouncer: RenderDebouncer; + private _screenDprMonitor: ScreenDprMonitor; private _topBoundaryFocusListener: (e: FocusEvent) => void; private _bottomBoundaryFocusListener: (e: FocusEvent) => void; @@ -81,12 +83,12 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount))); this.register(this._terminal.onKey(e => this._onKey(e.key))); this.register(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion())); - // TODO: Maybe renderer should fire an event on terminal when the characters change and that - // should be listened to instead? That would mean that the order of events are always - // guarenteed - this.register(this._terminal.addDisposableListener('dprchange', () => this.refreshRowsDimensions())); + + this._screenDprMonitor = new ScreenDprMonitor(); + this.register(this._screenDprMonitor); + this._screenDprMonitor.setListener(() => this.refreshRowsDimensions()); // This shouldn't be needed on modern browsers but is present in case the - // media query that drives the dprchange event isn't supported + // media query that drives the ScreenDprMonitor isn't supported this.register(addDisposableDomListener(window, 'resize', () => this.refreshRowsDimensions())); } diff --git a/src/Terminal.ts b/src/Terminal.ts index 4f91d27e..d82b5bf3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -42,7 +42,6 @@ import { MouseHelper } from './MouseHelper'; import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; -import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; @@ -216,7 +215,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public mouseHelper: MouseHelper; private _accessibilityManager: AccessibilityManager; private _colorManager: ColorManager; - private _screenDprMonitor: ScreenDprMonitor; private _theme: ITheme; private _windowsMode: IDisposable | undefined; @@ -707,10 +705,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._context = this._parent.ownerDocument.defaultView; this._document = this._parent.ownerDocument; - this._screenDprMonitor = new ScreenDprMonitor(); - this._screenDprMonitor.setListener(() => this.emit('dprchange', window.devicePixelRatio)); - this.register(this._screenDprMonitor); - // Create main element container this.element = this._document.createElement('div'); this.element.dir = 'ltr'; // xterm.css assumes LTR @@ -784,7 +778,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.onResize(() => this.renderer.onResize(this.cols, this.rows))); this.register(this.addDisposableListener('blur', () => this.renderer.onBlur())); this.register(this.addDisposableListener('focus', () => this.renderer.onFocus())); - this.register(this.addDisposableListener('dprchange', () => this.renderer.onWindowResize(window.devicePixelRatio))); // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. this.register(addDisposableDomListener(window, 'resize', () => this.renderer.onWindowResize(window.devicePixelRatio))); diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderCoordinator.ts index 8ee3fb69..3b9eed2d 100644 --- a/src/renderer/RenderCoordinator.ts +++ b/src/renderer/RenderCoordinator.ts @@ -6,9 +6,12 @@ import { IRenderer } from './Types'; import { RenderDebouncer } from '../ui/RenderDebouncer'; import { EventEmitter2, IEvent } from '../common/EventEmitter2'; +import { Disposable } from '../common/Lifecycle'; +import { ScreenDprMonitor } from '../../lib/ui/ScreenDprMonitor'; -export class RenderCoordinator { +export class RenderCoordinator extends Disposable { private _renderDebouncer: RenderDebouncer; + private _screenDprMonitor: ScreenDprMonitor; private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } @@ -22,7 +25,13 @@ export class RenderCoordinator { private _renderer: IRenderer, private _rowCount: number ) { + super(); this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end)); + this.register(this._renderDebouncer); + + this._screenDprMonitor = new ScreenDprMonitor(); + this._screenDprMonitor.setListener(() => this._renderer.onWindowResize(window.devicePixelRatio)); + this.register(this._screenDprMonitor); } public refreshRows(start: number, end: number): void { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 240e04ec..754020fd 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -9,7 +9,6 @@ import { CursorRenderLayer } from './CursorRenderLayer'; import { IRenderLayer, IRenderer, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { ITerminal, CharacterJoinerHandler } from '../Types'; import { LinkRenderLayer } from './LinkRenderLayer'; -import { ScreenDprMonitor } from '../ui/ScreenDprMonitor'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; import { Disposable } from '../common/Lifecycle'; import { IColorSet } from '../ui/Types'; @@ -17,7 +16,6 @@ import { IColorSet } from '../ui/Types'; export class Renderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; - private _screenDprMonitor: ScreenDprMonitor; private _isPaused: boolean = false; private _needsFullRefresh: boolean = false; private _characterJoinerRegistry: ICharacterJoinerRegistry; @@ -56,11 +54,6 @@ export class Renderer extends Disposable implements IRenderer { this._updateDimensions(); this.onOptionsChanged(); - // TODO: Move dpr monitor to RenderCoordinator - this._screenDprMonitor = new ScreenDprMonitor(); - this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); - this.register(this._screenDprMonitor); - // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so if ('IntersectionObserver' in window) { From b64cc0a9de04771df5c499c571682ac8e55c914d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 19:16:06 -0700 Subject: [PATCH 04/12] Fix ui project build --- src/ui/ColorManager.ts | 13 ++++++++----- src/ui/Types.ts | 24 ++++++++++++++++++++++++ src/ui/tsconfig.json | 8 ++------ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/ui/ColorManager.ts b/src/ui/ColorManager.ts index 6d68c58d..923b2823 100644 --- a/src/ui/ColorManager.ts +++ b/src/ui/ColorManager.ts @@ -3,8 +3,7 @@ * @license MIT */ -import { IColorManager, IColor, IColorSet } from './Types'; -import { ITheme } from 'xterm'; +import { IColorManager, IColor, IColorSet, ITheme } from './Types'; const DEFAULT_FOREGROUND = fromHex('#ffffff'); const DEFAULT_BACKGROUND = fromHex('#000000'); @@ -90,7 +89,11 @@ export class ColorManager implements IColorManager { const canvas = document.createElement('canvas'); canvas.width = 1; canvas.height = 1; - this._ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d'); + if (!ctx) { + throw new Error('Could not get rendering context'); + } + this._ctx = ctx; this._ctx.globalCompositeOperation = 'copy'; this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1); this.colors = { @@ -133,11 +136,11 @@ export class ColorManager implements IColorManager { } private _parseColor( - css: string, + css: string | undefined, fallback: IColor, allowTransparency: boolean = this.allowTransparency ): IColor { - if (!css) { + if (css === undefined) { return fallback; } diff --git a/src/ui/Types.ts b/src/ui/Types.ts index ef725ba6..b7b0ac21 100644 --- a/src/ui/Types.ts +++ b/src/ui/Types.ts @@ -20,3 +20,27 @@ export interface IColorSet { selection: IColor; ansi: IColor[]; } + +export interface ITheme { + foreground?: string; + background?: string; + cursor?: string; + cursorAccent?: string; + selection?: string; + black?: string; + red?: string; + green?: string; + yellow?: string; + blue?: string; + magenta?: string; + cyan?: string; + white?: string; + brightBlack?: string; + brightRed?: string; + brightGreen?: string; + brightYellow?: string; + brightBlue?: string; + brightMagenta?: string; + brightCyan?: string; + brightWhite?: string; +} diff --git a/src/ui/tsconfig.json b/src/ui/tsconfig.json index b3613eef..e07bd9c3 100644 --- a/src/ui/tsconfig.json +++ b/src/ui/tsconfig.json @@ -10,12 +10,8 @@ "../../node_modules/@types/mocha" ] }, - "include": [ - "./Lifecycle.ts", - "./RenderDebouncer.ts", - "./ScreenDprMonitor.ts" - ], + "include": [ "./**/*" ], "references": [ { "path": "../common" } ] -} \ No newline at end of file +} From 5f70b3f3be5c70d08b76e7ce62f60d47db6a138c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 19:22:51 -0700 Subject: [PATCH 05/12] Move window resize listener to render coordinator --- src/Terminal.ts | 3 --- src/renderer/RenderCoordinator.ts | 5 +++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index d82b5bf3..e06938d9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -778,9 +778,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.onResize(() => this.renderer.onResize(this.cols, this.rows))); this.register(this.addDisposableListener('blur', () => this.renderer.onBlur())); this.register(this.addDisposableListener('focus', () => this.renderer.onFocus())); - // dprchange should handle this case, we need this as well for browsers that don't support the - // matchMedia query. - this.register(addDisposableDomListener(window, 'resize', () => this.renderer.onWindowResize(window.devicePixelRatio))); this.register(this.charMeasure.onCharSizeChanged(() => this.renderer.onCharSizeChanged())); this.register(this._renderCoordinator.onCanvasResize(() => this.viewport.syncScrollArea())); diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderCoordinator.ts index 3b9eed2d..4d051640 100644 --- a/src/renderer/RenderCoordinator.ts +++ b/src/renderer/RenderCoordinator.ts @@ -8,6 +8,7 @@ import { RenderDebouncer } from '../ui/RenderDebouncer'; import { EventEmitter2, IEvent } from '../common/EventEmitter2'; import { Disposable } from '../common/Lifecycle'; import { ScreenDprMonitor } from '../../lib/ui/ScreenDprMonitor'; +import { addDisposableDomListener } from '../ui/Lifecycle'; export class RenderCoordinator extends Disposable { private _renderDebouncer: RenderDebouncer; @@ -32,6 +33,10 @@ export class RenderCoordinator extends Disposable { this._screenDprMonitor = new ScreenDprMonitor(); this._screenDprMonitor.setListener(() => this._renderer.onWindowResize(window.devicePixelRatio)); this.register(this._screenDprMonitor); + + // dprchange should handle this case, we need this as well for browsers that don't support the + // matchMedia query. + this.register(addDisposableDomListener(window, 'resize', () => this._renderer.onWindowResize(window.devicePixelRatio))); } public refreshRows(start: number, end: number): void { From 24de302d2f4e884d088007f9ba9038c6889b438f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 19:27:06 -0700 Subject: [PATCH 06/12] onWindowResize -> onDevicePixelRatioChange --- src/TestUtils.test.ts | 2 +- src/renderer/RenderCoordinator.ts | 4 ++-- src/renderer/Renderer.ts | 6 +++--- src/renderer/Types.ts | 2 +- src/renderer/dom/DomRenderer.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 86745fc7..842f30d0 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -394,7 +394,7 @@ export class MockRenderer implements IRenderer { onSelectionChanged(start: [number, number], end: [number, number]): void {} onCursorMove(): void {} onOptionsChanged(): void {} - onWindowResize(devicePixelRatio: number): void {} + onDevicePixelRatioChange(): void {} clear(): void {} renderRows(start: number, end: number): void {} registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; } diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderCoordinator.ts index 4d051640..d8895bc4 100644 --- a/src/renderer/RenderCoordinator.ts +++ b/src/renderer/RenderCoordinator.ts @@ -31,12 +31,12 @@ export class RenderCoordinator extends Disposable { this.register(this._renderDebouncer); this._screenDprMonitor = new ScreenDprMonitor(); - this._screenDprMonitor.setListener(() => this._renderer.onWindowResize(window.devicePixelRatio)); + this._screenDprMonitor.setListener(() => this._renderer.onDevicePixelRatioChange()); this.register(this._screenDprMonitor); // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. - this.register(addDisposableDomListener(window, 'resize', () => this._renderer.onWindowResize(window.devicePixelRatio))); + this.register(addDisposableDomListener(window, 'resize', () => this._renderer.onDevicePixelRatioChange())); } public refreshRows(start: number, end: number): void { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 754020fd..b941d314 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -76,11 +76,11 @@ export class Renderer extends Disposable implements IRenderer { } } - public onWindowResize(devicePixelRatio: number): void { + public onDevicePixelRatioChange(): void { // If the device pixel ratio changed, the char atlas needs to be regenerated // and the terminal needs to refreshed - if (this._devicePixelRatio !== devicePixelRatio) { - this._devicePixelRatio = devicePixelRatio; + if (this._devicePixelRatio !== window.devicePixelRatio) { + this._devicePixelRatio = window.devicePixelRatio; this.onResize(this._terminal.cols, this._terminal.rows); } } diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index d08360ef..a72546fb 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -29,7 +29,7 @@ export interface IRenderer extends IDisposable { dispose(): void; onThemeChange(colors: IColorSet): void; - onWindowResize(devicePixelRatio: number): void; + onDevicePixelRatioChange(): void; onResize(cols: number, rows: number): void; onCharSizeChanged(): void; onBlur(): void; diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index d1f877e5..1f3b11b8 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -213,7 +213,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._themeStyleElement.innerHTML = styles; } - public onWindowResize(devicePixelRatio: number): void { + public onDevicePixelRatioChange(): void { this._updateDimensions(); } From 497d31bec3108e191709629dacf202b9b4796c06 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 19:31:35 -0700 Subject: [PATCH 07/12] onThemeChange -> setColors --- src/Terminal.ts | 2 +- src/TestUtils.test.ts | 2 +- src/renderer/BaseRenderLayer.ts | 2 +- src/renderer/Renderer.ts | 6 ++++-- src/renderer/Types.ts | 4 ++-- src/renderer/dom/DomRenderer.ts | 2 +- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index e06938d9..652e04ca 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -851,7 +851,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._theme = theme; this._colorManager.setTheme(theme); if (this.renderer) { - this.renderer.onThemeChange(this._colorManager.colors); + this.renderer.setColors(this._colorManager.colors); } if (this.viewport) { this.viewport.onThemeChange(this._colorManager.colors); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 842f30d0..f83e7d22 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -384,7 +384,7 @@ export class MockRenderer implements IRenderer { throw new Error('Method not implemented.'); } dimensions: IRenderDimensions; - onThemeChange(colors: IColorSet): void { + setColors(colors: IColorSet): void { throw new Error('Method not implemented.'); } onResize(cols: number, rows: number): void {} diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 3901a860..bae96d72 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -74,7 +74,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {} public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {} - public onThemeChange(terminal: ITerminal, colorSet: IColorSet): void { + public setColors(terminal: ITerminal, colorSet: IColorSet): void { this._refreshCharAtlas(terminal, colorSet); } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index b941d314..50b41c46 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -85,10 +85,12 @@ export class Renderer extends Disposable implements IRenderer { } } - public onThemeChange(colors: IColorSet): void { + public setColors(colors: IColorSet): void { + this._colors = colors; + // Clear layers and force a full render this._renderLayers.forEach(l => { - l.onThemeChange(this._terminal, this._colors); + l.setColors(this._terminal, this._colors); l.reset(this._terminal); }); diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index a72546fb..c4028d6c 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -28,7 +28,7 @@ export interface IRenderer extends IDisposable { dimensions: IRenderDimensions; dispose(): void; - onThemeChange(colors: IColorSet): void; + setColors(colors: IColorSet): void; onDevicePixelRatioChange(): void; onResize(cols: number, rows: number): void; onCharSizeChanged(): void; @@ -82,7 +82,7 @@ export interface IRenderLayer extends IDisposable { /** * Called when the theme changes. */ - onThemeChange(terminal: ITerminal, colorSet: IColorSet): void; + setColors(terminal: ITerminal, colorSet: IColorSet): void; /** * Called when the data in the grid has changed (or needs to be rendered diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 1f3b11b8..2797639d 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -131,7 +131,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; } - public onThemeChange(colors: IColorSet): void { + public setColors(colors: IColorSet): void { this._colors = colors; this._injectCss(); } From 9ed59202757b5ac31e511f8fb13efb91f1b66e56 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 19:50:29 -0700 Subject: [PATCH 08/12] Add some basic fg/bg renderer tests --- src/public/Terminal.api.ts | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 2ad5313e..d8325229 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -104,6 +104,58 @@ describe('API Integration Tests', () => { assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom'); }); + describe('renderer', () => { + it('foreground', async function(): Promise { + this.timeout(10000); + await openTerminal({ rendererType: 'dom' }); + await page.evaluate(`window.term.write('\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7')`); + assert.deepEqual(await page.evaluate(` + [ + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(2)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(3)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(4)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(5)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(6)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(7)').className + ] + `), [ + 'xterm-fg-0', + 'xterm-fg-1', + 'xterm-fg-2', + 'xterm-fg-3', + 'xterm-fg-4', + 'xterm-fg-5', + 'xterm-fg-6' + ]); + }); + + it('background', async function(): Promise { + this.timeout(10000); + await openTerminal({ rendererType: 'dom' }); + await page.evaluate(`window.term.write('\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7')`); + assert.deepEqual(await page.evaluate(` + [ + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(2)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(3)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(4)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(5)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(6)').className, + document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(7)').className + ] + `), [ + 'xterm-bg-0', + 'xterm-bg-1', + 'xterm-bg-2', + 'xterm-bg-3', + 'xterm-bg-4', + 'xterm-bg-5', + 'xterm-bg-6' + ]); + }); + }); + it('selection', async function(): Promise { this.timeout(10000); await openTerminal({ rows: 5, cols: 5 }); From c7416a6eaa956f6ee88c0c05ccdafd9b84f9ee9e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 19:56:28 -0700 Subject: [PATCH 09/12] Use logo in demo Fixes #2093 --- demo/index.html | 1 + demo/logo.png | Bin 0 -> 2019 bytes demo/server.js | 1 + 3 files changed, 2 insertions(+) create mode 100644 demo/logo.png diff --git a/demo/index.html b/demo/index.html index a7ab0f0c..4e9a75d4 100644 --- a/demo/index.html +++ b/demo/index.html @@ -2,6 +2,7 @@ xterm.js demo + diff --git a/demo/logo.png b/demo/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ed7025707c7c33267a1a7a8d5d9d5cf02b0e4bde GIT binary patch literal 2019 zcmeAS@N?(olHy`uVBq!ia0y~yVCVs14mJh`hII!V?HCx?S9!WPhE&A8oqIM&+FhWb zI&8udfeN-;vTF`#Ft5~FaA1knbiS~JDUBhb0SQ4wwEN^eipF929+FGrQl}$`cEN$P;7hYxnNh`*Pj9 zxpQj|^?2)E^WVQ+-naCzaB_l}sd=`&-+7<4`>M88SZcgGVSDD%rAslHS{7!<9;oqt zJN)(P+oRK#`cSu#j*_hcIhUG30hw}*T?&BX0{yTr?LY~C5n94UQ2r`Z)fg2@P#WZPV+jG z!4K*6H{Q7heqP-x{!J7DHpud8 zUidJ}MdQE<%i74ab8aoHJ+m%9_bN75GD)~Hhe1U`P|Lv8OkLBhh0iDOtY??tQzoB_ zXFMkfK4tR>JmWb_@F{0cMe(Ezmz_+?H|H#o3Ve6K!$ke0n+uaf<};O>9VZx+kIq>l zEvT8WV|K7$PluF2&e@e3ZZ3Qhea}Kvl$AJn)Qh!d2~K2f>$Vg<+u^}{_~0Bf>5KbZ z-z7|3w)xNk)^or9Unp$sx*?h@{C+;U!aR$8? z2wXY;JlnHZwhbR||JGl4eGb#-{agX>1vdU%W}CnJ<9&uYDY0E&W(6nw(sJ1Ad?Zb| zD(m2vI@{D+LRYt5pTlIlfayy0>s+~QS=Z8Tmspm)FkQU5Nu$W`H)KvYI+YvblzI4CLGo8BxI~m+g z%@k%=7Gmc4y8P>;^K&XBCaNU-m?f;NEX2y=Ui>J{GEKB|bfC*|F}P zQzuhfvt?zp=aSB&jE4`-nQ-lzx5Pw{(>`sQ#W7jSKvMiBSAFRwkAxNW4)Y8buejXF zoK{%)@#2+|mq#RwyQ4fC`mcwZub%7oT*y){%Odc=oqCSER}u%#B!^v$s=m0l_xi23 z-z?Ma*d%!}wS77Ks8VNL|BR#t&cmP9ar&HfpXhd0viWY_JhSf`9zPTHXE8{Wurp_r zNf6)o{Yp&9t(u7<+pBKeG7@|9`qZb>OEzBFe@;%~mD`sO2|MiWIXrN0+$EU%r|g@A z*WK^@^0(gGI+R9ph28(N=S!!<&tqPVZ)Mwd3Eth#y7OIW#S8Oo{Fm=fzZPd3owD+N zOrv71%P)Z+5$5{!;(_K$)8i_x-P&7adgoiXs=e{%-G@IIDD8f~Ehepdw&g~qwr;_l zW|EisPwn~}(QDc@TX1LH;K_}Cl^hQoI{VrD-4_%ZU+0S^PE^c0C1 z!kGyvk`uMfww&$MF}T!oLR9?KGZ!I=m2OXXcpn<87ab5e&=Ip!G1Ko#gJy@|P9x{@ zb37P>Rg{a|+GbczWNh0d_*HDjt&VTJpMFnzcW_a%pGw2RJQx0Q=GsBU*xYi!Mqo&P<_?M;4>`EnbOk3DAx z)n2R&YBl<2RjOR{vrHRQ+TPr!e^NDXp8xBoMePq0T=clZU5cI9L4IZmHYs*uZJRB) z(&I zB`smP=wo8#j!BCxds%y=En=-PX189A__!0ZE;}S#>EO8FbY+f^`Q8$*=VQ&To1_n=8KbLh*2~7a4_=@!a literal 0 HcmV?d00001 diff --git a/demo/server.js b/demo/server.js index e3473400..c7c9fc9b 100644 --- a/demo/server.js +++ b/demo/server.js @@ -18,6 +18,7 @@ function startServer() { logs = {}; app.use('/src', express.static(__dirname + '/../src')); + app.get('/logo.png', (req, res) => res.sendFile(__dirname + '/logo.png')); app.get('/', function(req, res){ res.sendFile(__dirname + '/index.html'); From 87dca56dee8018a17b5cb22ec844b7013629da63 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 20:18:59 -0700 Subject: [PATCH 10/12] Support pausing in RenderCoordinator This actually removes the pause rendering functionality when the resize and theme changes but simplifies it as a result, they also rarely happen especially when off screen/hidden. Fixes #2099 --- src/Terminal.ts | 2 +- src/renderer/RenderCoordinator.ts | 31 ++++++++++++++++++++++---- src/renderer/Renderer.ts | 36 +++---------------------------- 3 files changed, 31 insertions(+), 38 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 652e04ca..cb2b3220 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -766,7 +766,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._colorManager.setTheme(this._theme); this._setupRenderer(); - this._renderCoordinator = new RenderCoordinator(this.renderer, this.rows); + this._renderCoordinator = new RenderCoordinator(this.renderer, this.rows, this.screenElement); this._renderCoordinator.onRender(e => this._onRender.fire(e)); this.onResize(e => this._renderCoordinator.resize(e.cols, e.rows)); diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderCoordinator.ts index d8895bc4..a843c19d 100644 --- a/src/renderer/RenderCoordinator.ts +++ b/src/renderer/RenderCoordinator.ts @@ -14,17 +14,20 @@ export class RenderCoordinator extends Disposable { private _renderDebouncer: RenderDebouncer; private _screenDprMonitor: ScreenDprMonitor; + private _isPaused: boolean = false; + private _needsFullRefresh: boolean = false; + private _canvasWidth: number = 0; + private _canvasHeight: number = 0; + private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } private _onRender = new EventEmitter2<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } - private _canvasWidth: number = 0; - private _canvasHeight: number = 0; - constructor( private _renderer: IRenderer, - private _rowCount: number + private _rowCount: number, + screenElement: HTMLElement ) { super(); this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end)); @@ -37,9 +40,29 @@ export class RenderCoordinator extends Disposable { // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. this.register(addDisposableDomListener(window, 'resize', () => this._renderer.onDevicePixelRatioChange())); + + // Detect whether IntersectionObserver is detected and enable renderer pause + // and resume based on terminal visibility if so + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver(e => this._onIntersectionChange(e[e.length - 1]), { threshold: 0 }); + observer.observe(screenElement); + this.register({ dispose: () => observer.disconnect() }); + } + } + + private _onIntersectionChange(entry: IntersectionObserverEntry): void { + this._isPaused = entry.intersectionRatio === 0; + if (!this._isPaused && this._needsFullRefresh) { + this.refreshRows(0, this._rowCount - 1); + this._needsFullRefresh = false; + } } public refreshRows(start: number, end: number): void { + if (this._isPaused) { + this._needsFullRefresh = true; + return; + } this._renderDebouncer.refresh(start, end, this._rowCount); } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 50b41c46..08586b16 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -16,8 +16,6 @@ import { IColorSet } from '../ui/Types'; export class Renderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; - private _isPaused: boolean = false; - private _needsFullRefresh: boolean = false; private _characterJoinerRegistry: ICharacterJoinerRegistry; public dimensions: IRenderDimensions; @@ -53,14 +51,6 @@ export class Renderer extends Disposable implements IRenderer { this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); this.onOptionsChanged(); - - // Detect whether IntersectionObserver is detected and enable renderer pause - // and resume based on terminal visibility if so - if ('IntersectionObserver' in window) { - const observer = new IntersectionObserver(e => this.onIntersectionChange(e[e.length - 1]), { threshold: 0 }); - observer.observe(this._terminal.element); - this.register({ dispose: () => observer.disconnect() }); - } } public dispose(): void { @@ -68,14 +58,6 @@ export class Renderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.dispose()); } - public onIntersectionChange(entry: IntersectionObserverEntry): void { - this._isPaused = entry.intersectionRatio === 0; - if (!this._isPaused && this._needsFullRefresh) { - this._terminal.refresh(0, this._terminal.rows - 1); - this._needsFullRefresh = false; - } - } - public onDevicePixelRatioChange(): void { // If the device pixel ratio changed, the char atlas needs to be regenerated // and the terminal needs to refreshed @@ -94,11 +76,7 @@ export class Renderer extends Disposable implements IRenderer { l.reset(this._terminal); }); - if (this._isPaused) { - this._needsFullRefresh = true; - } else { - this._terminal.refresh(0, this._terminal.rows - 1); - } + this._terminal.refresh(0, this._terminal.rows - 1); } public onResize(cols: number, rows: number): void { @@ -109,11 +87,7 @@ export class Renderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); // Force a refresh - if (this._isPaused) { - this._needsFullRefresh = true; - } else { - this._terminal.refresh(0, this._terminal.rows - 1); - } + this._terminal.refresh(0, this._terminal.rows - 1); // Resize the screen this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; @@ -149,11 +123,7 @@ export class Renderer extends Disposable implements IRenderer { } private _runOperation(operation: (layer: IRenderLayer) => void): void { - if (this._isPaused) { - this._needsFullRefresh = true; - } else { - this._renderLayers.forEach(l => operation(l)); - } + this._renderLayers.forEach(l => operation(l)); } /** From 43015f85f45bea83cc07a066f382dd5d09b458e8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 21:15:18 -0700 Subject: [PATCH 11/12] Move Renderer ownership to RenderCoordinator --- demo/client.ts | 4 +- src/AccessibilityManager.ts | 25 +++++++--- src/MouseHelper.test.ts | 2 +- src/MouseHelper.ts | 13 +++-- src/Terminal.ts | 82 ++++++++++++------------------- src/Types.ts | 2 - src/Viewport.ts | 16 ++++-- src/addons/fit/fit.ts | 6 +-- src/renderer/RenderCoordinator.ts | 82 +++++++++++++++++++++++++++---- src/renderer/Renderer.ts | 5 -- src/renderer/Types.ts | 15 +++++- 11 files changed, 158 insertions(+), 94 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 01789402..8bcdc971 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -296,8 +296,8 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a function updateTerminalSize(): void { const cols = parseInt((document.getElementById(`opt-cols`)).value, 10); const rows = parseInt((document.getElementById(`opt-rows`)).value, 10); - const width = (cols * term._core.renderer.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px'; - const height = (rows * term._core.renderer.dimensions.actualCellHeight).toString() + 'px'; + const width = (cols * term._core._renderCoordinator.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px'; + const height = (rows * term._core._renderCoordinator.dimensions.actualCellHeight).toString() + 'px'; terminalContainer.style.width = width; terminalContainer.style.height = height; term.fit(); diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index bc1dcdd0..7f1d8eaa 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -10,6 +10,7 @@ import { RenderDebouncer } from './ui/RenderDebouncer'; import { addDisposableDomListener } from './ui/Lifecycle'; import { Disposable } from './common/Lifecycle'; import { ScreenDprMonitor } from './ui/ScreenDprMonitor'; +import { IRenderDimensions } from './renderer/Types'; const MAX_ROWS_TO_READ = 20; @@ -42,7 +43,10 @@ export class AccessibilityManager extends Disposable { */ private _charsToConsume: string[] = []; - constructor(private _terminal: ITerminal) { + constructor( + private _terminal: ITerminal, + private _dimensions: IRenderDimensions + ) { super(); this._accessibilityTreeRoot = document.createElement('div'); this._accessibilityTreeRoot.classList.add('xterm-accessibility'); @@ -60,7 +64,7 @@ export class AccessibilityManager extends Disposable { this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); - this.refreshRowsDimensions(); + this._refreshRowsDimensions(); this._accessibilityTreeRoot.appendChild(this._rowContainer); this._renderRowsDebouncer = new RenderDebouncer(this._renderRows.bind(this)); @@ -86,10 +90,10 @@ export class AccessibilityManager extends Disposable { this._screenDprMonitor = new ScreenDprMonitor(); this.register(this._screenDprMonitor); - this._screenDprMonitor.setListener(() => this.refreshRowsDimensions()); + this._screenDprMonitor.setListener(() => this._refreshRowsDimensions()); // This shouldn't be needed on modern browsers but is present in case the // media query that drives the ScreenDprMonitor isn't supported - this.register(addDisposableDomListener(window, 'resize', () => this.refreshRowsDimensions())); + this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions())); } public dispose(): void { @@ -175,7 +179,7 @@ export class AccessibilityManager extends Disposable { // Add bottom boundary listener this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); - this.refreshRowsDimensions(); + this._refreshRowsDimensions(); } private _createAccessibilityTreeNode(): HTMLElement { @@ -258,8 +262,8 @@ export class AccessibilityManager extends Disposable { } } - public refreshRowsDimensions(): void { - if (!this._terminal.renderer.dimensions.actualCellHeight) { + private _refreshRowsDimensions(): void { + if (!this._dimensions.actualCellHeight) { return; } if (this._rowElements.length !== this._terminal.rows) { @@ -270,8 +274,13 @@ export class AccessibilityManager extends Disposable { } } + public setDimensions(dimensions: IRenderDimensions): void { + this._dimensions = dimensions; + this._refreshRowsDimensions(); + } + private _refreshRowDimensions(element: HTMLElement): void { - element.style.height = `${this._terminal.renderer.dimensions.actualCellHeight}px`; + element.style.height = `${this._dimensions.actualCellHeight}px`; } private _announceCharacter(char: string): void { diff --git a/src/MouseHelper.test.ts b/src/MouseHelper.test.ts index 26888bf7..0925d49f 100644 --- a/src/MouseHelper.test.ts +++ b/src/MouseHelper.test.ts @@ -31,7 +31,7 @@ describe('MouseHelper.getCoords', () => { actualCellWidth: CHAR_WIDTH, actualCellHeight: CHAR_HEIGHT }; - mouseHelper = new MouseHelper(renderer); + mouseHelper = new MouseHelper(renderer as any); }); describe('when charMeasure is not initialized', () => { diff --git a/src/MouseHelper.ts b/src/MouseHelper.ts index fb91c9ef..12905222 100644 --- a/src/MouseHelper.ts +++ b/src/MouseHelper.ts @@ -4,13 +4,12 @@ */ import { ICharMeasure, IMouseHelper } from './Types'; -import { IRenderer } from './renderer/Types'; +import { RenderCoordinator } from './renderer/RenderCoordinator'; export class MouseHelper implements IMouseHelper { - constructor(private _renderer: IRenderer) {} - - public setRenderer(renderer: IRenderer): void { - this._renderer = renderer; + constructor( + private _renderCoordinator: RenderCoordinator + ) { } public static getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] { @@ -42,8 +41,8 @@ export class MouseHelper implements IMouseHelper { return null; } - coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderer.dimensions.actualCellWidth / 2 : 0)) / this._renderer.dimensions.actualCellWidth); - coords[1] = Math.ceil(coords[1] / this._renderer.dimensions.actualCellHeight); + coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderCoordinator.dimensions.actualCellWidth / 2 : 0)) / this._renderCoordinator.dimensions.actualCellWidth); + coords[1] = Math.ceil(coords[1] / this._renderCoordinator.dimensions.actualCellHeight); // Ensure coordinates are within the terminal viewport. Note that selections // need an addition point of precision to cover the end point (as characters diff --git a/src/Terminal.ts b/src/Terminal.ts index cb2b3220..f55a84f6 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -204,7 +204,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _inputHandler: InputHandler; public soundManager: SoundManager; private _renderCoordinator: RenderCoordinator; - public renderer: IRenderer; public selectionManager: SelectionManager; public linkifier: ILinkifier; public buffers: BufferSet; @@ -356,8 +355,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); - // Reuse renderer if the Terminal is being recreated via a reset call. - this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; this.linkifier = this.linkifier || new Linkifier(this); this._mouseZoneManager = this._mouseZoneManager || null; @@ -469,12 +466,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } break; case 'theme': - // If open has been called we do not want to set options.theme as the - // source of truth is owned by the renderer. - if (this.renderer) { - this._setTheme(value); - return; - } + this._setTheme(value); break; case 'scrollback': value = Math.min(value, MAX_BUFFER_SIZE); @@ -503,8 +495,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II case 'fontFamily': case 'fontSize': // When the font changes the size of the cells may change which requires a renderer clear - if (this.renderer) { - this.renderer.clear(); + if (this._renderCoordinator) { + this._renderCoordinator.clear(); this.charMeasure.measure(this.options); } break; @@ -516,21 +508,16 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II case 'fontWeight': case 'fontWeightBold': // When the font changes the size of the cells may change which requires a renderer clear - if (this.renderer) { - this.renderer.clear(); - this.renderer.onResize(this.cols, this.rows); + if (this._renderCoordinator) { + this._renderCoordinator.clear(); + this._renderCoordinator.onResize(this.cols, this.rows); this.refresh(0, this.rows - 1); } break; case 'rendererType': - if (this.renderer) { - this.unregister(this.renderer); - this.renderer.dispose(); - this.renderer = null; + if (this._renderCoordinator) { + this._renderCoordinator.setRenderer(this._createRenderer()); } - this._setupRenderer(); - this.renderer.onCharSizeChanged(); - this.mouseHelper.setRenderer(this.renderer); break; case 'scrollback': this.buffers.resize(this.cols, this.rows); @@ -540,8 +527,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II break; case 'screenReaderMode': if (value) { - if (!this._accessibilityManager) { - this._accessibilityManager = new AccessibilityManager(this); + if (!this._accessibilityManager && this._renderCoordinator) { + this._accessibilityManager = new AccessibilityManager(this, this._renderCoordinator.dimensions); } } else { if (this._accessibilityManager) { @@ -565,8 +552,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II break; } // Inform renderer of changes - if (this.renderer) { - this.renderer.onOptionsChanged(); + if (this._renderCoordinator) { + this._renderCoordinator.onOptionsChanged(); } } @@ -764,27 +751,27 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.options.theme = null; this._colorManager = new ColorManager(document, this.options.allowTransparency); this._colorManager.setTheme(this._theme); - this._setupRenderer(); - this._renderCoordinator = new RenderCoordinator(this.renderer, this.rows, this.screenElement); + const renderer = this._createRenderer(); + this._renderCoordinator = new RenderCoordinator(renderer, this.rows, this.screenElement); this._renderCoordinator.onRender(e => this._onRender.fire(e)); this.onResize(e => this._renderCoordinator.resize(e.cols, e.rows)); - this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure); + this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure, this._renderCoordinator.dimensions); this.viewport.onThemeChange(this._colorManager.colors); this.register(this.viewport); - this.register(this.onCursorMove(() => this.renderer.onCursorMove())); - this.register(this.onResize(() => this.renderer.onResize(this.cols, this.rows))); - this.register(this.addDisposableListener('blur', () => this.renderer.onBlur())); - this.register(this.addDisposableListener('focus', () => this.renderer.onFocus())); - this.register(this.charMeasure.onCharSizeChanged(() => this.renderer.onCharSizeChanged())); - this.register(this._renderCoordinator.onCanvasResize(() => this.viewport.syncScrollArea())); + this.register(this.onCursorMove(() => this._renderCoordinator.onCursorMove())); + this.register(this.onResize(() => this._renderCoordinator.onResize(this.cols, this.rows))); + this.register(this.addDisposableListener('blur', () => this._renderCoordinator.onBlur())); + this.register(this.addDisposableListener('focus', () => this._renderCoordinator.onFocus())); + this.register(this.charMeasure.onCharSizeChanged(() => this._renderCoordinator.onCharSizeChanged())); + this.register(this._renderCoordinator.onDimensionsChange(() => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this.charMeasure); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); - this.register(this.selectionManager.onRedrawRequest(e => this.renderer.onSelectionChanged(e.start, e.end, e.columnSelectMode))); + this.register(this.selectionManager.onRedrawRequest(e => this._renderCoordinator.onSelectionChanged(e.start, e.end, e.columnSelectMode))); this.register(this.selectionManager.onLinuxMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired @@ -799,7 +786,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II })); this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this.selectionManager.refresh())); - this.mouseHelper = new MouseHelper(this.renderer); + this.mouseHelper = new MouseHelper(this._renderCoordinator); // apply mouse event classes set by escape codes before terminal was attached this.element.classList.toggle('enable-mouse-events', this.mouseEvents); if (this.mouseEvents) { @@ -811,8 +798,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (this.options.screenReaderMode) { // Note that this must be done *after* the renderer is created in order to // ensure the correct order of the dprchange event - this._accessibilityManager = new AccessibilityManager(this); - this._accessibilityManager.register(this._renderCoordinator.onCanvasResize(() => this._accessibilityManager.refreshRowsDimensions())); + this._accessibilityManager = new AccessibilityManager(this, this._renderCoordinator.dimensions); + this._accessibilityManager.register(this._renderCoordinator.onDimensionsChange(e => this._accessibilityManager.setDimensions(e))); } // Measure the character size @@ -830,17 +817,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } - private _setupRenderer(): void { + private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': this.renderer = new Renderer(this, this._colorManager.colors); break; - case 'dom': this.renderer = new DomRenderer(this, this._colorManager.colors); break; + case 'canvas': return new Renderer(this, this._colorManager.colors); break; + case 'dom': return new DomRenderer(this, this._colorManager.colors); break; default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } - // TODO: Setting of renderer should be owned by RenderCoordinator - if (this._renderCoordinator) { - this._renderCoordinator.setRenderer(this.renderer); - } - this.register(this.renderer); } /** @@ -850,8 +832,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _setTheme(theme: ITheme): void { this._theme = theme; this._colorManager.setTheme(theme); - if (this.renderer) { - this.renderer.setColors(this._colorManager.colors); + if (this._renderCoordinator) { + this._renderCoordinator.setColors(this._colorManager.colors); } if (this.viewport) { this.viewport.onThemeChange(this._colorManager.colors); @@ -1593,13 +1575,13 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { - const joinerId = this.renderer.registerCharacterJoiner(handler); + const joinerId = this._renderCoordinator.registerCharacterJoiner(handler); this.refresh(0, this.rows - 1); return joinerId; } public deregisterCharacterJoiner(joinerId: number): void { - if (this.renderer.deregisterCharacterJoiner(joinerId)) { + if (this._renderCoordinator.deregisterCharacterJoiner(joinerId)) { this.refresh(0, this.rows - 1); } } diff --git a/src/Types.ts b/src/Types.ts index 30f943fe..4ab165c1 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -4,7 +4,6 @@ */ import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm'; -import { IRenderer } from './renderer/Types'; import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from './core/Types'; import { ICircularList } from './common/Types'; import { IEvent } from './common/EventEmitter2'; @@ -201,7 +200,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc screenElement: HTMLElement; selectionManager: ISelectionManager; charMeasure: ICharMeasure; - renderer: IRenderer; browser: IBrowser; writeBuffer: string[]; cursorHidden: boolean; diff --git a/src/Viewport.ts b/src/Viewport.ts index e32113e1..e53c99ed 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -8,6 +8,7 @@ import { CharMeasure } from './CharMeasure'; import { Disposable } from './common/Lifecycle'; import { addDisposableDomListener } from './ui/Lifecycle'; import { IColorSet } from './ui/Types'; +import { IRenderDimensions } from './renderer/Types'; const FALLBACK_SCROLL_BAR_WIDTH = 15; @@ -43,7 +44,8 @@ export class Viewport extends Disposable implements IViewport { private _terminal: ITerminal, private _viewportElement: HTMLElement, private _scrollArea: HTMLElement, - private _charMeasure: CharMeasure + private _charMeasure: CharMeasure, + private _dimensions: IRenderDimensions ) { super(); @@ -57,6 +59,10 @@ export class Viewport extends Disposable implements IViewport { setTimeout(() => this.syncScrollArea(), 0); } + public onDimensionsChance(dimensions: IRenderDimensions): void { + this._dimensions = dimensions; + } + public onThemeChange(colors: IColorSet): void { this._viewportElement.style.backgroundColor = colors.background.css; } @@ -73,9 +79,9 @@ export class Viewport extends Disposable implements IViewport { private _innerRefresh(): void { if (this._charMeasure.height > 0) { - this._currentRowHeight = this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio; + this._currentRowHeight = this._dimensions.scaledCellHeight / window.devicePixelRatio; this._lastRecordedViewportHeight = this._viewportElement.offsetHeight; - const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._terminal.renderer.dimensions.canvasHeight); + const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._dimensions.canvasHeight); if (this._lastRecordedBufferHeight !== newBufferHeight) { this._lastRecordedBufferHeight = newBufferHeight; this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px'; @@ -106,7 +112,7 @@ export class Viewport extends Disposable implements IViewport { } // If viewport height changed - if (this._lastRecordedViewportHeight !== (this._terminal).renderer.dimensions.canvasHeight) { + if (this._lastRecordedViewportHeight !== this._dimensions.canvasHeight) { this._refresh(); return; } @@ -125,7 +131,7 @@ export class Viewport extends Disposable implements IViewport { } // If row height changed - if (this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) { + if (this._dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) { this._refresh(); return; } diff --git a/src/addons/fit/fit.ts b/src/addons/fit/fit.ts index ac978807..26f8752c 100644 --- a/src/addons/fit/fit.ts +++ b/src/addons/fit/fit.ts @@ -39,8 +39,8 @@ export function proposeGeometry(term: Terminal): IGeometry { const availableHeight = parentElementHeight - elementPaddingVer; const availableWidth = parentElementWidth - elementPaddingHor - (term)._core.viewport.scrollBarWidth; const geometry = { - cols: Math.floor(availableWidth / (term)._core.renderer.dimensions.actualCellWidth), - rows: Math.floor(availableHeight / (term)._core.renderer.dimensions.actualCellHeight) + cols: Math.floor(availableWidth / (term)._core._renderCoordinator.dimensions.actualCellWidth), + rows: Math.floor(availableHeight / (term)._core._renderCoordinator.dimensions.actualCellHeight) }; return geometry; } @@ -50,7 +50,7 @@ export function fit(term: Terminal): void { if (geometry) { // Force a full render if (term.rows !== geometry.rows || term.cols !== geometry.cols) { - (term)._core.renderer.clear(); + (term)._core._renderCoordinator.clear(); term.resize(geometry.cols, geometry.rows); } } diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderCoordinator.ts index a843c19d..251af38f 100644 --- a/src/renderer/RenderCoordinator.ts +++ b/src/renderer/RenderCoordinator.ts @@ -3,12 +3,14 @@ * @license MIT */ -import { IRenderer } from './Types'; +import { IRenderer, IRenderDimensions } from './Types'; import { RenderDebouncer } from '../ui/RenderDebouncer'; import { EventEmitter2, IEvent } from '../common/EventEmitter2'; import { Disposable } from '../common/Lifecycle'; -import { ScreenDprMonitor } from '../../lib/ui/ScreenDprMonitor'; +import { ScreenDprMonitor } from '../ui/ScreenDprMonitor'; import { addDisposableDomListener } from '../ui/Lifecycle'; +import { IColorSet } from '..//ui/Types'; +import { CharacterJoinerHandler } from '../Types'; export class RenderCoordinator extends Disposable { private _renderDebouncer: RenderDebouncer; @@ -19,10 +21,14 @@ export class RenderCoordinator extends Disposable { private _canvasWidth: number = 0; private _canvasHeight: number = 0; - private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); - public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } + private _onDimensionsChange = new EventEmitter2(); + public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; } private _onRender = new EventEmitter2<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + private _onRefreshRequest = new EventEmitter2<{ start: number, end: number }>(); + public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; } + + public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } constructor( private _renderer: IRenderer, @@ -86,15 +92,71 @@ export class RenderCoordinator extends Disposable { if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) { return; } - this._canvasWidth = this._renderer.dimensions.canvasWidth; - this._canvasHeight = this._renderer.dimensions.canvasHeight; - this._onCanvasResize.fire({ - width: this._canvasWidth, - height: this._canvasHeight - }); + this._onDimensionsChange.fire(this._renderer.dimensions); } public setRenderer(renderer: IRenderer): void { + // TODO: RenderCoordinator should be the only one to dispose the renderer + this._renderer.dispose(); this._renderer = renderer; } + + private _fullRefresh(): void { + if (this._isPaused) { + this._needsFullRefresh = true; + } else { + this.refreshRows(0, this._rowCount); + } + } + + public setColors(colors: IColorSet): void { + this._renderer.setColors(colors); + this._fullRefresh(); + } + + public onDevicePixelRatioChange(): void { + this._renderer.onDevicePixelRatioChange(); + } + + public onResize(cols: number, rows: number): void { + this._renderer.onResize(cols, rows); + this._fullRefresh(); + } + + // TODO: Is this useful when we have onResize? + public onCharSizeChanged(): void { + this._renderer.onCharSizeChanged(); + } + + public onBlur(): void { + this._renderer.onBlur(); + } + + public onFocus(): void { + this._renderer.onFocus(); + } + + public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { + this._renderer.onSelectionChanged(start, end, columnSelectMode); + } + + public onCursorMove(): void { + this._renderer.onCursorMove(); + } + + public onOptionsChanged(): void { + this._renderer.onOptionsChanged(); + } + + public clear(): void { + this._renderer.clear(); + } + + public registerCharacterJoiner(handler: CharacterJoinerHandler): number { + return this._renderer.registerCharacterJoiner(handler); + } + + public deregisterCharacterJoiner(joinerId: number): boolean { + return this._renderer.deregisterCharacterJoiner(joinerId); + } } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 08586b16..b3b93ca7 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -75,8 +75,6 @@ export class Renderer extends Disposable implements IRenderer { l.setColors(this._terminal, this._colors); l.reset(this._terminal); }); - - this._terminal.refresh(0, this._terminal.rows - 1); } public onResize(cols: number, rows: number): void { @@ -86,9 +84,6 @@ export class Renderer extends Disposable implements IRenderer { // Resize all render layers this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); - // Force a refresh - this._terminal.refresh(0, this._terminal.rows - 1); - // Resize the screen this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index c4028d6c..c9881ae4 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -25,7 +25,20 @@ export const enum FLAGS { * rendering rows to the screen. */ export interface IRenderer extends IDisposable { - dimensions: IRenderDimensions; + readonly dimensions: IRenderDimensions; + + /** + * A property that is set by consumers of this interface, this will be set to true when the + * terminal is completely offscreen and to false when it comes back on. When true the consumer of + * the renderer will not trigger `renderRows`, the renderer should disable code in functions other + * than `renderRows` that renders to the screen but it should continue to gather state changes. + * When the renderer is unpaused, a full `renderRows` will be triggered if it was called while + * paused. + * + * For example, when `isPaused` is `true`, `IRenderer.onBlur` should record the state change, but + * not actually draw the blurred cursor. + */ + // isPaused: boolean; dispose(): void; setColors(colors: IColorSet): void; From 2f08b87d95348d32a54b0c00779455e3c26b37b6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 21:21:02 -0700 Subject: [PATCH 12/12] Remove old comment --- src/renderer/Types.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index c9881ae4..4a4afbf0 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -27,19 +27,6 @@ export const enum FLAGS { export interface IRenderer extends IDisposable { readonly dimensions: IRenderDimensions; - /** - * A property that is set by consumers of this interface, this will be set to true when the - * terminal is completely offscreen and to false when it comes back on. When true the consumer of - * the renderer will not trigger `renderRows`, the renderer should disable code in functions other - * than `renderRows` that renders to the screen but it should continue to gather state changes. - * When the renderer is unpaused, a full `renderRows` will be triggered if it was called while - * paused. - * - * For example, when `isPaused` is `true`, `IRenderer.onBlur` should record the state change, but - * not actually draw the blurred cursor. - */ - // isPaused: boolean; - dispose(): void; setColors(colors: IColorSet): void; onDevicePixelRatioChange(): void;