mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 05:43:55 +00:00
fix(electron): theme Windows titlebar overlay (#16843)
Co-authored-by: Brendan Allan <brendonovich@outlook.com>
This commit is contained in:
parent
54e7baa6cf
commit
d481f64bde
@ -62,6 +62,9 @@ declare global {
|
|||||||
deepLinks?: string[]
|
deepLinks?: string[]
|
||||||
wsl?: boolean
|
wsl?: boolean
|
||||||
}
|
}
|
||||||
|
api?: {
|
||||||
|
setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise<void>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +118,11 @@ export function AppBaseProviders(props: ParentProps) {
|
|||||||
return (
|
return (
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<Font />
|
<Font />
|
||||||
<ThemeProvider>
|
<ThemeProvider
|
||||||
|
onThemeApplied={(_, mode) => {
|
||||||
|
void window.api?.setTitlebar?.({ mode })
|
||||||
|
}}
|
||||||
|
>
|
||||||
<LanguageProvider>
|
<LanguageProvider>
|
||||||
<UiI18nBridge>
|
<UiI18nBridge>
|
||||||
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
|
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { createEffect, createMemo, Show, untrack } from "solid-js"
|
import { createEffect, createMemo, onCleanup, Show, untrack } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
@ -282,7 +282,7 @@ export function Titlebar() {
|
|||||||
>
|
>
|
||||||
<div id="opencode-titlebar-right" class="flex items-center gap-1 shrink-0 justify-end" />
|
<div id="opencode-titlebar-right" class="flex items-center gap-1 shrink-0 justify-end" />
|
||||||
<Show when={windows()}>
|
<Show when={windows()}>
|
||||||
<div class="w-6 shrink-0" />
|
{!tauriApi() && <div class="w-36 shrink-0" />}
|
||||||
<div data-tauri-decorum-tb class="flex flex-row" />
|
<div data-tauri-decorum-tb class="flex flex-row" />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,8 +2,9 @@ import { execFile } from "node:child_process"
|
|||||||
import { BrowserWindow, Notification, app, clipboard, dialog, ipcMain, shell } from "electron"
|
import { BrowserWindow, Notification, app, clipboard, dialog, ipcMain, shell } from "electron"
|
||||||
import type { IpcMainEvent, IpcMainInvokeEvent } from "electron"
|
import type { IpcMainEvent, IpcMainInvokeEvent } from "electron"
|
||||||
|
|
||||||
import type { InitStep, ServerReadyData, SqliteMigrationProgress, WslConfig } from "../preload/types"
|
import type { InitStep, ServerReadyData, SqliteMigrationProgress, TitlebarTheme, WslConfig } from "../preload/types"
|
||||||
import { getStore } from "./store"
|
import { getStore } from "./store"
|
||||||
|
import { setTitlebar } from "./windows"
|
||||||
|
|
||||||
type Deps = {
|
type Deps = {
|
||||||
killSidecar: () => void
|
killSidecar: () => void
|
||||||
@ -161,6 +162,11 @@ export function registerIpcHandlers(deps: Deps) {
|
|||||||
|
|
||||||
ipcMain.handle("get-zoom-factor", (event: IpcMainInvokeEvent) => event.sender.getZoomFactor())
|
ipcMain.handle("get-zoom-factor", (event: IpcMainInvokeEvent) => event.sender.getZoomFactor())
|
||||||
ipcMain.handle("set-zoom-factor", (event: IpcMainInvokeEvent, factor: number) => event.sender.setZoomFactor(factor))
|
ipcMain.handle("set-zoom-factor", (event: IpcMainInvokeEvent, factor: number) => event.sender.setZoomFactor(factor))
|
||||||
|
ipcMain.handle("set-titlebar", (event: IpcMainInvokeEvent, theme: TitlebarTheme) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender)
|
||||||
|
if (!win) return
|
||||||
|
setTitlebar(win, theme)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendSqliteMigrationProgress(win: BrowserWindow, progress: SqliteMigrationProgress) {
|
export function sendSqliteMigrationProgress(win: BrowserWindow, progress: SqliteMigrationProgress) {
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import windowState from "electron-window-state"
|
import windowState from "electron-window-state"
|
||||||
import { app, BrowserWindow, nativeImage } from "electron"
|
import { app, BrowserWindow, nativeImage, nativeTheme } from "electron"
|
||||||
import { dirname, join } from "node:path"
|
import { dirname, join } from "node:path"
|
||||||
import { fileURLToPath } from "node:url"
|
import { fileURLToPath } from "node:url"
|
||||||
|
import type { TitlebarTheme } from "../preload/types"
|
||||||
|
|
||||||
type Globals = {
|
type Globals = {
|
||||||
updaterEnabled: boolean
|
updaterEnabled: boolean
|
||||||
@ -20,6 +21,24 @@ function iconPath() {
|
|||||||
return join(iconsDir(), `icon.${ext}`)
|
return join(iconsDir(), `icon.${ext}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tone() {
|
||||||
|
return nativeTheme.shouldUseDarkColors ? "dark" : "light"
|
||||||
|
}
|
||||||
|
|
||||||
|
function overlay(theme: Partial<TitlebarTheme> = {}) {
|
||||||
|
const mode = theme.mode ?? tone()
|
||||||
|
return {
|
||||||
|
color: "#00000000",
|
||||||
|
symbolColor: mode === "dark" ? "white" : "black",
|
||||||
|
height: 40,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTitlebar(win: BrowserWindow, theme: Partial<TitlebarTheme> = {}) {
|
||||||
|
if (process.platform !== "win32") return
|
||||||
|
win.setTitleBarOverlay(overlay(theme))
|
||||||
|
}
|
||||||
|
|
||||||
export function setDockIcon() {
|
export function setDockIcon() {
|
||||||
if (process.platform !== "darwin") return
|
if (process.platform !== "darwin") return
|
||||||
app.dock?.setIcon(nativeImage.createFromPath(join(iconsDir(), "128x128@2x.png")))
|
app.dock?.setIcon(nativeImage.createFromPath(join(iconsDir(), "128x128@2x.png")))
|
||||||
@ -31,6 +50,7 @@ export function createMainWindow(globals: Globals) {
|
|||||||
defaultHeight: 800,
|
defaultHeight: 800,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mode = tone()
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
x: state.x,
|
x: state.x,
|
||||||
y: state.y,
|
y: state.y,
|
||||||
@ -49,11 +69,7 @@ export function createMainWindow(globals: Globals) {
|
|||||||
? {
|
? {
|
||||||
frame: false,
|
frame: false,
|
||||||
titleBarStyle: "hidden" as const,
|
titleBarStyle: "hidden" as const,
|
||||||
titleBarOverlay: {
|
titleBarOverlay: overlay({ mode }),
|
||||||
color: "transparent",
|
|
||||||
symbolColor: "#999",
|
|
||||||
height: 40,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
@ -71,6 +87,7 @@ export function createMainWindow(globals: Globals) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createLoadingWindow(globals: Globals) {
|
export function createLoadingWindow(globals: Globals) {
|
||||||
|
const mode = tone()
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
width: 640,
|
width: 640,
|
||||||
height: 480,
|
height: 480,
|
||||||
@ -83,11 +100,7 @@ export function createLoadingWindow(globals: Globals) {
|
|||||||
? {
|
? {
|
||||||
frame: false,
|
frame: false,
|
||||||
titleBarStyle: "hidden" as const,
|
titleBarStyle: "hidden" as const,
|
||||||
titleBarOverlay: {
|
titleBarOverlay: overlay({ mode }),
|
||||||
color: "transparent",
|
|
||||||
symbolColor: "#999",
|
|
||||||
height: 40,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
|||||||
@ -57,6 +57,7 @@ const api: ElectronAPI = {
|
|||||||
relaunch: () => ipcRenderer.send("relaunch"),
|
relaunch: () => ipcRenderer.send("relaunch"),
|
||||||
getZoomFactor: () => ipcRenderer.invoke("get-zoom-factor"),
|
getZoomFactor: () => ipcRenderer.invoke("get-zoom-factor"),
|
||||||
setZoomFactor: (factor) => ipcRenderer.invoke("set-zoom-factor", factor),
|
setZoomFactor: (factor) => ipcRenderer.invoke("set-zoom-factor", factor),
|
||||||
|
setTitlebar: (theme) => ipcRenderer.invoke("set-titlebar", theme),
|
||||||
loadingWindowComplete: () => ipcRenderer.send("loading-window-complete"),
|
loadingWindowComplete: () => ipcRenderer.send("loading-window-complete"),
|
||||||
runUpdater: (alertOnFail) => ipcRenderer.invoke("run-updater", alertOnFail),
|
runUpdater: (alertOnFail) => ipcRenderer.invoke("run-updater", alertOnFail),
|
||||||
checkUpdate: () => ipcRenderer.invoke("check-update"),
|
checkUpdate: () => ipcRenderer.invoke("check-update"),
|
||||||
|
|||||||
@ -10,6 +10,9 @@ export type SqliteMigrationProgress = { type: "InProgress"; value: number } | {
|
|||||||
export type WslConfig = { enabled: boolean }
|
export type WslConfig = { enabled: boolean }
|
||||||
|
|
||||||
export type LinuxDisplayBackend = "wayland" | "auto"
|
export type LinuxDisplayBackend = "wayland" | "auto"
|
||||||
|
export type TitlebarTheme = {
|
||||||
|
mode: "light" | "dark"
|
||||||
|
}
|
||||||
|
|
||||||
export type ElectronAPI = {
|
export type ElectronAPI = {
|
||||||
killSidecar: () => Promise<void>
|
killSidecar: () => Promise<void>
|
||||||
@ -57,6 +60,7 @@ export type ElectronAPI = {
|
|||||||
relaunch: () => void
|
relaunch: () => void
|
||||||
getZoomFactor: () => Promise<number>
|
getZoomFactor: () => Promise<number>
|
||||||
setZoomFactor: (factor: number) => Promise<void>
|
setZoomFactor: (factor: number) => Promise<void>
|
||||||
|
setTitlebar: (theme: TitlebarTheme) => Promise<void>
|
||||||
loadingWindowComplete: () => void
|
loadingWindowComplete: () => void
|
||||||
runUpdater: (alertOnFail: boolean) => Promise<void>
|
runUpdater: (alertOnFail: boolean) => Promise<void>
|
||||||
checkUpdate: () => Promise<{ updateAvailable: boolean; version?: string }>
|
checkUpdate: () => Promise<{ updateAvailable: boolean; version?: string }>
|
||||||
|
|||||||
@ -77,7 +77,7 @@ function cacheThemeVariants(theme: DesktopTheme, themeId: string) {
|
|||||||
|
|
||||||
export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
||||||
name: "Theme",
|
name: "Theme",
|
||||||
init: (props: { defaultTheme?: string }) => {
|
init: (props: { defaultTheme?: string; onThemeApplied?: (theme: DesktopTheme, mode: "light" | "dark") => void }) => {
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
themes: DEFAULT_THEMES as Record<string, DesktopTheme>,
|
themes: DEFAULT_THEMES as Record<string, DesktopTheme>,
|
||||||
themeId: normalize(props.defaultTheme) ?? "oc-2",
|
themeId: normalize(props.defaultTheme) ?? "oc-2",
|
||||||
@ -119,10 +119,15 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const applyTheme = (theme: DesktopTheme, themeId: string, mode: "light" | "dark") => {
|
||||||
|
applyThemeCss(theme, themeId, mode)
|
||||||
|
props.onThemeApplied?.(theme, mode)
|
||||||
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const theme = store.themes[store.themeId]
|
const theme = store.themes[store.themeId]
|
||||||
if (theme) {
|
if (theme) {
|
||||||
applyThemeCss(theme, store.themeId, store.mode)
|
applyTheme(theme, store.themeId, store.mode)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -171,7 +176,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||||||
? getSystemMode()
|
? getSystemMode()
|
||||||
: store.previewScheme
|
: store.previewScheme
|
||||||
: store.mode
|
: store.mode
|
||||||
applyThemeCss(theme, next, previewMode)
|
applyTheme(theme, next, previewMode)
|
||||||
},
|
},
|
||||||
previewColorScheme: (scheme: ColorScheme) => {
|
previewColorScheme: (scheme: ColorScheme) => {
|
||||||
setStore("previewScheme", scheme)
|
setStore("previewScheme", scheme)
|
||||||
@ -179,7 +184,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||||||
const id = store.previewThemeId ?? store.themeId
|
const id = store.previewThemeId ?? store.themeId
|
||||||
const theme = store.themes[id]
|
const theme = store.themes[id]
|
||||||
if (theme) {
|
if (theme) {
|
||||||
applyThemeCss(theme, id, previewMode)
|
applyTheme(theme, id, previewMode)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commitPreview: () => {
|
commitPreview: () => {
|
||||||
@ -197,7 +202,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|||||||
setStore("previewScheme", null)
|
setStore("previewScheme", null)
|
||||||
const theme = store.themes[store.themeId]
|
const theme = store.themes[store.themeId]
|
||||||
if (theme) {
|
if (theme) {
|
||||||
applyThemeCss(theme, store.themeId, store.mode)
|
applyTheme(theme, store.themeId, store.mode)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user