From 6daa3f449925661f8985e42f9a78bd37c9a973e5 Mon Sep 17 00:00:00 2001 From: Puneethnaik Date: Sun, 31 May 2020 12:42:14 +0530 Subject: [PATCH 1/5] ColorManager.ts : modified the setTheme function to add opacity of 0.3 to the selection color if selection color provided is opaque. --- src/browser/ColorManager.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 6ca4fd96..057797e9 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -5,7 +5,7 @@ import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types'; import { ITheme } from 'common/services/Services'; -import { channels, color, css } from 'browser/Color'; +import { channels, color, css, rgba } from 'browser/Color'; import { ColorContrastCache } from 'browser/ColorContrastCache'; const DEFAULT_FOREGROUND = css.toColor('#ffffff'); @@ -116,6 +116,16 @@ export class ColorManager implements IColorManager { this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); + const [selectionR, selectionG, selectionB, selectionA] = rgba.toChannels(this.colors.selection.rgba); + // The selection color is opaque. It needs to be blended with background color at 0.3 opacity Issue #2737 + if (selectionA === 0xFF) { + const opacity = 0.3; + const newAlpha = Math.round(opacity*255); + this.colors.selection = { + css: channels.toCss(selectionR, selectionG, selectionB, newAlpha), + rgba: channels.toRgba(selectionR, selectionG, selectionB, newAlpha) + }; + } this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selection); this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); From d5bc65ec0e7efc88f5b5f4d6a280b680bc136d30 Mon Sep 17 00:00:00 2001 From: Puneethnaik Date: Wed, 3 Jun 2020 19:48:34 +0530 Subject: [PATCH 2/5] 1. Removed logic to blend selection with background with 0.3 opacity from ColorManager to a function in color namespace. 2. Changed the DomRenderer and CanvasRenderer where the function defined in color namespace is called. --- src/browser/Color.ts | 13 ++++++++++++- src/browser/ColorManager.ts | 10 ---------- src/browser/renderer/Renderer.ts | 4 ++-- src/browser/renderer/dom/DomRenderer.ts | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 9649227e..f2ef590f 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColor } from 'browser/Types'; +import { IColor, IColorSet } from 'browser/Types'; /** * Helper functions where the source type is "channels" (individual color channels as numbers). @@ -47,6 +47,17 @@ export namespace color { const rgba = channels.toRgba(r, g, b); return { css, rgba }; } + export function blendSelectionWithBgWithOpacity(colors: IColorSet, opacity: number=0.3): void { + const [selectionR, selectionG, selectionB, selectionA] = rgba.toChannels(colors.selection.rgba); + // The selection color is opaque. It needs to be blended with background color at 0.3 opacity Issue #2737 + if (selectionA === 0xFF) { + const newAlpha: number = Math.round(opacity*255); + colors.selection = { + css: channels.toCss(selectionR, selectionG, selectionB, newAlpha), + rgba: channels.toRgba(selectionR, selectionG, selectionB, newAlpha) + }; + } + } export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { const result = rgba.ensureContrastRatio(bg.rgba, fg.rgba, ratio); diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 057797e9..7d5ad598 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -116,16 +116,6 @@ export class ColorManager implements IColorManager { this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); - const [selectionR, selectionG, selectionB, selectionA] = rgba.toChannels(this.colors.selection.rgba); - // The selection color is opaque. It needs to be blended with background color at 0.3 opacity Issue #2737 - if (selectionA === 0xFF) { - const opacity = 0.3; - const newAlpha = Math.round(opacity*255); - this.colors.selection = { - css: channels.toCss(selectionR, selectionG, selectionB, newAlpha), - rgba: channels.toRgba(selectionR, selectionG, selectionB, newAlpha) - }; - } this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selection); this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index b9f577fe..73b2019a 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -15,6 +15,7 @@ import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { color } from 'browser/Color'; let nextRendererId = 1; @@ -44,7 +45,7 @@ export class Renderer extends Disposable implements IRenderer { super(); const allowTransparency = this._optionsService.options.allowTransparency; this._characterJoinerRegistry = new CharacterJoinerRegistry(this._bufferService); - + color.blendSelectionWithBgWithOpacity(this._colors); this._renderLayers = [ new TextRenderLayer(this._screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService), new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, _optionsService), @@ -87,7 +88,6 @@ export class Renderer extends Disposable implements IRenderer { public setColors(colors: IColorSet): void { this._colors = colors; - // Clear layers and force a full render this._renderLayers.forEach(l => { l.setColors(this._colors); diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index ff955140..0086f881 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -53,7 +53,7 @@ export class DomRenderer extends Disposable implements IRenderer { @IBufferService private readonly _bufferService: IBufferService ) { super(); - + color.blendSelectionWithBgWithOpacity(this._colors); this._rowContainer = document.createElement('div'); this._rowContainer.classList.add(ROW_CONTAINER_CLASS); this._rowContainer.style.lineHeight = 'normal'; From 61432ab65d457a8d4ed21b22cec23b0c48a73828 Mon Sep 17 00:00:00 2001 From: Puneethnaik Date: Tue, 9 Jun 2020 23:34:53 +0530 Subject: [PATCH 3/5] Made changes according as per Review Comments. --- .../src/atlas/CharAtlasUtils.ts | 2 +- src/browser/Color.ts | 22 +++++++++---------- src/browser/ColorManager.ts | 16 ++++++++++---- src/browser/Types.d.ts | 2 +- src/browser/renderer/Renderer.ts | 2 -- src/browser/renderer/SelectionRenderLayer.ts | 2 +- src/browser/renderer/dom/DomRenderer.ts | 3 +-- 7 files changed, 27 insertions(+), 22 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 8e26b1d6..5496a500 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -20,7 +20,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number background: colors.background, cursor: NULL_COLOR, cursorAccent: NULL_COLOR, - selection: NULL_COLOR, + selectionTransparent: NULL_COLOR, selectionOpaque: NULL_COLOR, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. diff --git a/src/browser/Color.ts b/src/browser/Color.ts index f2ef590f..2effe515 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColor, IColorSet } from 'browser/Types'; +import { IColor } from 'browser/Types'; /** * Helper functions where the source type is "channels" (individual color channels as numbers). @@ -47,16 +47,8 @@ export namespace color { const rgba = channels.toRgba(r, g, b); return { css, rgba }; } - export function blendSelectionWithBgWithOpacity(colors: IColorSet, opacity: number=0.3): void { - const [selectionR, selectionG, selectionB, selectionA] = rgba.toChannels(colors.selection.rgba); - // The selection color is opaque. It needs to be blended with background color at 0.3 opacity Issue #2737 - if (selectionA === 0xFF) { - const newAlpha: number = Math.round(opacity*255); - colors.selection = { - css: channels.toCss(selectionR, selectionG, selectionB, newAlpha), - rgba: channels.toRgba(selectionR, selectionG, selectionB, newAlpha) - }; - } + export function isOpaque(color: IColor): boolean { + return (color.rgba & 0xFF) === 0xFF; } export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined { @@ -79,6 +71,14 @@ export namespace color { rgba: rgbaColor }; } + export function opacity(color: IColor, opacity: number): IColor { + const a = Math.round(opacity * 0xFF); + const [r, g, b] = rgba.toChannels(color.rgba); + return { + css: channels.toCss(r, g, b, a), + rgba: channels.toRgba(r, g, b, a) + }; + } } /** diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 7d5ad598..9a0fd7ee 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -5,7 +5,7 @@ import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types'; import { ITheme } from 'common/services/Services'; -import { channels, color, css, rgba } from 'browser/Color'; +import { channels, color, css } from 'browser/Color'; import { ColorContrastCache } from 'browser/ColorContrastCache'; const DEFAULT_FOREGROUND = css.toColor('#ffffff'); @@ -92,7 +92,7 @@ export class ColorManager implements IColorManager { background: DEFAULT_BACKGROUND, cursor: DEFAULT_CURSOR, cursorAccent: DEFAULT_CURSOR_ACCENT, - selection: DEFAULT_SELECTION, + selectionTransparent: DEFAULT_SELECTION, selectionOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), ansi: DEFAULT_ANSI_COLORS.slice(), contrastCache: this._contrastCache @@ -115,8 +115,16 @@ export class ColorManager implements IColorManager { this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); - this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); - this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selection); + this.colors.selectionTransparent = this._parseColor(theme.selection, DEFAULT_SELECTION, true); + this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selectionTransparent); + /** + * If selection color is opaque, blend it with background with 0.3 opacity + * Issue #2737 + */ + if (color.isOpaque(this.colors.selectionTransparent)) { + const opacity = 0.3; + this.colors.selectionTransparent = color.opacity(this.colors.selectionTransparent, opacity); + } this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index d0f8da15..1245ab04 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -120,7 +120,7 @@ export interface IColorSet { background: IColor; cursor: IColor; cursorAccent: IColor; - selection: IColor; + selectionTransparent: IColor; /** The selection blended on top of background. */ selectionOpaque: IColor; ansi: IColor[]; diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index b00b4116..b9d02ff8 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -15,7 +15,6 @@ import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { color } from 'browser/Color'; let nextRendererId = 1; @@ -45,7 +44,6 @@ export class Renderer extends Disposable implements IRenderer { super(); const allowTransparency = this._optionsService.options.allowTransparency; this._characterJoinerRegistry = new CharacterJoinerRegistry(this._bufferService); - color.blendSelectionWithBgWithOpacity(this._colors); this._renderLayers = [ new TextRenderLayer(this._screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService), new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, _optionsService), diff --git a/src/browser/renderer/SelectionRenderLayer.ts b/src/browser/renderer/SelectionRenderLayer.ts index 5689ce93..685111e8 100644 --- a/src/browser/renderer/SelectionRenderLayer.ts +++ b/src/browser/renderer/SelectionRenderLayer.ts @@ -78,7 +78,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { return; } - this._ctx.fillStyle = this._colors.selection.css; + this._ctx.fillStyle = this._colors.selectionTransparent.css; if (columnSelectMode) { const startCol = start[0]; diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 64e2fc68..f42ff684 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -54,7 +54,6 @@ export class DomRenderer extends Disposable implements IRenderer { @IBufferService private readonly _bufferService: IBufferService ) { super(); - color.blendSelectionWithBgWithOpacity(this._colors); this._rowContainer = document.createElement('div'); this._rowContainer.classList.add(ROW_CONTAINER_CLASS); this._rowContainer.style.lineHeight = 'normal'; @@ -226,7 +225,7 @@ export class DomRenderer extends Disposable implements IRenderer { `}` + `${this._terminalSelector} .${SELECTION_CLASS} div {` + ` position: absolute;` + - ` background-color: ${this._colors.selection.css};` + + ` background-color: ${this._colors.selectionTransparent.css};` + `}`; // Colors this._colors.ansi.forEach((c, i) => { From 4c943ddb0d0c3395c6f8154a4c31d8c85a4fa27c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 10 Jun 2020 12:39:11 -0700 Subject: [PATCH 4/5] Space out exports --- src/browser/Color.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 2effe515..60d3529f 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -47,6 +47,7 @@ export namespace color { const rgba = channels.toRgba(r, g, b); return { css, rgba }; } + export function isOpaque(color: IColor): boolean { return (color.rgba & 0xFF) === 0xFF; } @@ -71,6 +72,7 @@ export namespace color { rgba: rgbaColor }; } + export function opacity(color: IColor, opacity: number): IColor { const a = Math.round(opacity * 0xFF); const [r, g, b] = rgba.toChannels(color.rgba); From 98d076089845794035b26076b2e459bb3c9a19e3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 10 Jun 2020 18:37:13 -0700 Subject: [PATCH 5/5] Add tests, support #rrggbbaa in css.toColor --- src/browser/Color.test.ts | 53 ++++++++++++++++++++++++++++++++++++++- src/browser/Color.ts | 17 ++++++++++--- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/browser/Color.test.ts b/src/browser/Color.test.ts index ff8da246..0d410930 100644 --- a/src/browser/Color.test.ts +++ b/src/browser/Color.test.ts @@ -117,11 +117,43 @@ describe('Color', () => { assert.deepEqual(color.opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF }); }); }); + + describe('isOpaque', () => { + it('should return true for opaque colors', () => { + assert.ok(color.isOpaque(css.toColor('#000000'))); + assert.ok(color.isOpaque(css.toColor('#000000ff'))); + assert.ok(color.isOpaque(css.toColor('#808080'))); + assert.ok(color.isOpaque(css.toColor('#808080ff'))); + assert.ok(color.isOpaque(css.toColor('#ffffff'))); + assert.ok(color.isOpaque(css.toColor('#ffffffff'))); + }); + it('should return false for transparent colors', () => { + assert.ok(!color.isOpaque(css.toColor('#00000000'))); + assert.ok(!color.isOpaque(css.toColor('#00000080'))); + assert.ok(!color.isOpaque(css.toColor('#000000fe'))); + assert.ok(!color.isOpaque(css.toColor('#80808000'))); + assert.ok(!color.isOpaque(css.toColor('#80808080'))); + assert.ok(!color.isOpaque(css.toColor('#808080fe'))); + assert.ok(!color.isOpaque(css.toColor('#ffffff00'))); + assert.ok(!color.isOpaque(css.toColor('#ffffff80'))); + assert.ok(!color.isOpaque(css.toColor('#fffffffe'))); + }); + }); + + describe('opacity', () => { + it('should make the color transparent', () => { + assert.deepEqual(color.opacity(css.toColor('#000000'), 0), { css: '#00000000', rgba: 0x00000000 }); + assert.deepEqual(color.opacity(css.toColor('#000000'), 0.25), { css: '#00000040', rgba: 0x00000040 }); + assert.deepEqual(color.opacity(css.toColor('#000000'), 0.5), { css: '#00000080', rgba: 0x00000080 }); + assert.deepEqual(color.opacity(css.toColor('#000000'), 0.75), { css: '#000000bf', rgba: 0x000000bf }); + assert.deepEqual(color.opacity(css.toColor('#000000'), 1), { css: '#000000ff', rgba: 0x000000ff }); + }); + }); }); describe('css', () => { describe('toColor', () => { - it('should covert a CSS string to an IColor', () => { + it('should convert the #rrggbb format to an IColor', () => { assert.deepEqual(css.toColor('#000000'), { css: '#000000', rgba: 0x000000FF }); assert.deepEqual(css.toColor('#101010'), { css: '#101010', rgba: 0x101010FF }); assert.deepEqual(css.toColor('#202020'), { css: '#202020', rgba: 0x202020FF }); @@ -140,6 +172,25 @@ describe('Color', () => { assert.deepEqual(css.toColor('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF }); assert.deepEqual(css.toColor('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF }); }); + it('should convert the #rrggbbaa format to an IColor', () => { + assert.deepEqual(css.toColor('#00000000'), { css: '#00000000', rgba: 0x00000000 }); + assert.deepEqual(css.toColor('#10101010'), { css: '#10101010', rgba: 0x10101010 }); + assert.deepEqual(css.toColor('#20202020'), { css: '#20202020', rgba: 0x20202020 }); + assert.deepEqual(css.toColor('#30303030'), { css: '#30303030', rgba: 0x30303030 }); + assert.deepEqual(css.toColor('#40404040'), { css: '#40404040', rgba: 0x40404040 }); + assert.deepEqual(css.toColor('#50505050'), { css: '#50505050', rgba: 0x50505050 }); + assert.deepEqual(css.toColor('#60606060'), { css: '#60606060', rgba: 0x60606060 }); + assert.deepEqual(css.toColor('#70707070'), { css: '#70707070', rgba: 0x70707070 }); + assert.deepEqual(css.toColor('#80808080'), { css: '#80808080', rgba: 0x80808080 }); + assert.deepEqual(css.toColor('#90909090'), { css: '#90909090', rgba: 0x90909090 }); + assert.deepEqual(css.toColor('#a0a0a0a0'), { css: '#a0a0a0a0', rgba: 0xa0a0a0a0 }); + assert.deepEqual(css.toColor('#b0b0b0b0'), { css: '#b0b0b0b0', rgba: 0xb0b0b0b0 }); + assert.deepEqual(css.toColor('#c0c0c0c0'), { css: '#c0c0c0c0', rgba: 0xc0c0c0c0 }); + assert.deepEqual(css.toColor('#d0d0d0d0'), { css: '#d0d0d0d0', rgba: 0xd0d0d0d0 }); + assert.deepEqual(css.toColor('#e0e0e0e0'), { css: '#e0e0e0e0', rgba: 0xe0e0e0e0 }); + assert.deepEqual(css.toColor('#f0f0f0f0'), { css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }); + assert.deepEqual(css.toColor('#ffffffff'), { css: '#ffffffff', rgba: 0xffffffff }); + }); }); }); diff --git a/src/browser/Color.ts b/src/browser/Color.ts index 60d3529f..c43c5eb5 100644 --- a/src/browser/Color.ts +++ b/src/browser/Color.ts @@ -88,10 +88,19 @@ export namespace color { */ export namespace css { export function toColor(css: string): IColor { - return { - css, - rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0 - }; + switch (css.length) { + case 7: // #rrggbb + return { + css, + rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0 + }; + case 9: // #rrggbbaa + return { + css, + rgba: parseInt(css.slice(1), 16) >>> 0 + }; + } + throw new Error('css.toColor: Unsupported css format'); } }