diff --git a/demo/index.html b/demo/index.html index e4db4118..29b6fcf0 100644 --- a/demo/index.html +++ b/demo/index.html @@ -26,6 +26,9 @@
macOptionIsMeta
+ transparency +
cursorStyle diff --git a/demo/main.js b/demo/main.js index f7c863f5..682fc5a0 100644 --- a/demo/main.js +++ b/demo/main.js @@ -31,6 +31,7 @@ var terminalContainer = document.getElementById('terminal-container'), cursorStyle: document.querySelector('#option-cursor-style'), macOptionIsMeta: document.querySelector('#option-mac-option-is-meta'), scrollback: document.querySelector('#option-scrollback'), + transparency: document.querySelector('#option-transparency'), tabstopwidth: document.querySelector('#option-tabstopwidth'), experimentalCharAtlas: document.querySelector('#option-experimental-char-atlas'), bellStyle: document.querySelector('#option-bell-style'), @@ -75,15 +76,20 @@ actionElements.findPrevious.addEventListener('keypress', function (e) { optionElements.cursorBlink.addEventListener('change', function () { term.setOption('cursorBlink', optionElements.cursorBlink.checked); }); +optionElements.macOptionIsMeta.addEventListener('change', function () { + term.setOption('macOptionIsMeta', optionElements.macOptionIsMeta.checked); +}); +optionElements.transparency.addEventListener('change', function () { + var checked = optionElements.transparency.checked; + term.setOption('allowTransparency', checked); + term.setOption('theme', checked ? {background: 'rgba(0, 0, 0, .5)'} : {}); +}); optionElements.cursorStyle.addEventListener('change', function () { term.setOption('cursorStyle', optionElements.cursorStyle.value); }); optionElements.bellStyle.addEventListener('change', function () { term.setOption('bellStyle', optionElements.bellStyle.value); }); -optionElements.macOptionIsMeta.addEventListener('change', function () { - term.setOption('macOptionIsMeta', optionElements.macOptionIsMeta.checked); -}); optionElements.scrollback.addEventListener('change', function () { term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10)); }); diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 9404a461..c41dece5 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -150,6 +150,7 @@ export class Renderer extends EventEmitter implements IRenderer { } public onOptionsChanged(): void { + this.colorManager.allowTransparency = this._terminal.options.allowTransparency; this._runOperation(l => l.onOptionsChanged(this._terminal)); } diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index 25dbca18..f388bf65 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -14,6 +14,11 @@ import LRUMap from './LRUMap'; const TEXTURE_WIDTH = 1024; const TEXTURE_HEIGHT = 1024; +const TRANSPARENT_COLOR = { + css: 'rgba(0, 0, 0, 0)', + rgba: 0, +} + interface IGlyphCacheValue { index: number; isEmpty: boolean; @@ -137,7 +142,6 @@ export default class DynamicCharAtlas extends BaseCharAtlas { // into a shared function. private _drawToCache(glyph: IGlyphIdentifier, index: number): IGlyphCacheValue { this._tmpCtx.save(); - // no need to clear _tmpCtx, since we're going to draw a fully opaque background // draw the background let backgroundColor = this._config.colors.background; @@ -146,8 +150,25 @@ export default class DynamicCharAtlas extends BaseCharAtlas { } else if (glyph.bg < 256) { backgroundColor = this._config.colors.ansi[glyph.bg]; } + + let backgroundIsTransparent = false; + if ((backgroundColor.rgba & 0xFF) !== 0xFF) { + // The background color has some transparency, so we need to render it as fully transparent + // in the atlas. Otherwise we'd end up drawing the transparent background twice around the + // anti-aliased edges of the glyph, and it would look too dark. + // + // This has the side-effect of disabling RGB subpixel antialiasing, but most compositors will + // disable that anyways on a partially transparent background for similar reasons. + backgroundColor = TRANSPARENT_COLOR; + backgroundIsTransparent = true; + } + + // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtx, regardless of + // transparency in backgroundColor + this._tmpCtx.globalCompositeOperation = 'copy'; this._tmpCtx.fillStyle = backgroundColor.css; this._tmpCtx.fillRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight); + this._tmpCtx.globalCompositeOperation = 'source-over'; // draw the foreground/glyph this._tmpCtx.font = @@ -179,7 +200,10 @@ export default class DynamicCharAtlas extends BaseCharAtlas { const imageData = this._tmpCtx.getImageData( 0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight, ); - const isEmpty = clearColor(imageData, backgroundColor); + let isEmpty = false; + if (!backgroundIsTransparent) { + isEmpty = clearColor(imageData, backgroundColor); + } // copy the data from _tmpCanvas to _cacheCanvas const [x, y] = this._toCoordinates(index);