automatic dark/light theme - fixes #3934

This commit is contained in:
Eugene Pankov
2023-07-18 23:48:43 +02:00
parent bd337a4197
commit 299be86498
16 changed files with 343 additions and 230 deletions

View File

@@ -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,

View File

@@ -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,
]
}
}

View File

@@ -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')

View File

@@ -0,0 +1,117 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import deepEqual from 'deep-equal'
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
import { Component, Inject, Input, ChangeDetectionStrategy, ChangeDetectorRef, HostBinding } from '@angular/core'
import { ConfigService, PlatformService, TranslateService } from 'tabby-core'
import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider'
import { TerminalColorScheme } from '../api/interfaces'
_('Search color schemes')
/** @hidden */
@Component({
selector: 'color-scheme-settings-for-mode',
templateUrl: './colorSchemeSettingsForMode.component.pug',
styleUrls: ['./colorSchemeSettingsForMode.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ColorSchemeSettingsForModeComponent {
@Input() configKey: 'colorScheme'|'lightColorScheme'
@Input() stockColorSchemes: TerminalColorScheme[] = []
@Input() customColorSchemes: TerminalColorScheme[] = []
@Input() allColorSchemes: TerminalColorScheme[] = []
@Input() filter = ''
@Input() editing = false
colorIndexes = [...new Array(16).keys()]
currentStockScheme: TerminalColorScheme|null = null
currentCustomScheme: TerminalColorScheme|null = null
@HostBinding('class.content-box') true
constructor (
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
private changeDetector: ChangeDetectorRef,
private platform: PlatformService,
private translate: TranslateService,
public config: ConfigService,
) { }
async ngOnInit () {
this.stockColorSchemes = (await Promise.all(this.config.enabledServices(this.colorSchemeProviders).map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
this.stockColorSchemes.sort((a, b) => a.name.localeCompare(b.name))
this.customColorSchemes = this.config.store.terminal.customColorSchemes
this.changeDetector.markForCheck()
this.update()
}
ngOnChanges () {
this.update()
}
selectScheme (scheme: TerminalColorScheme) {
this.config.store.terminal[this.configKey] = { ...scheme }
this.config.save()
this.cancelEditing()
this.update()
}
update () {
this.currentCustomScheme = this.findMatchingScheme(this.config.store.terminal[this.configKey], this.customColorSchemes)
this.currentStockScheme = this.findMatchingScheme(this.config.store.terminal[this.configKey], this.stockColorSchemes)
this.allColorSchemes = this.customColorSchemes.concat(this.stockColorSchemes)
this.changeDetector.markForCheck()
}
editScheme () {
this.editing = true
}
saveScheme () {
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== this.config.store.terminal[this.configKey].name)
this.customColorSchemes.push(this.config.store.terminal[this.configKey])
this.config.store.terminal.customColorSchemes = this.customColorSchemes
this.config.save()
this.cancelEditing()
this.update()
}
cancelEditing () {
this.editing = false
}
async deleteScheme (scheme: TerminalColorScheme) {
if ((await this.platform.showMessageBox(
{
type: 'warning',
message: this.translate.instant('Delete "{name}"?', scheme),
buttons: [
this.translate.instant('Delete'),
this.translate.instant('Keep'),
],
defaultId: 1,
cancelId: 1,
},
)).response === 0) {
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== scheme.name)
this.config.store.terminal.customColorSchemes = this.customColorSchemes
this.config.save()
this.update()
}
}
getCurrentSchemeName () {
return (this.currentCustomScheme ?? this.currentStockScheme)?.name ?? 'Custom'
}
findMatchingScheme (scheme: TerminalColorScheme, schemes: TerminalColorScheme[]) {
return schemes.find(x => deepEqual(x, scheme)) ?? null
}
colorsTrackBy (index) {
return index
}
}

View File

@@ -1,117 +1,14 @@
.head
h3.mb-3(translate) Current color scheme
h3.mb-3(translate) Color schemes
.d-flex.align-items-center(*ngIf='!editing')
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.colorScheme)',
*ngIf='currentCustomScheme'
)
i.fas.fa-trash
span(translate) Delete
ul.nav-tabs(ngbNav, #nav='ngbNav', [activeId]='defaultTab')
li(ngbNavItem='dark')
a(ngbNavLink, translate) Dark mode
ng-template(ngbNavContent)
color-scheme-settings-for-mode(configKey='colorScheme')
div(*ngIf='editing')
.mb-3
label(translate) Name
input.form-control(type='text', [(ngModel)]='config.store.terminal.colorScheme.name')
li(ngbNavItem='light')
a(ngbNavLink, translate) Light mode
ng-template(ngbNavContent)
color-scheme-settings-for-mode(configKey='lightColorScheme')
.mb-3
color-picker(
[(model)]='config.store.terminal.colorScheme.foreground',
(modelChange)='config.save()',
title='FG',
hint='Foreground'
)
color-picker(
[(model)]='config.store.terminal.colorScheme.background',
(modelChange)='config.save()',
title='BG',
hint='Background'
)
color-picker(
[(model)]='config.store.terminal.colorScheme.cursor',
(modelChange)='config.save()',
title='CU',
hint='Cursor color'
)
color-picker(
[(model)]='config.store.terminal.colorScheme.cursorAccent',
(modelChange)='config.save()',
title='CA',
hint='Block cursor foreground'
)
color-picker(
[(model)]='config.store.terminal.colorScheme.selection',
(modelChange)='config.save()',
title='SB',
hint='Selection background'
)
color-picker(
[(model)]='config.store.terminal.colorScheme.selectionForeground',
(modelChange)='config.save()',
title='SF',
hint='Selection foreground'
)
color-picker(
*ngFor='let _ of config.store.terminal.colorScheme.colors; let idx = index; trackBy: colorsTrackBy',
[(model)]='config.store.terminal.colorScheme.colors[idx]',
(modelChange)='config.save()',
[title]='idx.toString()',
hint='ANSI color {{idx}}'
)
color-scheme-preview([scheme]='config.store.terminal.colorScheme')
.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')
div([ngbNavOutlet]='nav')

View File

@@ -1,114 +1,16 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import deepEqual from 'deep-equal'
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
import { Component, Inject, Input, ChangeDetectionStrategy, ChangeDetectorRef, HostBinding } from '@angular/core'
import { ConfigService, PlatformService, TranslateService } from 'tabby-core'
import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider'
import { TerminalColorScheme } from '../api/interfaces'
_('Search color schemes')
import { Component } from '@angular/core'
import { PlatformService } from 'tabby-core'
/** @hidden */
@Component({
templateUrl: './colorSchemeSettingsTab.component.pug',
styleUrls: ['./colorSchemeSettingsTab.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ColorSchemeSettingsTabComponent {
@Input() stockColorSchemes: TerminalColorScheme[] = []
@Input() customColorSchemes: TerminalColorScheme[] = []
@Input() allColorSchemes: TerminalColorScheme[] = []
@Input() filter = ''
@Input() editing = false
colorIndexes = [...new Array(16).keys()]
currentStockScheme: TerminalColorScheme|null = null
currentCustomScheme: TerminalColorScheme|null = null
@HostBinding('class.content-box') true
defaultTab = 'dark'
constructor (
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
private changeDetector: ChangeDetectorRef,
private platform: PlatformService,
private translate: TranslateService,
public config: ConfigService,
) { }
async ngOnInit () {
this.stockColorSchemes = (await Promise.all(this.config.enabledServices(this.colorSchemeProviders).map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
this.stockColorSchemes.sort((a, b) => a.name.localeCompare(b.name))
this.customColorSchemes = this.config.store.terminal.customColorSchemes
this.changeDetector.markForCheck()
this.update()
}
ngOnChanges () {
this.update()
}
selectScheme (scheme: TerminalColorScheme) {
this.config.store.terminal.colorScheme = { ...scheme }
this.config.save()
this.cancelEditing()
this.update()
}
update () {
this.currentCustomScheme = this.findMatchingScheme(this.config.store.terminal.colorScheme, this.customColorSchemes)
this.currentStockScheme = this.findMatchingScheme(this.config.store.terminal.colorScheme, this.stockColorSchemes)
this.allColorSchemes = this.customColorSchemes.concat(this.stockColorSchemes)
this.changeDetector.markForCheck()
}
editScheme () {
this.editing = true
}
saveScheme () {
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== this.config.store.terminal.colorScheme.name)
this.customColorSchemes.push(this.config.store.terminal.colorScheme)
this.config.store.terminal.customColorSchemes = this.customColorSchemes
this.config.save()
this.cancelEditing()
this.update()
}
cancelEditing () {
this.editing = false
}
async deleteScheme (scheme: TerminalColorScheme) {
if ((await this.platform.showMessageBox(
{
type: 'warning',
message: this.translate.instant('Delete "{name}"?', scheme),
buttons: [
this.translate.instant('Delete'),
this.translate.instant('Keep'),
],
defaultId: 1,
cancelId: 1,
},
)).response === 0) {
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== scheme.name)
this.config.store.terminal.customColorSchemes = this.customColorSchemes
this.config.save()
this.update()
}
}
getCurrentSchemeName () {
return (this.currentCustomScheme ?? this.currentStockScheme)?.name ?? 'Custom'
}
findMatchingScheme (scheme: TerminalColorScheme, schemes: TerminalColorScheme[]) {
return schemes.find(x => deepEqual(x, scheme)) ?? null
}
colorsTrackBy (index) {
return index
platform: PlatformService,
) {
this.defaultTab = platform.getTheme()
}
}

View File

@@ -33,10 +33,12 @@ export class TerminalConfigProvider extends ConfigProvider {
wordSeparator: ' ()[]{}\'"',
colorScheme: {
__nonStructural: true,
selection: null,
cursorAccent: null,
...DefaultColorSchemes.defaultColorScheme,
},
lightColorScheme: {
__nonStructural: true,
...DefaultColorSchemes.defaultLightColorScheme,
},
customColorSchemes: [],
warnOnMultilinePaste: true,
searchRegexAlwaysEnabled: false,

View File

@@ -363,7 +363,7 @@ export class XTermFrontend extends Frontend {
private configureColors (scheme: TerminalColorScheme|undefined): void {
const config = this.configService.store
scheme = scheme ?? config.terminal.colorScheme
scheme = scheme ?? this.themes._getActiveColorScheme()
const theme: ITheme = {
foreground: scheme!.foreground,

View File

@@ -19,6 +19,7 @@ import { LoginScriptsSettingsComponent } from './components/loginScriptsSettings
import { TerminalToolbarComponent } from './components/terminalToolbar.component'
import { ColorSchemeSelectorComponent } from './components/colorSchemeSelector.component'
import { InputProcessingSettingsComponent } from './components/inputProcessingSettings.component'
import { ColorSchemeSettingsForModeComponent } from './components/colorSchemeSettingsForMode.component'
import { TerminalDecorator } from './api/decorator'
import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
@@ -78,6 +79,7 @@ import { DefaultColorSchemes } from './colorSchemes'
LoginScriptsSettingsComponent,
TerminalToolbarComponent,
InputProcessingSettingsComponent,
ColorSchemeSettingsForModeComponent,
],
exports: [
ColorPickerComponent,