feat(theme): add background image dim scrim slider
New terminal.backgroundImageDim setting (0-90%, default 0 = off). A black scrim layer sits between the background image and the xterm screen, so bright wallpapers stay readable at any opacity: unlike the opacity slider (which blends the image toward the dark dock base), the scrim darkens the image while preserving its saturation, and the raised minimumContrastRatio (4.5) keeps lifted text legible on top. - settings: backgroundImageDim with deepMerge type-fallback sanitize - TerminalView: render .term-bg-dim only when image set and dim > 0 - ThemeSettingsTab: slider follows the existing draft + onChangeComplete pattern (no settings writes while dragging) - i18n: settings.theme.backgroundImageDim(+Desc) in zh-CN/zh-TW/en/ja - AGENTS.md: document the third image-mode invariant
This commit is contained in:
1 parent
e16c860c7a
commit
6c04f0e8c1
9 files changed
+53
-2
No files matched your search
@@ -51,7 +51,7 @@ Electron + electron-vite + React 终端工具(本地终端 / SSH / SFTP)。
|
||||
|
||||
- 终端主题由 xterm 主题派生 UI 配色:`src/renderer/src/theme/chrome.ts` 的 `applyChromeTheme` 写入 `--chrome-bg/-bg-deep/-border/-hover` CSS 变量,antd token 在 `main.tsx` ThemedConfigProvider 派生
|
||||
- 标签强调色:`settings.tabAccentColor`(默认 `#3fb950`),经 `--tab-accent` CSS 变量生效
|
||||
- 背景图(`settings.terminal.backgroundImage`):图片层 `.term-bg-image` 在 xterm 画布之下(`terminal.css`),xterm 背景设 `#00000000` 透明。**图片模式下的两条铁律**:① `.terminal-view.has-bg-image .terminal-view-dock` 强制深色底衬 `#0d1117`,不能用 `--chrome-bg`——浅色主题下它是白色,调低透明度会把图洗白而不是压暗;② `minimumContrastRatio` 从 1 提到 4.5(WCAG AA,与 VS Code 终端默认值一致;TerminalView 构造与设置热更两处都要改;注意正确拼写是 `minimumContrastRatio`,`minContrastRatio` 会被静默忽略),xterm 的对比度计算把透明背景当黑色亮度,会把浅色主题的深色前景自动提亮到可读。无图时保持 1 不动主题配色
|
||||
- 背景图(`settings.terminal.backgroundImage`):图片层 `.term-bg-image` 在 xterm 画布之下(`terminal.css`),xterm 背景设 `#00000000` 透明。**图片模式下的三条铁律**:① `.terminal-view.has-bg-image .terminal-view-dock` 强制深色底衬 `#0d1117`,不能用 `--chrome-bg`——浅色主题下它是白色,调低透明度会把图洗白而不是压暗;② `minimumContrastRatio` 从 1 提到 4.5(WCAG AA,与 VS Code 终端默认值一致;TerminalView 构造与设置热更两处都要改;注意正确拼写是 `minimumContrastRatio`,`minContrastRatio` 会被静默忽略),xterm 的对比度计算把透明背景当黑色亮度,会把浅色主题的深色前景自动提亮到可读。无图时保持 1 不动主题配色。③ 压暗层(`settings.terminal.backgroundImageDim`,0–90,**默认 0**):`.term-bg-dim` 纯黑 scrim 在 DOM 序上位于 `.term-bg-image` 之后、xterm(z-index:1)之下(同在 z-index:0 层,靠 DOM 序压图),与透明度的区别是透明度把图与深色底衬混合、压暗在图之上叠黑(保饱和但整体变暗);**dim=0 时 TerminalView 不渲染该层,零成本**
|
||||
|
||||
## 终端尺寸同步
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ export function ThemeSettingsTab({
|
||||
const setCustomThemes = useSettingsStore((s) => s.setCustomThemes)
|
||||
const activeTheme = useResolvedTheme()
|
||||
const [opacityDraft, setOpacityDraft] = useState<number | null>(null)
|
||||
const [dimDraft, setDimDraft] = useState<number | null>(null)
|
||||
|
||||
const themeCards = useMemo(
|
||||
() => [...BUILTIN_THEMES, ...settings.customThemes],
|
||||
@@ -90,6 +91,26 @@ export function ThemeSettingsTab({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{settings.terminal.backgroundImage !== '' && (
|
||||
<div className="theme-accent-row">
|
||||
<div>
|
||||
<div className="settings-block-label">{t('settings.theme.backgroundImageDim')}</div>
|
||||
<div className="settings-block-hint">{t('settings.theme.backgroundImageDimDesc')}</div>
|
||||
</div>
|
||||
<Slider
|
||||
style={{ width: 200 }}
|
||||
min={0}
|
||||
max={90}
|
||||
step={5}
|
||||
value={dimDraft ?? settings.terminal.backgroundImageDim}
|
||||
onChange={(v) => setDimDraft(v)}
|
||||
onChangeComplete={(v) => {
|
||||
setDimDraft(null)
|
||||
void updateTerminal({ backgroundImageDim: v })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="theme-accent-row">
|
||||
<div>
|
||||
<div className="settings-block-label">{t('settings.theme.accent')}</div>
|
||||
|
||||
@@ -1400,6 +1400,14 @@ export const TerminalView: ForwardRefExoticComponent<TerminalViewProps & { ref?:
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{settings.terminal.backgroundImage !== '' && settings.terminal.backgroundImageDim > 0 && (
|
||||
<div
|
||||
className="term-bg-dim"
|
||||
style={{
|
||||
opacity: Math.min(90, Math.max(0, settings.terminal.backgroundImageDim)) / 100
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{dead && (
|
||||
<div className="term-dead-mask">
|
||||
<div>{t('terminal.dead.message', { code: exitCode })}</div>
|
||||
|
||||
@@ -380,6 +380,17 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Black scrim between the background image and the xterm screen: dims the
|
||||
image without washing it out (unlike opacity, which blends toward the dark
|
||||
dock base), so bright images stay readable at any opacity. */
|
||||
.term-bg-dim {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
background: #000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.terminal-view-dock > .xterm {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
@@ -288,6 +288,8 @@ const settings: Record<string, string> = {
|
||||
'settings.theme.chooseImage': 'Choose image',
|
||||
'settings.theme.clearImage': 'Remove',
|
||||
'settings.theme.imageOpacity': 'Image opacity',
|
||||
'settings.theme.backgroundImageDim': 'Background dim',
|
||||
'settings.theme.backgroundImageDimDesc': 'Overlays a black dim layer on top of the image; text is brightened automatically. 30–50% works well for bright images',
|
||||
'settings.theme.customBadge': 'Custom',
|
||||
'settings.theme.deleteTitle': 'Delete custom theme',
|
||||
'settings.theme.create': 'New theme',
|
||||
|
||||
@@ -283,6 +283,8 @@ const settings: Record<string, string> = {
|
||||
'settings.theme.chooseImage': '画像を選択',
|
||||
'settings.theme.clearImage': '削除',
|
||||
'settings.theme.imageOpacity': '画像の不透明度',
|
||||
'settings.theme.backgroundImageDim': '背景の減光',
|
||||
'settings.theme.backgroundImageDimDesc': '画像の上に黒い減光レイヤーを重ね、文字は自動で明るくなります。明るい画像は 30–50% が目安です',
|
||||
'settings.theme.customBadge': 'カスタム',
|
||||
'settings.theme.deleteTitle': 'カスタムテーマを削除',
|
||||
'settings.theme.create': '新しいテーマ',
|
||||
|
||||
@@ -272,6 +272,8 @@ const settings: Record<string, string> = {
|
||||
'settings.theme.chooseImage': '选择图片',
|
||||
'settings.theme.clearImage': '移除图片',
|
||||
'settings.theme.imageOpacity': '图片不透明度',
|
||||
'settings.theme.backgroundImageDim': '背景压暗',
|
||||
'settings.theme.backgroundImageDimDesc': '在图片之上叠加黑色压暗层,文字随之自动提亮;亮图建议 30–50%',
|
||||
'settings.theme.customBadge': '自定义',
|
||||
'settings.theme.deleteTitle': '删除自定义主题',
|
||||
'settings.theme.create': '新建主题',
|
||||
|
||||
@@ -272,6 +272,8 @@ const settings: Record<string, string> = {
|
||||
'settings.theme.chooseImage': '選擇圖片',
|
||||
'settings.theme.clearImage': '移除圖片',
|
||||
'settings.theme.imageOpacity': '圖片不透明度',
|
||||
'settings.theme.backgroundImageDim': '背景壓暗',
|
||||
'settings.theme.backgroundImageDimDesc': '在圖片之上疊加黑色壓暗層,文字隨之自動提亮;亮圖建議 30–50%',
|
||||
'settings.theme.customBadge': '自訂',
|
||||
'settings.theme.deleteTitle': '刪除自訂主題',
|
||||
'settings.theme.create': '新增主題',
|
||||
|
||||
@@ -112,6 +112,8 @@ export interface TerminalSettings {
|
||||
backgroundImage: string
|
||||
/** background image layer opacity, 10..100 (%) */
|
||||
backgroundImageOpacity: number
|
||||
/** black scrim over the background image, under the text, 0..90 (%); 0 = off */
|
||||
backgroundImageDim: number
|
||||
}
|
||||
|
||||
export type HighlightMode = 'all' | 'basic' | 'off'
|
||||
@@ -459,7 +461,8 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
||||
highlightThemeColors: false,
|
||||
highlightPerHost: false,
|
||||
backgroundImage: '',
|
||||
backgroundImageOpacity: 30
|
||||
backgroundImageOpacity: 30,
|
||||
backgroundImageDim: 0
|
||||
},
|
||||
customThemes: [],
|
||||
highlightRules: DEFAULT_HIGHLIGHT_RULES,
|
||||
|
||||
Reference in new issue
Block a user