From 9f85790ad15716694d7ca0d2c17002662a816015 Mon Sep 17 00:00:00 2001 From: CHaBou Date: Thu, 14 Dec 2017 15:42:53 +0100 Subject: [PATCH 1/4] Add allowTransparency option Fixes #1004 --- fixtures/typings-test/typings-test.ts | 2 ++ src/Interfaces.ts | 1 + src/Terminal.ts | 5 +++-- src/renderer/CharAtlas.ts | 8 ++++---- src/renderer/ColorManager.ts | 18 +----------------- src/renderer/Renderer.ts | 5 +++-- src/renderer/TextRenderLayer.ts | 4 ++-- typings/xterm.d.ts | 10 ++++++++-- 8 files changed, 24 insertions(+), 29 deletions(-) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index 887eb322..d1fda14d 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -144,6 +144,7 @@ namespace methods_core { const r20: string = t.getOption('bellStyle'); const r21: boolean = t.getOption('enableBold'); const r22: number = t.getOption('letterSpacing'); + const r23: boolean = t.getOption('allowTransparency'); } { const t: Terminal = new Terminal(); @@ -160,6 +161,7 @@ namespace methods_core { t.setOption('popOnBell', true); t.setOption('screenKeys', true); t.setOption('useFlowControl', true); + t.setOption('allowTransparency', true); t.setOption('visualBell', true); t.setOption('colors', ['a', 'b']); t.setOption('letterSpacing', 1); diff --git a/src/Interfaces.ts b/src/Interfaces.ts index f90cb8c0..2fab9e83 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -150,6 +150,7 @@ export interface ITerminalOptions { termName?: string; theme?: ITheme; useFlowControl?: boolean; + allowTransparency?: boolean; } export interface IBuffer { diff --git a/src/Terminal.ts b/src/Terminal.ts index 9e6312a5..739988da 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -89,6 +89,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { cancelEvents: false, disableStdin: false, useFlowControl: false, + allowTransparency: false, tabStopWidth: 8, theme: null // programFeatures: false, @@ -578,7 +579,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.document = this.parent.ownerDocument; this.body = this.document.body; - initializeCharAtlas(this.document); + initializeCharAtlas(this.document, this.options.allowTransparency); // Create main element container this.element = this.document.createElement('div'); @@ -629,7 +630,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.charMeasure = new CharMeasure(document, this.helperContainer); - this.renderer = new Renderer(this, this.options.theme); + this.renderer = new Renderer(this, this.options.theme, this.options.allowTransparency); this.options.theme = null; this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); this.viewport.onThemeChanged(this.renderer.colorManager.colors); diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index 9ac1e161..8cd09119 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -110,9 +110,9 @@ let generator: CharAtlasGenerator; * Initializes the char atlas generator. * @param document The document. */ -export function initialize(document: Document): void { +export function initialize(document: Document, alpha: boolean): void { if (!generator) { - generator = new CharAtlasGenerator(document); + generator = new CharAtlasGenerator(document, alpha); } } @@ -120,9 +120,9 @@ class CharAtlasGenerator { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; - constructor(private _document: Document) { + constructor(private _document: Document, alpha: boolean) { this._canvas = this._document.createElement('canvas'); - this._ctx = this._canvas.getContext('2d', {alpha: false}); + this._ctx = this._canvas.getContext('2d', {alpha}); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 6c0d2945..ea5f1cb0 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -86,7 +86,7 @@ export class ColorManager implements IColorManager { */ public setTheme(theme: ITheme): void { this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND; - this.colors.background = this._validateColor(theme.background, DEFAULT_BACKGROUND); + this.colors.background = theme.background || DEFAULT_BACKGROUND; this.colors.cursor = theme.cursor || DEFAULT_CURSOR; this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT; this.colors.selection = theme.selection || DEFAULT_SELECTION; @@ -107,20 +107,4 @@ export class ColorManager implements IColorManager { this.colors.ansi[14] = theme.brightCyan || DEFAULT_ANSI_COLORS[14]; this.colors.ansi[15] = theme.brightWhite || DEFAULT_ANSI_COLORS[15]; } - - private _validateColor(color: string, fallback: string): string { - if (!color) { - return fallback; - } - if (color.length === 7 && color.charAt(0) === '#') { - return color; - } - if (color.length === 4 && color.charAt(0) === '#') { - const r = color.charAt(1); - const g = color.charAt(2); - const b = color.charAt(3); - return `#${r}${r}${g}${g}${b}${b}`; - } - return fallback; - } } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index ea373e5e..27ba76ad 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -25,14 +25,15 @@ export class Renderer extends EventEmitter implements IRenderer { public colorManager: ColorManager; public dimensions: IRenderDimensions; - constructor(private _terminal: ITerminal, theme: ITheme) { + constructor(private _terminal: ITerminal, theme: ITheme, alpha: boolean) { super(); this.colorManager = new ColorManager(); if (theme) { this.colorManager.setTheme(theme); } + this._renderLayers = [ - new TextRenderLayer(this._terminal.element, 0, this.colorManager.colors), + new TextRenderLayer(this._terminal.element, 0, alpha, 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) diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 2ecb6ec6..fd8acbdf 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -24,8 +24,8 @@ export class TextRenderLayer extends BaseRenderLayer { private _characterFont: string; private _characterOverlapCache: { [key: string]: boolean } = {}; - constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { - super(container, 'text', zIndex, false, colors); + constructor(container: HTMLElement, zIndex: number, alpha: boolean, colors: IColorSet) { + super(container, 'text', zIndex, alpha, colors); this._state = new GridCache(); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 43be69c2..4e26dff1 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -12,6 +12,12 @@ declare module 'xterm' { * An object containing start up options for the terminal. */ export interface ITerminalOptions { + /** + * Whether background should support non-opaque color. It must be set before + * executing open() method and can't be changed later without excuting it again. + * Warning: Enabling this option can reduce performances somewhat. + */ + allowTransparency?: boolean; /** * A data uri of the sound to use for the bell (needs bellStyle = 'sound'). */ @@ -409,7 +415,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; + getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'allowTransparency'): boolean; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -454,7 +460,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; + setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'allowTransparency', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key. From 9c5aaed4415428a0a28eac51dd80762520110888 Mon Sep 17 00:00:00 2001 From: CHaBou Date: Fri, 15 Dec 2017 00:02:37 +0100 Subject: [PATCH 2/4] Fix some function parameters --- src/Terminal.ts | 2 +- src/renderer/Renderer.ts | 4 ++-- src/renderer/TextRenderLayer.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 739988da..c2d04cbb 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -630,7 +630,7 @@ 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.allowTransparency); + this.renderer = new Renderer(this, this.options.theme); this.options.theme = null; this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); this.viewport.onThemeChanged(this.renderer.colorManager.colors); diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 27ba76ad..3ba896a6 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -25,7 +25,7 @@ export class Renderer extends EventEmitter implements IRenderer { public colorManager: ColorManager; public dimensions: IRenderDimensions; - constructor(private _terminal: ITerminal, theme: ITheme, alpha: boolean) { + constructor(private _terminal: ITerminal, theme: ITheme) { super(); this.colorManager = new ColorManager(); if (theme) { @@ -33,7 +33,7 @@ export class Renderer extends EventEmitter implements IRenderer { } this._renderLayers = [ - new TextRenderLayer(this._terminal.element, 0, alpha, this.colorManager.colors), + new TextRenderLayer(this._terminal.element, 0, this.colorManager.colors, this._terminal.options.allowTransparency), 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) diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index fd8acbdf..c5198e15 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -24,7 +24,7 @@ export class TextRenderLayer extends BaseRenderLayer { private _characterFont: string; private _characterOverlapCache: { [key: string]: boolean } = {}; - constructor(container: HTMLElement, zIndex: number, alpha: boolean, colors: IColorSet) { + constructor(container: HTMLElement, zIndex: number, colors: IColorSet, alpha: boolean) { super(container, 'text', zIndex, alpha, colors); this._state = new GridCache(); } From fe53e1a09136828cb32faab5326d48ec11456c1f Mon Sep 17 00:00:00 2001 From: CHaBou Date: Thu, 25 Jan 2018 23:20:20 +0100 Subject: [PATCH 3/4] Change canvas transparency when option is updated --- src/renderer/BaseRenderLayer.ts | 22 +++++++++++++++++++++- src/renderer/CharAtlas.ts | 3 +++ src/renderer/TextRenderLayer.ts | 4 ++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index bf952d06..b9627dee 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -54,6 +54,26 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._refreshCharAtlas(terminal, colorSet); } + protected setTransparency(terminal: ITerminal, alpha: boolean): void { + if (alpha === this._alpha) { + return; + } + this._alpha = alpha + const oldCanvas = this._canvas + // Cloning preserves canvas properties and permits to acquire a new context + this._canvas = this._canvas.cloneNode(); + this._ctx = this._canvas.getContext('2d', {alpha: this._alpha}); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + // Draw the background if this is an opaque layer + if (!this._alpha) { + this.clearAll(); + } + oldCanvas.parentNode.replaceChild(this._canvas, oldCanvas); + this._refreshCharAtlas(terminal, this._colors); + // Force a full redraw + this.onGridChanged(terminal, 0, terminal.rows - 1) + } + /** * Refreshes the char atlas, aquiring a new one if necessary. * @param terminal The terminal. @@ -64,7 +84,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { return; } this._charAtlas = null; - const result = acquireCharAtlas(terminal, this._colors, this._scaledCharWidth, this._scaledCharHeight); + const result = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight); if (result instanceof HTMLCanvasElement) { this._charAtlas = result; } else { diff --git a/src/renderer/CharAtlas.ts b/src/renderer/CharAtlas.ts index f5a13492..1911d4e2 100644 --- a/src/renderer/CharAtlas.ts +++ b/src/renderer/CharAtlas.ts @@ -17,6 +17,7 @@ interface ICharAtlasConfig { fontWeightBold: string; scaledCharWidth: number; scaledCharHeight: number; + allowTransparency: boolean; colors: IColorSet; } @@ -112,6 +113,7 @@ function generateConfig(scaledCharWidth: number, scaledCharHeight: number, termi fontSize: terminal.options.fontSize, fontWeight: terminal.options.fontWeight, fontWeightBold: terminal.options.fontWeightBold, + allowTransparency: terminal.options.allowTransparency, colors: clonedColors }; } @@ -126,6 +128,7 @@ function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { a.fontSize === b.fontSize && a.fontWeight === b.fontWeight && a.fontWeightBold === b.fontWeightBold && + a.allowTransparency === b.allowTransparency && a.scaledCharWidth === b.scaledCharWidth && a.scaledCharHeight === b.scaledCharHeight && a.colors.foreground === b.colors.foreground && diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index b2563e97..0617d2d2 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -192,6 +192,10 @@ export class TextRenderLayer extends BaseRenderLayer { } } + public onOptionsChanged(terminal: ITerminal): void { + this.setTransparency(terminal, terminal.options.allowTransparency); + } + /** * Whether a character is overlapping to the next cell. */ From fc2af1d806e970b4d6ff331401b93f8417f8b784 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Jan 2018 14:13:25 -0800 Subject: [PATCH 4/4] Fix lint, reduce duplication, don't scale ctx Scaling ctx was breaking the scale when setOption was called, not sure it was doing anything. --- src/renderer/BaseRenderLayer.ts | 36 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 23eb8f21..35e1a57e 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -24,7 +24,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _charAtlas: HTMLCanvasElement | ImageBitmap; constructor( - container: HTMLElement, + private _container: HTMLElement, id: string, zIndex: number, private _alpha: boolean, @@ -33,13 +33,16 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); - this._ctx = this._canvas.getContext('2d', {alpha: _alpha}); - this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + this._initCanvas(); + this._container.appendChild(this._canvas); + } + + private _initCanvas(): void { + this._ctx = this._canvas.getContext('2d', {alpha: this._alpha}); // Draw the background if this is an opaque layer - if (!_alpha) { + if (!this._alpha) { this.clearAll(); } - container.appendChild(this._canvas); } public onOptionsChanged(terminal: ITerminal): void {} @@ -54,23 +57,22 @@ export abstract class BaseRenderLayer implements IRenderLayer { } protected setTransparency(terminal: ITerminal, alpha: boolean): void { + // Do nothing when alpha doesn't change if (alpha === this._alpha) { return; } - this._alpha = alpha - const oldCanvas = this._canvas - // Cloning preserves canvas properties and permits to acquire a new context + + // Create new canvas and replace old one + const oldCanvas = this._canvas; + this._alpha = alpha; + // Closing preserves properties this._canvas = this._canvas.cloneNode(); - this._ctx = this._canvas.getContext('2d', {alpha: this._alpha}); - this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - // Draw the background if this is an opaque layer - if (!this._alpha) { - this.clearAll(); - } - oldCanvas.parentNode.replaceChild(this._canvas, oldCanvas); + this._initCanvas(); + this._container.replaceChild(this._canvas, oldCanvas); + + // Regenerate char atlas and force a full redraw this._refreshCharAtlas(terminal, this._colors); - // Force a full redraw - this.onGridChanged(terminal, 0, terminal.rows - 1) + this.onGridChanged(terminal, 0, terminal.rows - 1); } /**