From 0e6ed9c34760ddf5f16596c601ddad1fb3d27330 Mon Sep 17 00:00:00 2001 From: Sential Date: Sat, 2 Feb 2019 19:33:27 +0100 Subject: [PATCH] feat: add support for theme-color, fixes #122 --- src/main/browser-view-wrapper.ts | 7 ++ src/renderer/app/models/tab.tsx | 88 +++++++++++++++------- src/renderer/app/models/tabs.ts | 4 +- src/renderer/app/scss/tabs.scss | 9 +-- src/renderer/app/utils/colors.ts | 124 +++++++++++++++++++++++++++++++ 5 files changed, 198 insertions(+), 34 deletions(-) create mode 100644 src/renderer/app/utils/colors.ts diff --git a/src/main/browser-view-wrapper.ts b/src/main/browser-view-wrapper.ts index db66ec530..8721d5f34 100644 --- a/src/main/browser-view-wrapper.ts +++ b/src/main/browser-view-wrapper.ts @@ -30,6 +30,13 @@ export default class BrowserViewWrapper extends BrowserView { ); }); + this.webContents.addListener('did-change-theme-color', (e, color) => { + appWindow.window.webContents.send( + `browserview-theme-color-updated-${this.tabId}`, + color, + ); + }); + (this.webContents as any).addListener( 'certificate-error', ( diff --git a/src/renderer/app/models/tab.tsx b/src/renderer/app/models/tab.tsx index 97472fd3d..ab001a239 100644 --- a/src/renderer/app/models/tab.tsx +++ b/src/renderer/app/models/tab.tsx @@ -3,6 +3,7 @@ import { ipcRenderer } from 'electron'; import { TABS_PADDING, TAB_ANIMATION_DURATION } from '../constants'; import { closeWindow } from '../utils'; import { createElement } from 'ui'; +import { shadeBlendConvert, getColorBrightness } from '../utils/colors'; let id = 0; @@ -24,6 +25,7 @@ export class Tab { private _favicon: string = ''; private _title: string; + private _background: string | null = null; constructor() { this.root = ( @@ -44,6 +46,8 @@ export class Tab { ) as any; + this.background = null; + app.tabs.container.appendChild(this.root); ipcRenderer.send('browserview-create', this.id); @@ -62,6 +66,14 @@ export class Tab { }, ); + ipcRenderer.on( + `browserview-theme-color-updated-${this.id}`, + (e: any, themeColor: string) => { + console.log(themeColor); + this.background = themeColor; + }, + ); + requestAnimationFrame(this.tick); } @@ -95,6 +107,10 @@ export class Tab { return app.tabs.list[app.tabs.list.indexOf(this) - 1]; } + public get nextTab() { + return app.tabs.list[app.tabs.list.indexOf(this) + 1]; + } + public get favicon() { return this._favicon; } @@ -120,22 +136,56 @@ export class Tab { this.rightBorder.style.display = value ? 'block' : 'none'; } - public onMouseEnter = () => { - this.root.classList.add('hover'); + public get background() { + return this._background; + } + + public set background(value: string | null) { + if (value && getColorBrightness(value) > 170) { + return; + } + + this._background = value || '#2196F3'; + this.update(); + } + + public update = () => { + const canHideSeparator = this.isHovered || this.selected; const previousTab = this.previousTab; if (previousTab) { - previousTab.rightBorderVisible = false; + previousTab.rightBorderVisible = !canHideSeparator; } - }; + this.rightBorderVisible = !canHideSeparator; - public onMouseLeave = () => { - this.root.classList.remove('hover'); + if (this.selected) { + this.root.classList.add('selected'); - const previousTab = this.previousTab; - if (previousTab && !this.selected) { - previousTab.rightBorderVisible = true; + const background = shadeBlendConvert(0.85, this.background); + + this.root.style.backgroundColor = background; + this.titleElement.style.color = this.background; + } else { + this.root.classList.remove('selected'); + + this.titleElement.style.color = 'rgba(0, 0, 0, 0.87)'; + this.root.style.backgroundColor = 'transparent'; } + + if (this.isHovered) { + this.root.classList.add('hover'); + } else { + this.root.classList.remove('hover'); + } + }; + + public onMouseEnter = () => { + this.update(); + }; + + public onMouseLeave = () => { + this.update(); + if (this.nextTab) this.nextTab.update(); }; public onMouseDown = (e: any) => { @@ -212,26 +262,14 @@ export class Tab { public select() { const { selectedTab } = app.tabs; - if (selectedTab) { - selectedTab.rightBorderVisible = true; - selectedTab.root.classList.remove('selected'); - - const previousTab = selectedTab.previousTab; - if (previousTab) { - previousTab.rightBorderVisible = true; - } - } - - this.root.classList.add('selected'); - this.rightBorderVisible = false; - app.tabs.selectedTabId = this.id; - const previousTab = this.previousTab; - if (previousTab) { - previousTab.rightBorderVisible = false; + if (selectedTab) { + selectedTab.update(); } + this.update(); + ipcRenderer.send('browserview-select', this.id); } diff --git a/src/renderer/app/models/tabs.ts b/src/renderer/app/models/tabs.ts index 7a0543bc6..cbf567b53 100644 --- a/src/renderer/app/models/tabs.ts +++ b/src/renderer/app/models/tabs.ts @@ -147,10 +147,8 @@ export class Tabs { this.list = tabsCopy; for (const tab of this.list) { - tab.rightBorderVisible = true; + tab.update(); } - - firstTab.select(); } public getTabsToReplace(callingTab: Tab, direction: string) { diff --git a/src/renderer/app/scss/tabs.scss b/src/renderer/app/scss/tabs.scss index 27e27b0f4..d1f212d6b 100644 --- a/src/renderer/app/scss/tabs.scss +++ b/src/renderer/app/scss/tabs.scss @@ -75,11 +75,6 @@ &.selected { z-index: 2; - background-color: #e3f2fd; - - & .tab-title { - color: #2196f3; - } &:after { background-color: rgba(33, 150, 243, 0.08); @@ -117,8 +112,10 @@ .tab-icon { min-width: 16px; - height: 16px; + height: 100%; background-size: 16px; + background-repeat: no-repeat; + background-position: center; margin-right: 8px; opacity: 0; transition: 0.2s opacity; diff --git a/src/renderer/app/utils/colors.ts b/src/renderer/app/utils/colors.ts new file mode 100644 index 000000000..5fd82c530 --- /dev/null +++ b/src/renderer/app/utils/colors.ts @@ -0,0 +1,124 @@ +// https://stackoverflow.com/a/13542669 +export const shadeBlendConvert = function ( + this: any, + p: any, + from: any, + to: any = null, +) { + if ( + typeof p !== 'number' || + p < -1 || + p > 1 || + typeof from !== 'string' || + (from[0] !== 'r' && from[0] !== '#') || + (to && typeof to !== 'string') + ) { + return null; + } // ErrorCheck + if (!(this as any).sbcRip) { + this.sbcRip = (d: any) => { + const l = d.length; + const RGB: any = {}; + + if (l > 9) { + d = d.split(','); + if (d.length < 3 || d.length > 4) return null; // ErrorCheck + (RGB[0] = i(d[0].split('(')[1])), + (RGB[1] = i(d[1])), + (RGB[2] = i(d[2])), + (RGB[3] = d[3] ? parseFloat(d[3]) : -1); + } else { + if (l === 8 || l === 6 || l < 4) return null; // ErrorCheck + if (l < 6) { + d = `#${d[1]}${d[1]}${d[2]}${d[2]}${d[3]}${d[3]}${ + l > 4 ? `${d[4]}${d[4]}` : '' + }`; + } // 3 or 4 digit + (d = i(d.slice(1), 16)), + (RGB[0] = (d >> 16) & 255), + (RGB[1] = (d >> 8) & 255), + (RGB[2] = d & 255), + (RGB[3] = -1); + if (l === 9 || l === 5) { + (RGB[3] = r((RGB[2] / 255) * 10000) / 10000), + (RGB[2] = RGB[1]), + (RGB[1] = RGB[0]), + (RGB[0] = (d >> 24) & 255); + } + } + return RGB; + }; + } + const i = parseInt; + const r = Math.round; + let h = from.length > 9; + h = + typeof to === 'string' + ? to.length > 9 + ? true + : to === 'c' + ? !h + : false + : h; + const b = p < 0; + p = b ? p * -1 : p; + to = to && to !== 'c' ? to : b ? '#000000' : '#FFFFFF'; + const f = this.sbcRip(from); + const t = this.sbcRip(to); + if (!f || !t) return null; // ErrorCheck + if (h) { + return `rgb${f[3] > -1 || t[3] > -1 ? 'a(' : '('}${r( + (t[0] - f[0]) * p + f[0], + )},${r((t[1] - f[1]) * p + f[1])},${r((t[2] - f[2]) * p + f[2])}${ + f[3] < 0 && t[3] < 0 + ? ')' + : `,${ + f[3] > -1 && t[3] > -1 + ? r(((t[3] - f[3]) * p + f[3]) * 10000) / 10000 + : t[3] < 0 + ? f[3] + : t[3] + })` + }`; + } + + return `#${( + 0x100000000 + + r((t[0] - f[0]) * p + f[0]) * 0x1000000 + + r((t[1] - f[1]) * p + f[1]) * 0x10000 + + r((t[2] - f[2]) * p + f[2]) * 0x100 + + (f[3] > -1 && t[3] > -1 + ? r(((t[3] - f[3]) * p + f[3]) * 255) + : t[3] > -1 + ? r(t[3] * 255) + : f[3] > -1 + ? r(f[3] * 255) + : 255) + ) + .toString(16) + .slice(1, f[3] > -1 || t[3] > -1 ? undefined : -2)}`; +}; + +export const getColorBrightness = (color: any) => { + let r; + let g; + let b; + + if (color.match(/^rgb/)) { + color = color.match( + /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/, + ); + + r = color[1]; + g = color[2]; + b = color[3]; + } else { + color = +`0x${color.slice(1).replace(color.length < 5 && /./g, '$&$&')}`; + + r = color >> 16; + g = (color >> 8) & 255; + b = color & 255; + } + + return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)); +};