diff --git a/AGENTS.md b/AGENTS.md index fb83122..bd107e6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 不渲染该层,零成本** ## 终端尺寸同步 diff --git a/src/renderer/src/settings/ThemeSettingsTab.tsx b/src/renderer/src/settings/ThemeSettingsTab.tsx index 92d47d7..ef1eec9 100644 --- a/src/renderer/src/settings/ThemeSettingsTab.tsx +++ b/src/renderer/src/settings/ThemeSettingsTab.tsx @@ -32,6 +32,7 @@ export function ThemeSettingsTab({ const setCustomThemes = useSettingsStore((s) => s.setCustomThemes) const activeTheme = useResolvedTheme() const [opacityDraft, setOpacityDraft] = useState(null) + const [dimDraft, setDimDraft] = useState(null) const themeCards = useMemo( () => [...BUILTIN_THEMES, ...settings.customThemes], @@ -90,6 +91,26 @@ export function ThemeSettingsTab({ /> )} + {settings.terminal.backgroundImage !== '' && ( +
+
+
{t('settings.theme.backgroundImageDim')}
+
{t('settings.theme.backgroundImageDimDesc')}
+
+ setDimDraft(v)} + onChangeComplete={(v) => { + setDimDraft(null) + void updateTerminal({ backgroundImageDim: v }) + }} + /> +
+ )}
{t('settings.theme.accent')}
diff --git a/src/renderer/src/terminal/TerminalView.tsx b/src/renderer/src/terminal/TerminalView.tsx index 4e76f35..871f0a3 100644 --- a/src/renderer/src/terminal/TerminalView.tsx +++ b/src/renderer/src/terminal/TerminalView.tsx @@ -1400,6 +1400,14 @@ export const TerminalView: ForwardRefExoticComponent )} + {settings.terminal.backgroundImage !== '' && settings.terminal.backgroundImageDim > 0 && ( +
+ )} {dead && (
{t('terminal.dead.message', { code: exitCode })}
diff --git a/src/renderer/src/terminal/terminal.css b/src/renderer/src/terminal/terminal.css index adf6676..d8854b6 100644 --- a/src/renderer/src/terminal/terminal.css +++ b/src/renderer/src/terminal/terminal.css @@ -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; diff --git a/src/shared/i18n/dicts/en/settings.ts b/src/shared/i18n/dicts/en/settings.ts index 315ede4..70d9205 100644 --- a/src/shared/i18n/dicts/en/settings.ts +++ b/src/shared/i18n/dicts/en/settings.ts @@ -288,6 +288,8 @@ const settings: Record = { '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', diff --git a/src/shared/i18n/dicts/ja/settings.ts b/src/shared/i18n/dicts/ja/settings.ts index 508e366..2b8cc2e 100644 --- a/src/shared/i18n/dicts/ja/settings.ts +++ b/src/shared/i18n/dicts/ja/settings.ts @@ -283,6 +283,8 @@ const settings: Record = { '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': '新しいテーマ', diff --git a/src/shared/i18n/dicts/zh-CN/settings.ts b/src/shared/i18n/dicts/zh-CN/settings.ts index d05e644..b18f7a0 100644 --- a/src/shared/i18n/dicts/zh-CN/settings.ts +++ b/src/shared/i18n/dicts/zh-CN/settings.ts @@ -272,6 +272,8 @@ const settings: Record = { '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': '新建主题', diff --git a/src/shared/i18n/dicts/zh-TW/settings.ts b/src/shared/i18n/dicts/zh-TW/settings.ts index 760ff15..0eaecbd 100644 --- a/src/shared/i18n/dicts/zh-TW/settings.ts +++ b/src/shared/i18n/dicts/zh-TW/settings.ts @@ -272,6 +272,8 @@ const settings: Record = { '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': '新增主題', diff --git a/src/shared/settings.ts b/src/shared/settings.ts index 6c44172..7c69ae6 100644 --- a/src/shared/settings.ts +++ b/src/shared/settings.ts @@ -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,