Skip to content

Commit

Permalink
automatic dark/light theme - fixes #3934
Browse files Browse the repository at this point in the history
  • Loading branch information
Eugeny committed Jul 18, 2023
1 parent bd337a4 commit 299be86
Show file tree
Hide file tree
Showing 16 changed files with 343 additions and 230 deletions.
8 changes: 8 additions & 0 deletions tabby-core/src/api/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,18 @@ export interface FileUploadOptions {
multiple: boolean
}

export type PlatformTheme = 'light'|'dark'

export abstract class PlatformService {
supportsWindowControls = false

get fileTransferStarted$ (): Observable<FileTransfer> { return this.fileTransferStarted }
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
get themeChanged$ (): Observable<PlatformTheme> { return this.themeChanged }

protected fileTransferStarted = new Subject<FileTransfer>()
protected displayMetricsChanged = new Subject<void>()
protected themeChanged = new Subject<PlatformTheme>()

abstract readClipboard (): string
abstract setClipboard (content: ClipboardContent): void
Expand Down Expand Up @@ -169,6 +173,10 @@ export abstract class PlatformService {
throw new Error('Not implemented')
}

getTheme (): PlatformTheme {
return 'dark'
}

abstract getOSRelease (): string
abstract getAppVersion (): string
abstract openExternal (url: string): void
Expand Down
2 changes: 1 addition & 1 deletion tabby-core/src/configDefaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ appearance:
tabsLocation: top
tabsInFullscreen: false
cycleTabs: true
theme: Standard
theme: Follow the color scheme
frame: thin
css: '/* * { color: blue !important; } */'
opacity: 1.0
Expand Down
23 changes: 20 additions & 3 deletions tabby-core/src/services/themes.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Subject, Observable } from 'rxjs'
import * as Color from 'color'
import { ConfigService } from '../services/config.service'
import { Theme } from '../api/theme'
import { PlatformService } from '../api/platform'
import { NewTheme } from '../theme'

