From 8792bb5f363033c612c732b0bd34db7dee9e9390 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 18:47:44 -0700 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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 });