Procházet zdrojové kódy

fix(desktop): stabilize Windows titlebar zoom (#25813)

Luke Parker před 4 měsíci
rodič
revize
07f1c8c0ac

+ 18 - 3
packages/app/src/components/titlebar.tsx

@@ -35,6 +35,9 @@ type TauriApi = {
 const tauriApi = () => (window as unknown as { __TAURI__?: TauriApi }).__TAURI__
 const currentDesktopWindow = () => tauriApi()?.window?.getCurrentWindow?.()
 const currentThemeWindow = () => tauriApi()?.webviewWindow?.getCurrentWebviewWindow?.()
+const titlebarHeight = 40
+const minTitlebarZoom = 0.25
+const windowsControlsBaseWidth = 138 // 3 native Windows caption buttons at 46px each.
 
 export function Titlebar() {
   const layout = useLayout()
@@ -51,7 +54,14 @@ export function Titlebar() {
   const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
   const web = createMemo(() => platform.platform === "web")
   const zoom = () => platform.webviewZoom?.() ?? 1
-  const minHeight = () => (mac() ? `${40 / zoom()}px` : undefined)
+  const titlebarZoom = () => (windows() ? Math.max(zoom(), minTitlebarZoom) : zoom())
+  const counterZoom = () => (windows() && titlebarZoom() < 1 ? 1 / titlebarZoom() : 1)
+  const minHeight = () => {
+    if (mac()) return `${titlebarHeight / zoom()}px`
+    if (windows()) return `${titlebarHeight / Math.min(titlebarZoom(), 1)}px`
+    return undefined
+  }
+  const windowsControlsWidth = () => `${windowsControlsBaseWidth / Math.max(titlebarZoom(), 1)}px`
 
   const [history, setHistory] = createStore({
     stack: [] as string[],
@@ -165,12 +175,16 @@ export function Titlebar() {
 
   return (
     <header
-      class="h-10 shrink-0 bg-background-base relative grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center"
+      class="h-10 shrink-0 bg-background-base relative overflow-hidden"
       style={{ "min-height": minHeight() }}
       data-tauri-drag-region
       onMouseDown={drag}
       onDblClick={maximize}
     >
+      <div
+        class="grid h-full min-h-full w-full grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center"
+        style={{ zoom: counterZoom() }}
+      >
       <div
         classList={{
           "flex items-center min-w-0": true,
@@ -312,10 +326,11 @@ export function Titlebar() {
       >
         <div id="opencode-titlebar-right" class="flex items-center gap-1 shrink-0 justify-end" />
         <Show when={windows()}>
-          {!tauriApi() && <div class="w-36 shrink-0" />}
+          {!tauriApi() && <div class="shrink-0" style={{ width: windowsControlsWidth() }} />}
           <div data-tauri-decorum-tb class="flex flex-row" />
         </Show>
       </div>
+      </div>
     </header>
   )
 }

+ 7 - 2
packages/desktop-electron/src/main/ipc.ts

@@ -11,7 +11,7 @@ import type {
   WslConfig,
 } from "../preload/types"
 import { getStore } from "./store"
-import { setTitlebar } from "./windows"
+import { setTitlebar, updateTitlebar } from "./windows"
 
 const pickerFilters = (ext?: string[]) => {
   if (!ext || ext.length === 0) return undefined
@@ -183,7 +183,12 @@ export function registerIpcHandlers(deps: Deps) {
   })
 
   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)
+    const win = BrowserWindow.fromWebContents(event.sender)
+    if (!win) return
+    updateTitlebar(win)
+  })
   ipcMain.handle("set-titlebar", (event: IpcMainInvokeEvent, theme: TitlebarTheme) => {
     const win = BrowserWindow.fromWebContents(event.sender)
     if (!win) return

+ 11 - 3
packages/desktop-electron/src/main/windows.ts

@@ -21,6 +21,8 @@ protocol.registerSchemesAsPrivileged([
 ])
 
 let backgroundColor: string | undefined
+const titlebarThemes = new WeakMap<BrowserWindow, Partial<TitlebarTheme>>()
+const titlebarHeight = 40
 
 export function setBackgroundColor(color: string) {
   backgroundColor = color
@@ -43,18 +45,23 @@ function tone() {
   return nativeTheme.shouldUseDarkColors ? "dark" : "light"
 }
 
-function overlay(theme: Partial<TitlebarTheme> = {}) {
+function overlay(theme: Partial<TitlebarTheme> = {}, zoom = 1) {
   const mode = theme.mode ?? tone()
   return {
     color: "#00000000",
     symbolColor: mode === "dark" ? "white" : "black",
-    height: 40,
+    height: Math.max(titlebarHeight, Math.round(titlebarHeight * zoom)),
   }
 }
 
 export function setTitlebar(win: BrowserWindow, theme: Partial<TitlebarTheme> = {}) {
+  titlebarThemes.set(win, theme)
+  updateTitlebar(win)
+}
+
+export function updateTitlebar(win: BrowserWindow) {
   if (process.platform !== "win32") return
-  win.setTitleBarOverlay(overlay(theme))
+  win.setTitleBarOverlay(overlay(titlebarThemes.get(win), win.webContents.getZoomFactor()))
 }
 
 export function setDockIcon() {
@@ -188,6 +195,7 @@ function wireZoom(win: BrowserWindow) {
   win.webContents.setZoomFactor(1)
   win.webContents.on("zoom-changed", () => {
     win.webContents.setZoomFactor(1)
+    updateTitlebar(win)
   })
 }
 

+ 11 - 4
packages/desktop-electron/src/renderer/webview-zoom.ts

@@ -12,6 +12,7 @@ const OS_NAME = (() => {
 })()
 
 const [webviewZoom, setWebviewZoom] = createSignal(1)
+let requestedZoom = 1
 
 const MAX_ZOOM_LEVEL = 10
 const MIN_ZOOM_LEVEL = 0.2
@@ -19,8 +20,14 @@ const MIN_ZOOM_LEVEL = 0.2
 const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL)
 
 const applyZoom = (next: number) => {
-  setWebviewZoom(next)
-  void window.api.setZoomFactor(next)
+  requestedZoom = next
+  void window.api.setZoomFactor(next).then(() => {
+    if (requestedZoom !== next) return
+    setWebviewZoom(next)
+  }).catch(() => {
+    if (requestedZoom !== next) return
+    requestedZoom = webviewZoom()
+  })
 }
 
 window.addEventListener("keydown", (event) => {
@@ -28,12 +35,12 @@ window.addEventListener("keydown", (event) => {
 
   if (event.key === "-") {
     event.preventDefault()
-    applyZoom(clamp(webviewZoom() - 0.2))
+    applyZoom(clamp(requestedZoom - 0.2))
     return
   }
   if (event.key === "=" || event.key === "+") {
     event.preventDefault()
-    applyZoom(clamp(webviewZoom() + 0.2))
+    applyZoom(clamp(requestedZoom + 0.2))
     return
   }
   if (event.key === "0") {