From d6e691baf7b080780684ac247464f5cef1e291da Mon Sep 17 00:00:00 2001 From: mrsibe Date: Thu, 24 Sep 2026 16:50:36 +0800 Subject: [PATCH] fix(window): match title-bar control symbols to the app theme The OS draws minimize / maximize / close on the transparent title-bar overlay using `symbolColor`. mainWindow, quizWindow and mindMapWindow hardcoded it to `white`, so in light mode the symbols were white on the `#fafafa` title bar and effectively invisible (issue #26). Derive the symbol colour from the app theme in one helper and reapply it when the theme changes, so the controls stay legible in both modes. This also replaces ankiWindow's `nativeTheme.shouldUseDarkColors || theme === 'dark'` check: the renderer always follows the app setting, so an OS-dark machine with the app set to light got white symbols on the light bar. Fixes #26 --- src/main/windows/ankiWindow.ts | 21 ++++--------------- src/main/windows/mainWindow.ts | 8 ++++---- src/main/windows/mindMapWindow.ts | 6 +++--- src/main/windows/quizWindow.ts | 6 +++--- src/main/windows/titleBarOverlay.ts | 31 +++++++++++++++++++++++++++++ test/titleBarOverlay.test.ts | 26 ++++++++++++++++++++++++ 6 files changed, 71 insertions(+), 27 deletions(-) create mode 100644 src/main/windows/titleBarOverlay.ts create mode 100644 test/titleBarOverlay.test.ts diff --git a/src/main/windows/ankiWindow.ts b/src/main/windows/ankiWindow.ts index f987739..fe5614a 100644 --- a/src/main/windows/ankiWindow.ts +++ b/src/main/windows/ankiWindow.ts @@ -1,7 +1,8 @@ -import { BrowserWindow, nativeTheme } from 'electron' +import { BrowserWindow } from 'electron' import { join } from 'path' import { is } from '@electron-toolkit/utils' import { settingsManager } from '../config' +import { applyTitleBarTheme, titleBarOverlayOptions } from './titleBarOverlay' let ankiWindow: BrowserWindow | null = null let settingsUnsubscribe: (() => void) | null = null @@ -30,7 +31,6 @@ export function createAnkiWindow(notebookId: string, ankiCardId?: string): void // 根据用户主题设置背景色 const theme = settingsManager.getSettingSync('theme') const backgroundColor = theme === 'dark' ? '#1a1b1e' : '#fafafa' - const preferDark = nativeTheme.shouldUseDarkColors || theme === 'dark' // 创建Anki卡片窗口 ankiWindow = new BrowserWindow({ @@ -43,15 +43,7 @@ export function createAnkiWindow(notebookId: string, ankiCardId?: string): void titleBarStyle: 'hidden', // Position macOS traffic lights (window controls) ...(process.platform === 'darwin' ? { trafficLightPosition: { x: 16, y: 16 } } : {}), - ...(process.platform !== 'darwin' - ? { - titleBarOverlay: { - color: 'rgba(0,0,0,0)', - height: 35, - symbolColor: preferDark ? 'white' : 'black' - } - } - : {}), + ...(process.platform !== 'darwin' ? { titleBarOverlay: titleBarOverlayOptions(theme) } : {}), backgroundColor, webPreferences: { preload: join(__dirname, '../preload/index.js'), @@ -84,12 +76,7 @@ export function createAnkiWindow(notebookId: string, ankiCardId?: string): void const newBackgroundColor = newSettings.theme === 'dark' ? '#1a1b1e' : '#fafafa' ankiWindow.setBackgroundColor(newBackgroundColor) // 更新 titleBarOverlay 的符号颜色 - const newSymbol = newSettings.theme === 'dark' ? 'white' : 'black' - try { - ankiWindow.setTitleBarOverlay({ symbolColor: newSymbol }) - } catch { - // 某些平台或旧版本可能不支持 setTitleBarOverlay - } + applyTitleBarTheme(ankiWindow, newSettings.theme) } }) .then((unsubscribe) => { diff --git a/src/main/windows/mainWindow.ts b/src/main/windows/mainWindow.ts index 7f62619..33de123 100644 --- a/src/main/windows/mainWindow.ts +++ b/src/main/windows/mainWindow.ts @@ -2,6 +2,7 @@ import { BrowserWindow, shell } from 'electron' import { join } from 'path' import { is } from '@electron-toolkit/utils' import { settingsManager } from '../config' +import { applyTitleBarTheme, titleBarOverlayOptions } from './titleBarOverlay' let mainWindow: BrowserWindow | null = null @@ -25,9 +26,7 @@ export function createMainWindow(): BrowserWindow { // Position macOS traffic lights (window controls) ...(process.platform === 'darwin' ? { trafficLightPosition: { x: 16, y: 16 } } : {}), // expose window controls in Windows/Linux - ...(process.platform !== 'darwin' - ? { titleBarOverlay: { color: 'rgba(0,0,0,0)', height: 35, symbolColor: 'white' } } - : {}), + ...(process.platform !== 'darwin' ? { titleBarOverlay: titleBarOverlayOptions(theme) } : {}), backgroundColor, webPreferences: { preload: join(__dirname, '../preload/index.js'), @@ -44,11 +43,12 @@ export function createMainWindow(): BrowserWindow { return { action: 'deny' } }) - // 监听主题变化,动态更新窗口背景色 + // 监听主题变化,动态更新窗口背景色和窗口按钮颜色 settingsManager.onSettingsChangeSync((newSettings) => { if (mainWindow && !mainWindow.isDestroyed()) { const newBackgroundColor = newSettings.theme === 'dark' ? '#282c34' : '#fafafa' mainWindow.setBackgroundColor(newBackgroundColor) + applyTitleBarTheme(mainWindow, newSettings.theme) } }) diff --git a/src/main/windows/mindMapWindow.ts b/src/main/windows/mindMapWindow.ts index 69373b7..e54cb7a 100644 --- a/src/main/windows/mindMapWindow.ts +++ b/src/main/windows/mindMapWindow.ts @@ -2,6 +2,7 @@ import { BrowserWindow } from 'electron' import { join } from 'path' import { is } from '@electron-toolkit/utils' import { settingsManager } from '../config' +import { applyTitleBarTheme, titleBarOverlayOptions } from './titleBarOverlay' let mindMapWindow: BrowserWindow | null = null @@ -41,9 +42,7 @@ export function createMindMapWindow(notebookId: string, mindMapId?: string): voi titleBarStyle: 'hidden', // Position macOS traffic lights (window controls) ...(process.platform === 'darwin' ? { trafficLightPosition: { x: 16, y: 16 } } : {}), - ...(process.platform !== 'darwin' - ? { titleBarOverlay: { color: 'rgba(0,0,0,0)', height: 35, symbolColor: 'white' } } - : {}), + ...(process.platform !== 'darwin' ? { titleBarOverlay: titleBarOverlayOptions(theme) } : {}), backgroundColor, webPreferences: { preload: join(__dirname, '../preload/index.js'), @@ -64,6 +63,7 @@ export function createMindMapWindow(notebookId: string, mindMapId?: string): voi if (mindMapWindow && !mindMapWindow.isDestroyed()) { const newBackgroundColor = newSettings.theme === 'dark' ? '#1a1b1e' : '#fafafa' mindMapWindow.setBackgroundColor(newBackgroundColor) + applyTitleBarTheme(mindMapWindow, newSettings.theme) } }) diff --git a/src/main/windows/quizWindow.ts b/src/main/windows/quizWindow.ts index 0994818..681947c 100644 --- a/src/main/windows/quizWindow.ts +++ b/src/main/windows/quizWindow.ts @@ -2,6 +2,7 @@ import { BrowserWindow } from 'electron' import { join } from 'path' import { is } from '@electron-toolkit/utils' import { settingsManager } from '../config' +import { applyTitleBarTheme, titleBarOverlayOptions } from './titleBarOverlay' let quizWindow: BrowserWindow | null = null @@ -41,9 +42,7 @@ export function createQuizWindow(notebookId: string, quizId?: string): void { titleBarStyle: 'hidden', // Position macOS traffic lights (window controls) ...(process.platform === 'darwin' ? { trafficLightPosition: { x: 16, y: 16 } } : {}), - ...(process.platform !== 'darwin' - ? { titleBarOverlay: { color: 'rgba(0,0,0,0)', height: 35, symbolColor: 'white' } } - : {}), + ...(process.platform !== 'darwin' ? { titleBarOverlay: titleBarOverlayOptions(theme) } : {}), backgroundColor, webPreferences: { preload: join(__dirname, '../preload/index.js'), @@ -64,6 +63,7 @@ export function createQuizWindow(notebookId: string, quizId?: string): void { if (quizWindow && !quizWindow.isDestroyed()) { const newBackgroundColor = newSettings.theme === 'dark' ? '#1a1b1e' : '#fafafa' quizWindow.setBackgroundColor(newBackgroundColor) + applyTitleBarTheme(quizWindow, newSettings.theme) } }) diff --git a/src/main/windows/titleBarOverlay.ts b/src/main/windows/titleBarOverlay.ts new file mode 100644 index 0000000..4dc7d02 --- /dev/null +++ b/src/main/windows/titleBarOverlay.ts @@ -0,0 +1,31 @@ +import type { BrowserWindow } from 'electron' +import type { AppSettings } from '../config' + +/** + * The OS draws the window controls (minimize / maximize / close) on top of our + * custom title bar. The overlay fill is transparent, so the symbol colour has to + * match the renderer: a white symbol on the light `#fafafa` title bar is + * invisible. That is issue #26. + */ +export function titleBarSymbolColor(theme: AppSettings['theme']): 'white' | 'black' { + return theme === 'dark' ? 'white' : 'black' +} + +/** `titleBarOverlay` options for the transparent custom title bar (Windows/Linux). */ +export function titleBarOverlayOptions(theme: AppSettings['theme']): { + color: string + height: number + symbolColor: 'white' | 'black' +} { + return { color: 'rgba(0,0,0,0)', height: 35, symbolColor: titleBarSymbolColor(theme) } +} + +/** Re-colour the window controls after a theme change. macOS has no overlay. */ +export function applyTitleBarTheme(window: BrowserWindow, theme: AppSettings['theme']): void { + if (process.platform === 'darwin') return + try { + window.setTitleBarOverlay({ symbolColor: titleBarSymbolColor(theme) }) + } catch { + // Older Electron builds may not implement setTitleBarOverlay. + } +} diff --git a/test/titleBarOverlay.test.ts b/test/titleBarOverlay.test.ts new file mode 100644 index 0000000..ba90bad --- /dev/null +++ b/test/titleBarOverlay.test.ts @@ -0,0 +1,26 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { titleBarOverlayOptions, titleBarSymbolColor } from '../src/main/windows/titleBarOverlay.ts' + +/** + * Issue #26: the custom title bar's window controls are drawn by the OS with + * `symbolColor`. In light mode a white symbol sits on the `#fafafa` title bar + * and becomes invisible, so the symbol colour must follow the app theme. + */ +test('window-control symbols are dark in light mode and light in dark mode', () => { + assert.equal(titleBarSymbolColor('light'), 'black') + assert.equal(titleBarSymbolColor('dark'), 'white') +}) + +test('titleBarOverlay keeps the transparent fill and fixed height', () => { + assert.deepEqual(titleBarOverlayOptions('light'), { + color: 'rgba(0,0,0,0)', + height: 35, + symbolColor: 'black' + }) + assert.deepEqual(titleBarOverlayOptions('dark'), { + color: 'rgba(0,0,0,0)', + height: 35, + symbolColor: 'white' + }) +})