diff --git a/css/xterm.css b/css/xterm.css index 5a6e088a..e9a1e3ad 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -91,8 +91,6 @@ } .xterm .xterm-viewport { - /* On OS X this is required in order for the scroll bar to appear fully opaque */ - background-color: #000; overflow-y: scroll; cursor: default; position: absolute; @@ -102,6 +100,11 @@ bottom: 0; } +.xterm:not(.allow-transparency) .xterm-viewport { + /* On OS X this is required in order for the scroll bar to appear fully opaque */ + background-color: #000; +} + .xterm .xterm-screen { position: relative; } diff --git a/demo/client/client.ts b/demo/client/client.ts index c2134773..6a0cdead 100644 --- a/demo/client/client.ts +++ b/demo/client/client.ts @@ -277,7 +277,7 @@ function createTerminal(): Terminal { buildNumber: 22621 } : undefined, fontFamily: '"Fira Code", monospace, "Powerline Extra Symbols"', - theme: xtermjsTheme + theme: { ...xtermjsTheme } } as ITerminalOptions); // Load addons @@ -419,8 +419,11 @@ function runFakeTerminal(): void { } function updateTerminalContainerBackground(): void { - const bg = term.options.theme?.background ?? '#000000'; - terminalContainer.style.backgroundColor = bg; + if (term.options.allowTransparency) { + terminalContainer.style.background = 'repeating-conic-gradient(#000000 0% 25%, #101010 0% 50%) 50% / 20px 20px'; + } else { + terminalContainer.style.background = term.options.theme?.background ?? '#000000'; + } } function initAddons(term: Terminal): void { diff --git a/demo/client/components/window/optionsWindow.ts b/demo/client/components/window/optionsWindow.ts index 0fbcf339..c2f810b8 100644 --- a/demo/client/components/window/optionsWindow.ts +++ b/demo/client/components/window/optionsWindow.ts @@ -8,7 +8,7 @@ import type { Terminal, ITheme } from '@xterm/xterm'; import type { IControlWindow } from '../controlBar'; import type { AddonCollection } from 'types'; -const xtermjsTheme: ITheme = { +const xtermjsTheme: ITheme = Object.freeze({ foreground: '#F8F8F8', background: '#2D2E2C', selectionBackground: '#5DA5D533', @@ -29,7 +29,52 @@ const xtermjsTheme: ITheme = { brightCyan: '#72F0FF', white: '#F8F8F8', brightWhite: '#FFFFFF' -}; +}); +const sapphireTheme: ITheme = Object.freeze({ + background: '#1c2431', + foreground: '#cccccc', + selectionBackground: '#399ef440', + black: '#666666', + blue: '#399ef4', + brightBlack: '#666666', + brightBlue: '#399ef4', + brightCyan: '#21c5c7', + brightGreen: '#4eb071', + brightMagenta: '#b168df', + brightRed: '#da6771', + brightWhite: '#efefef', + brightYellow: '#fff099', + cyan: '#21c5c7', + green: '#4eb071', + magenta: '#b168df', + red: '#da6771', + white: '#efefef', + yellow: '#fff099' +}); +const lightTheme: ITheme = Object.freeze({ + background: '#ffffff', + foreground: '#333333', + cursor: '#333333', + cursorAccent: '#ffffff', + selectionBackground: '#add6ff', + overviewRulerBorder: '#aaaaaa', + black: '#000000', + blue: '#0451a5', + brightBlack: '#666666', + brightBlue: '#0451a5', + brightCyan: '#0598bc', + brightGreen: '#14ce14', + brightMagenta: '#bc05bc', + brightRed: '#cd3131', + brightWhite: '#a5a5a5', + brightYellow: '#b5ba00', + cyan: '#0598bc', + green: '#00bc00', + magenta: '#bc05bc', + red: '#cd3131', + white: '#555555', + yellow: '#949800' +}); export class OptionsWindow extends BaseWindow implements IControlWindow { public readonly id = 'options'; @@ -136,6 +181,10 @@ export class OptionsWindow extends BaseWindow implements IControlWindow { addDomListener(input, 'change', () => { console.log('change', o, input.checked); this._terminal.options[o] = input.checked; + if (o ==='allowTransparency') { + this._terminal.options.theme = this._getTheme(); + this._handlers.updateTerminalContainerBackground(); + } }); }); numberOptions.forEach(o => { @@ -171,63 +220,7 @@ export class OptionsWindow extends BaseWindow implements IControlWindow { this._handlers.updateTerminalSize(); } } else if (o === 'theme') { - switch (input.value) { - case 'default': - value = undefined; - break; - case 'xtermjs': - value = xtermjsTheme; - break; - case 'sapphire': - value = { - background: '#1c2431', - foreground: '#cccccc', - selectionBackground: '#399ef440', - black: '#666666', - blue: '#399ef4', - brightBlack: '#666666', - brightBlue: '#399ef4', - brightCyan: '#21c5c7', - brightGreen: '#4eb071', - brightMagenta: '#b168df', - brightRed: '#da6771', - brightWhite: '#efefef', - brightYellow: '#fff099', - cyan: '#21c5c7', - green: '#4eb071', - magenta: '#b168df', - red: '#da6771', - white: '#efefef', - yellow: '#fff099' - }; - break; - case 'light': - value = { - background: '#ffffff', - foreground: '#333333', - cursor: '#333333', - cursorAccent: '#ffffff', - selectionBackground: '#add6ff', - overviewRulerBorder: '#aaaaaa', - black: '#000000', - blue: '#0451a5', - brightBlack: '#666666', - brightBlue: '#0451a5', - brightCyan: '#0598bc', - brightGreen: '#14ce14', - brightMagenta: '#bc05bc', - brightRed: '#cd3131', - brightWhite: '#a5a5a5', - brightYellow: '#b5ba00', - cyan: '#0598bc', - green: '#00bc00', - magenta: '#bc05bc', - red: '#cd3131', - white: '#555555', - yellow: '#949800' - }; - break; - } + value = this._getTheme(); } this._terminal.options[o] = value; if (o === 'theme') { @@ -244,4 +237,27 @@ export class OptionsWindow extends BaseWindow implements IControlWindow { public set autoResize(value: boolean) { this._autoResize = value; } + + private _getTheme() { + const input = document.querySelector('#opt-theme'); + let theme: ITheme; + switch (input.value) { + case 'default': + theme = undefined; + break; + case 'xtermjs': + theme = { ...xtermjsTheme }; + break; + case 'sapphire': + theme = { ...sapphireTheme }; + break; + case 'light': + theme = { ...lightTheme }; + break; + } + if (this._terminal.options.allowTransparency) { + theme.background = 'transparent'; + } + return theme; + } } diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index c4e83389..d2407062 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -436,6 +436,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { this.element.dir = 'ltr'; // xterm.css assumes LTR this.element.classList.add('terminal'); this.element.classList.add('xterm'); + this.element.classList.toggle('allow-transparency', this.options.allowTransparency); + this._register(this.optionsService.onSpecificOptionChange('allowTransparency', value => this.element!.classList.toggle('allow-transparency', value))); parent.appendChild(this.element); // Performance: Use a document fragment to build the terminal diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 1a9f2225..ad523049 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -71,6 +71,7 @@ export class Viewport extends Disposable { this._scrollableElement.setScrollDimensions({ height: 0, scrollHeight: 0 }); this._register(Event.runAndSubscribe(themeService.onChangeColors, () => { + element.style.backgroundColor = themeService.colors.background.css; this._scrollableElement.getDomNode().style.backgroundColor = themeService.colors.background.css; })); element.appendChild(this._scrollableElement.getDomNode()); diff --git a/src/common/Color.test.ts b/src/common/Color.test.ts index b1683711..dbe759b4 100644 --- a/src/common/Color.test.ts +++ b/src/common/Color.test.ts @@ -303,6 +303,9 @@ describe('Color', () => { assert.deepEqual(css.toColor('rgba(0, 0, 80, 0.5)'), { css: '#00005080', rgba: 0x00005080 }); assert.deepEqual(css.toColor('rgba(255, 255, 255, 1)'), { css: '#ffffffff', rgba: 0xffffffff }); }); + it('should convert "transparent" to an IColor', () => { + assert.deepEqual(css.toColor('transparent'), { css: 'transparent', rgba: 0x00000000 }); + }); }); }); diff --git a/src/common/Color.ts b/src/common/Color.ts index b7b3ff47..b3d13fc9 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -184,6 +184,14 @@ export namespace css { return channels.toColor($r, $g, $b, $a); } + // Handle the "transparent" keyword + if (css === 'transparent') { + return { + css: 'transparent', + rgba: 0x00000000 + }; + } + // Validate the context is available for canvas-based color parsing if (!$ctx || !$litmusColor) { throw new Error('css.toColor: Unsupported css format');