@Injectable({ providedIn: 'root' })
Expand All @@ -17,13 +18,18 @@ export class ThemesService {
private constructor (
private config: ConfigService,
private standardTheme: NewTheme,
private platform: PlatformService,
@Inject(Theme) private themes: Theme[],
) {
this.rootElementStyleBackup = document.documentElement.style.cssText
this.applyTheme(standardTheme)
config.ready$.toPromise().then(() => {
this.applyCurrentTheme()
this.applyThemeVariables()
platform.themeChanged$.subscribe(() => {
this.applyCurrentTheme()
this.applyThemeVariables()
})
config.changed$.subscribe(() => {
this.applyCurrentTheme()
this.applyThemeVariables()
Expand All @@ -36,7 +42,7 @@ export class ThemesService {
document.documentElement.style.cssText = this.rootElementStyleBackup
}

const theme = this.config.store.terminal.colorScheme
const theme = this._getActiveColorScheme()
const isDark = Color(theme.background).luminosity() < Color(theme.foreground).luminosity()

function more (some, factor) {
Expand Down Expand Up @@ -106,8 +112,10 @@ export class ThemesService {

const themeColors = {
primary: theme.colors[accentIndex],
secondary: less(theme.background, 0.5).string(),
tertiary: theme.colors[8],
secondary: isDark
? less(theme.background, 0.5).string()
: less(theme.background, 0.125).string(),
tertiary: more(theme.background, 0.75).string(),
warning: theme.colors[3],
danger: theme.colors[1],
success: theme.colors[2],
Expand Down Expand Up @@ -184,6 +192,15 @@ export class ThemesService {
return this.findTheme(this.config.store.appearance.theme) ?? this.standardTheme
}

/// @hidden
_getActiveColorScheme (): any {
if (this.platform.getTheme() === 'light') {
return this.config.store.terminal.lightColorScheme
} else {
return this.config.store.terminal.colorScheme
}
}

applyTheme (theme: Theme): void {
if (!this.styleElement) {
this.styleElement = document.createElement('style')
Expand Down
6 changes: 3 additions & 3 deletions tabby-core/src/theme.new.scss
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ body {
}

.nav {
--bs-nav-link-color: var(--theme-fg);
--bs-nav-link-color: var(--theme-fg-more);
--bs-nav-link-hover-color: var(--theme-fg-less);
--bs-nav-link-disabled-color: var(--bs-gray);
}
Expand All @@ -119,8 +119,8 @@ body {
--bs-nav-tabs-border-width: 2px;
--bs-nav-tabs-border-radius: 0;
--bs-nav-tabs-link-hover-border-color: var(--bs-body-bg);
--bs-nav-tabs-border-color: var(--theme-fg-less-2);
--bs-nav-tabs-link-active-color: var(--theme-fg-less-2);
--bs-nav-tabs-border-color: var(--theme-fg);
--bs-nav-tabs-link-active-color: var(--theme-fg);

--bs-nav-tabs-link-active-bg: transparent;
--bs-nav-tabs-link-active-border-color: transparent;
Expand Down
5 changes: 4 additions & 1 deletion tabby-electron/src/services/electron.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { App, IpcRenderer, Shell, Dialog, Clipboard, GlobalShortcut, Screen, AutoUpdater, TouchBar, BrowserWindow, Menu, MenuItem, PowerSaveBlocker } from 'electron'
import { App, IpcRenderer, Shell, Dialog, Clipboard, GlobalShortcut, Screen, AutoUpdater, TouchBar, BrowserWindow, Menu, MenuItem, PowerSaveBlocker, NativeTheme } from 'electron'
import * as remote from '@electron/remote'

export interface MessageBoxResponse {
Expand All @@ -20,6 +20,7 @@ export class ElectronService {
process: any
autoUpdater: AutoUpdater
powerSaveBlocker: PowerSaveBlocker
nativeTheme: NativeTheme
TouchBar: typeof TouchBar
BrowserWindow: typeof BrowserWindow
Menu: typeof Menu
Expand All @@ -43,5 +44,7 @@ export class ElectronService {
this.BrowserWindow = remote.BrowserWindow
this.Menu = remote.Menu
this.MenuItem = remote.MenuItem
this.MenuItem = remote.MenuItem
this.nativeTheme = remote.nativeTheme
}
}
13 changes: 13 additions & 0 deletions tabby-electron/src/services/platform.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ElectronService } from '../services/electron.service'
import { ElectronHostWindow } from './hostWindow.service'
import { ShellIntegrationService } from './shellIntegration.service'
import { ElectronHostAppService } from './hostApp.service'
import { PlatformTheme } from '../../../tabby-core/src/api/platform'
const fontManager = require('fontmanager-redux') // eslint-disable-line

/* eslint-disable block-scoped-var */
Expand Down Expand Up @@ -40,6 +41,10 @@ export class ElectronPlatformService extends PlatformService {
electron.ipcRenderer.on('host:display-metrics-changed', () => {
this.zone.run(() => this.displayMetricsChanged.next())
})

electron.nativeTheme.on('updated', () => {
this.zone.run(() => this.themeChanged.next(this.getTheme()))
})
}

readClipboard (): string {
Expand Down Expand Up @@ -243,6 +248,14 @@ export class ElectronPlatformService extends PlatformService {
},
)).filePaths[0]
}

getTheme (): PlatformTheme {
if (this.electron.nativeTheme.shouldUseDarkColors) {
return 'dark'
} else {
return 'light'
}
}
}

class ElectronFileUpload extends FileUpload {
Expand Down
4 changes: 4 additions & 0 deletions tabby-terminal/src/api/baseTerminalTab.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,10 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
this.frontend?.focus()
})

this.subscribeUntilDestroyed(this.platform.themeChanged$, () => {
this.configure()
})

const cls: new (..._) => Frontend = {
xterm: XTermFrontend,
'xterm-webgl': XTermWebGLFrontend,
Expand Down
34 changes: 33 additions & 1 deletion tabby-terminal/src/colorSchemes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,41 @@ export class DefaultColorSchemes extends TerminalColorSchemeProvider {
'#b7fff9',
'#ffffff',
],
selection: undefined,
cursorAccent: undefined,
}

static defaultLightColorScheme: TerminalColorScheme = {
name: 'Tabby Default Light',
foreground: '#4d4d4c',
background: '#ffffff',
cursor: '#4d4d4c',
colors: [
'#000000',
'#c82829',
'#718c00',
'#eab700',
'#4271ae',
'#8959a8',
'#3e999f',
'#ffffff',
'#000000',
'#c82829',
'#718c00',
'#eab700',
'#4271ae',
'#8959a8',
'#3e999f',
'#ffffff',
],
selection: undefined,
cursorAccent: undefined,
}

async getSchemes (): Promise<TerminalColorScheme[]> {
return [DefaultColorSchemes.defaultColorScheme]
return [
DefaultColorSchemes.defaultColorScheme,
DefaultColorSchemes.defaultLightColorScheme,
]
}
}
116 changes: 116 additions & 0 deletions tabby-terminal/src/components/colorSchemeSettingsForMode.component.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
.head
.d-flex.align-items-center(*ngIf='!editing')
strong.me-2(translate) Current color scheme
span {{getCurrentSchemeName()}}
.me-auto
.btn-toolbar
button.btn.btn-secondary((click)='editScheme()')
i.fas.fa-pen
span(translate) Edit
.me-1
button.btn.btn-danger(
(click)='deleteScheme(config.store.terminal[this.configKey])',
*ngIf='currentCustomScheme'
)
i.fas.fa-trash
span(translate) Delete

div(*ngIf='editing')
.mb-3
label(translate) Name
input.form-control(type='text', [(ngModel)]='config.store.terminal[this.configKey].name')

.mb-3
color-picker(
[(model)]='config.store.terminal[this.configKey].foreground',
(modelChange)='config.save()',
title='FG',
hint='Foreground'
)
color-picker(
[(model)]='config.store.terminal[this.configKey].background',
(modelChange)='config.save()',
title='BG',
hint='Background'
)
color-picker(
[(model)]='config.store.terminal[this.configKey].cursor',
(modelChange)='config.save()',
title='CU',
hint='Cursor color'
)
color-picker(
[(model)]='config.store.terminal[this.configKey].cursorAccent',
(modelChange)='config.save()',
title='CA',
hint='Block cursor foreground'
)
color-picker(
[(model)]='config.store.terminal[this.configKey].selection',
(modelChange)='config.save()',
title='SB',
hint='Selection background'
)
color-picker(
[(model)]='config.store.terminal[this.configKey].selectionForeground',
(modelChange)='config.save()',
title='SF',
hint='Selection foreground'
)
color-picker(
*ngFor='let _ of config.store.terminal[this.configKey].colors; let idx = index; trackBy: colorsTrackBy',
[(model)]='config.store.terminal[this.configKey].colors[idx]',
(modelChange)='config.save()',
[title]='idx.toString()',
hint='ANSI color {{idx}}'
)

color-scheme-preview([scheme]='config.store.terminal[this.configKey]')

.btn-toolbar.d-flex.mt-2(*ngIf='editing')
.me-auto
button.btn.btn-primary((click)='saveScheme()')
i.fas.fa-check
span(translate) Save
.me-1
button.btn.btn-secondary((click)='cancelEditing()')
i.fas.fa-times
span(translate) Cancel

hr.mt-3.mb-4

.input-group.mb-3
.input-group-text
i.fas.fa-fw.fa-search
input.form-control(type='search', [placeholder]='"Search color schemes"|translate', [(ngModel)]='filter')

.body
.list-group.list-group-light.mb-3
ng-container(*ngFor='let scheme of allColorSchemes')
.list-group-item.list-group-item-action(
[hidden]='filter && !scheme.name.toLowerCase().includes(filter.toLowerCase())',
(click)='selectScheme(scheme)',
[class.active]='(currentCustomScheme || currentStockScheme) === scheme'
)
.d-flex.w-100.align-items-center
i.fas.fa-fw([class.fa-check]='(currentCustomScheme || currentStockScheme) === scheme')

.ms-2

.me-auto
span {{scheme.name}}
.badge.text-bg-info.ms-2(*ngIf='customColorSchemes.includes(scheme)', translate) Custom

div
.d-flex
.swatch(
*ngFor='let index of colorIndexes.slice(0, 8)',
[style.background-color]='scheme.colors[index]'
)
.d-flex
.swatch(
*ngFor='let index of colorIndexes.slice(8, 16)',
[style.background-color]='scheme.colors[index]'
)

color-scheme-preview([scheme]='scheme')
Loading

0 comments on commit 299be86

Please sign in to comment.