diff --git a/src/Terminal.ts b/src/Terminal.ts index ef4b21eb..44320357 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -622,8 +622,11 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.charMeasure = new CharMeasure(document, this.helperContainer); + this.renderer = new Renderer(this, this.options.theme); + this.options.theme = null; this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); - this.renderer = new Renderer(this); + this.viewport.onThemeChanged(this.renderer.colorManager.colors); + this.on('cursormove', () => this.renderer.onCursorMove()); this.on('resize', () => this.renderer.onResize(this.cols, this.rows, false)); this.on('blur', () => this.renderer.onBlur()); @@ -652,15 +655,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Measure the character size this.charMeasure.measure(this.options); - // Set the theme if it was set via setOption/constructor before open. This - // must be run after CharMeasure.measure as it depends on char dimensions. - setTimeout(() => { - if (this.options.theme) { - this._setTheme(this.options.theme); - this.options.theme = null; - } - }, 0); - // Setup loop that draws to screen this.refresh(0, this.rows - 1); diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 3f996545..b55b5aaa 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -14,10 +14,10 @@ export const INVERTED_DEFAULT_COLOR = -1; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx: CanvasRenderingContext2D; - private scaledCharWidth: number; - private scaledCharHeight: number; - private scaledLineHeight: number; - private scaledLineDrawY: number; + private _scaledCharWidth: number; + private _scaledCharHeight: number; + private _scaledLineHeight: number; + private _scaledLineDrawY: number; private _charAtlas: HTMLCanvasElement | ImageBitmap; @@ -25,16 +25,17 @@ export abstract class BaseRenderLayer implements IRenderLayer { container: HTMLElement, id: string, zIndex: number, - private alpha: boolean, - protected colors: IColorSet + private _alpha: boolean, + protected _colors: IColorSet ) { this._canvas = document.createElement('canvas'); this._canvas.id = `xterm-${id}-layer`; this._canvas.style.zIndex = zIndex.toString(); - this._ctx = this._canvas.getContext('2d', {alpha}); + this._ctx = this._canvas.getContext('2d', {_alpha}); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); // Draw the background if this is an opaque layer - if (!alpha) { + if (!_alpha) { + console.log('clearAll!'); this.clearAll(); } container.appendChild(this._canvas); @@ -58,7 +59,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ private _refreshCharAtlas(terminal: ITerminal, colorSet: IColorSet): void { this._charAtlas = null; - const result = acquireCharAtlas(terminal, this.colors, this.scaledCharWidth, this.scaledCharHeight); + const result = acquireCharAtlas(terminal, this._colors, this._scaledCharWidth, this._scaledCharHeight); if (result instanceof HTMLCanvasElement) { this._charAtlas = result; } else { @@ -67,22 +68,22 @@ export abstract class BaseRenderLayer implements IRenderLayer { } public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void { - this.scaledCharWidth = dim.scaledCharWidth; - this.scaledCharHeight = dim.scaledCharHeight; - this.scaledLineHeight = dim.scaledLineHeight; - this.scaledLineDrawY = dim.scaledLineDrawY; + this._scaledCharWidth = dim.scaledCharWidth; + this._scaledCharHeight = dim.scaledCharHeight; + this._scaledLineHeight = dim.scaledLineHeight; + this._scaledLineDrawY = dim.scaledLineDrawY; this._canvas.width = dim.scaledCanvasWidth; this._canvas.height = dim.scaledCanvasHeight; this._canvas.style.width = `${dim.canvasWidth}px`; this._canvas.style.height = `${dim.canvasHeight}px`; // Draw the background if this is an opaque layer - if (!this.alpha) { + if (!this._alpha) { this.clearAll(); } if (charSizeChanged) { - this._refreshCharAtlas(terminal, this.colors); + this._refreshCharAtlas(terminal, this._colors); } } @@ -95,7 +96,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param x The column of the cell. */ private _getCellLeft(x: number): number { - return Math.round(x * this.scaledCharWidth); + return Math.round(x * this._scaledCharWidth); } /** @@ -109,9 +110,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { const cellLeft = this._getCellLeft(x); this._ctx.fillRect( cellLeft, - y * this.scaledLineHeight, + y * this._scaledLineHeight, this._getCellLeft(x + width) - cellLeft, - height * this.scaledLineHeight); + height * this._scaledLineHeight); } /** @@ -124,7 +125,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { const cellLeft = this._getCellLeft(x); this._ctx.fillRect( cellLeft, - (y + 1) * this.scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, + (y + 1) * this._scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, this._getCellLeft(x + width) - cellLeft, window.devicePixelRatio); } @@ -138,9 +139,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected fillLeftLineAtCell(x: number, y: number): void { this._ctx.fillRect( this._getCellLeft(x), - y * this.scaledLineHeight, + y * this._scaledLineHeight, window.devicePixelRatio, - this.scaledLineHeight); + this._scaledLineHeight); } /** @@ -154,19 +155,20 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.lineWidth = window.devicePixelRatio; this._ctx.strokeRect( cellLeft + window.devicePixelRatio / 2, - y * this.scaledLineHeight + (window.devicePixelRatio / 2), + y * this._scaledLineHeight + (window.devicePixelRatio / 2), this._getCellLeft(x + width) - cellLeft - window.devicePixelRatio, - (height * this.scaledLineHeight) - window.devicePixelRatio); + (height * this._scaledLineHeight) - window.devicePixelRatio); } /** * Clears the entire canvas. */ protected clearAll(): void { - if (this.alpha) { + if (this._alpha) { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } else { - this._ctx.fillStyle = this.colors.background; + console.log('fill with', this._colors.background); + this._ctx.fillStyle = this._colors.background; this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); } } @@ -180,19 +182,19 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected clearCells(x: number, y: number, width: number, height: number): void { const cellLeft = this._getCellLeft(x); - if (this.alpha) { + if (this._alpha) { this._ctx.clearRect( cellLeft, - y * this.scaledLineHeight, + y * this._scaledLineHeight, this._getCellLeft(x + width) - cellLeft, - height * this.scaledLineHeight); + height * this._scaledLineHeight); } else { - this._ctx.fillStyle = this.colors.background; + this._ctx.fillStyle = this._colors.background; this._ctx.fillRect( cellLeft, - y * this.scaledLineHeight, + y * this._scaledLineHeight, this._getCellLeft(x + width) - cellLeft, - height * this.scaledLineHeight); + height * this._scaledLineHeight); } } @@ -215,9 +217,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { // can bleed into other cells. This code will clip the following fillText, // ensuring that its contents don't go beyond the cell bounds. this._ctx.beginPath(); - this._ctx.rect(x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, charData[CHAR_DATA_WIDTH_INDEX] * this.scaledCharWidth, this.scaledCharHeight); + this._ctx.rect(x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, charData[CHAR_DATA_WIDTH_INDEX] * this._scaledCharWidth, this._scaledCharHeight); this._ctx.clip(); - this._ctx.fillText(charData[CHAR_DATA_CHAR_INDEX], x * this.scaledCharWidth, y * this.scaledCharHeight); + this._ctx.fillText(charData[CHAR_DATA_CHAR_INDEX], x * this._scaledCharWidth, y * this._scaledCharHeight); } /** @@ -255,11 +257,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { const isDefaultBackground = bg >= 256; if (isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground) { // ImageBitmap's draw about twice as fast as from a canvas - const charAtlasCellWidth = this.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; - const charAtlasCellHeight = this.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; + const charAtlasCellWidth = this._scaledCharWidth + CHAR_ATLAS_CELL_SPACING; + const charAtlasCellHeight = this._scaledCharHeight + CHAR_ATLAS_CELL_SPACING; this._ctx.drawImage(this._charAtlas, - code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this.scaledCharWidth, this.scaledCharHeight, - x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, this.scaledCharWidth, this.scaledCharHeight); + code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this._scaledCharWidth, this._scaledCharHeight, + x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, this._scaledCharWidth, this._scaledCharHeight); } else { this._drawUncachedChar(terminal, char, width, fg, x, y, bold); } @@ -288,12 +290,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.textBaseline = 'top'; if (fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this.colors.background; + this._ctx.fillStyle = this._colors.background; } else if (fg < 256) { // 256 color support - this._ctx.fillStyle = this.colors.ansi[fg]; + this._ctx.fillStyle = this._colors.ansi[fg]; } else { - this._ctx.fillStyle = this.colors.foreground; + this._ctx.fillStyle = this._colors.foreground; } // Since uncached characters are not coming off the char atlas with source @@ -301,11 +303,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { // can bleed into other cells. This code will clip the following fillText, // ensuring that its contents don't go beyond the cell bounds. this._ctx.beginPath(); - this._ctx.rect(x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY, width * this.scaledCharWidth, this.scaledCharHeight); + this._ctx.rect(x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, width * this._scaledCharWidth, this._scaledCharHeight); this._ctx.clip(); // Draw the character - this._ctx.fillText(char, x * this.scaledCharWidth, y * this.scaledLineHeight + this.scaledLineDrawY); + this._ctx.fillText(char, x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY); this._ctx.restore(); } } diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index b9afd0e1..746970bb 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColorSet } from './Interfaces'; +import { IColorSet, IColorManager } from './Interfaces'; import { ITheme } from '../Interfaces'; const DEFAULT_FOREGROUND = '#ffffff'; @@ -64,7 +64,7 @@ function toPaddedHex(c: number): string { /** * Manages the source of truth for a terminal's colors. */ -export class ColorManager { +export class ColorManager implements IColorManager { public colors: IColorSet; constructor() { diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index faed9bfe..008b5662 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -135,7 +135,7 @@ export class CursorRenderLayer extends BaseRenderLayer { if (!terminal.isFocused) { this._clearCursor(); this._ctx.save(); - this._ctx.fillStyle = this.colors.cursor; + this._ctx.fillStyle = this._colors.cursor; this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); this._ctx.restore(); this._state.x = terminal.buffer.x; @@ -190,30 +190,30 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.fillStyle = this.colors.cursor; + this._ctx.fillStyle = this._colors.cursor; this.fillLeftLineAtCell(x, y); this._ctx.restore(); } private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.fillStyle = this.colors.cursor; + this._ctx.fillStyle = this._colors.cursor; this.fillCells(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); - this._ctx.fillStyle = this.colors.background; + this._ctx.fillStyle = this._colors.background; this.fillCharTrueColor(terminal, charData, x, y); this._ctx.restore(); } private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.fillStyle = this.colors.cursor; + this._ctx.fillStyle = this._colors.cursor; this.fillBottomLineAtCells(x, y); this._ctx.restore(); } private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void { this._ctx.save(); - this._ctx.strokeStyle = this.colors.cursor; + this._ctx.strokeStyle = this._colors.cursor; this.strokeRectAtCell(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1); this._ctx.restore(); } diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index 890b01f5..719ca8cf 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -7,6 +7,7 @@ import { ITerminal, ITerminalOptions, ITheme, IEventEmitter } from '../Interface export interface IRenderer extends IEventEmitter { dimensions: IRenderDimensions; + colorManager: IColorManager; setTheme(theme: ITheme): IColorSet; onWindowResize(devicePixelRatio: number): void; @@ -69,6 +70,9 @@ export interface IRenderLayer { reset(terminal: ITerminal): void; } +export interface IColorManager { + colors: IColorSet; +} export interface IColorSet { foreground: string; diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 98084034..8f3b63a5 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -38,7 +38,7 @@ export class LinkRenderLayer extends BaseRenderLayer { } private _onLinkHover(e: LinkHoverEvent): void { - this._ctx.fillStyle = this.colors.foreground; + this._ctx.fillStyle = this._colors.foreground; this.fillBottomLineAtCells(e.x, e.y, e.length); this._state = e; } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 8e8934d6..1bdd3b4a 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -22,17 +22,20 @@ export class Renderer extends EventEmitter implements IRenderer { private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; - private _colorManager: ColorManager; + public colorManager: ColorManager; public dimensions: IRenderDimensions; - constructor(private _terminal: ITerminal) { + constructor(private _terminal: ITerminal, theme: ITheme) { super(); - this._colorManager = new ColorManager(); + this.colorManager = new ColorManager(); + if (theme) { + this.colorManager.setTheme(theme); + } this._renderLayers = [ - new TextRenderLayer(this._terminal.element, 0, this._colorManager.colors), - new SelectionRenderLayer(this._terminal.element, 1, this._colorManager.colors), - new LinkRenderLayer(this._terminal.element, 2, this._colorManager.colors, this._terminal), - new CursorRenderLayer(this._terminal.element, 3, this._colorManager.colors) + new TextRenderLayer(this._terminal.element, 0, this.colorManager.colors), + new SelectionRenderLayer(this._terminal.element, 1, this.colorManager.colors), + new LinkRenderLayer(this._terminal.element, 2, this.colorManager.colors, this._terminal), + new CursorRenderLayer(this._terminal.element, 3, this.colorManager.colors) ]; this.dimensions = { scaledCharWidth: null, @@ -57,17 +60,17 @@ export class Renderer extends EventEmitter implements IRenderer { } public setTheme(theme: ITheme): IColorSet { - this._colorManager.setTheme(theme); + this.colorManager.setTheme(theme); // Clear layers and force a full render this._renderLayers.forEach(l => { - l.onThemeChanged(this._terminal, this._colorManager.colors); + l.onThemeChanged(this._terminal, this.colorManager.colors); l.reset(this._terminal); }); this._terminal.refresh(0, this._terminal.rows - 1); - return this._colorManager.colors; + return this.colorManager.colors; } public onResize(cols: number, rows: number, didCharSizeChange: boolean): void { diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 7a0aff83..54eb7fc4 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -68,7 +68,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { // Draw first row const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; - this._ctx.fillStyle = this.colors.selection; + this._ctx.fillStyle = this._colors.selection; this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1); // Draw middle rows diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 41fcdeee..ced4ceaa 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -136,7 +136,7 @@ export class TextRenderLayer extends BaseRenderLayer { // Draw background if (bg < 256) { this._ctx.save(); - this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this.colors.foreground : this.colors.ansi[bg]); + this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this._colors.foreground : this._colors.ansi[bg]); this.fillCells(x, y, width, 1); this._ctx.restore(); } @@ -152,12 +152,12 @@ export class TextRenderLayer extends BaseRenderLayer { if (flags & FLAGS.UNDERLINE) { if (fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this.colors.background; + this._ctx.fillStyle = this._colors.background; } else if (fg < 256) { // 256 color support - this._ctx.fillStyle = this.colors.ansi[fg]; + this._ctx.fillStyle = this._colors.ansi[fg]; } else { - this._ctx.fillStyle = this.colors.foreground; + this._ctx.fillStyle = this._colors.foreground; } this.fillBottomLineAtCells(x, y); }