Skip to content

Commit 07f1c8c

Browse files
authored
fix(desktop): stabilize Windows titlebar zoom (#25813)
1 parent 2d0a757 commit 07f1c8c

4 files changed

Lines changed: 47 additions & 12 deletions

File tree

packages/app/src/components/titlebar.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ type TauriApi = {
3535
const tauriApi = () => (window as unknown as { __TAURI__?: TauriApi }).__TAURI__
3636
const currentDesktopWindow = () => tauriApi()?.window?.getCurrentWindow?.()
3737
const currentThemeWindow = () => tauriApi()?.webviewWindow?.getCurrentWebviewWindow?.()
38+
const titlebarHeight = 40
39+
const minTitlebarZoom = 0.25
40+
const windowsControlsBaseWidth = 138 // 3 native Windows caption buttons at 46px each.
3841

3942
export function Titlebar() {
4043
const layout = useLayout()
@@ -51,7 +54,14 @@ export function Titlebar() {
5154
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
5255
const web = createMemo(() => platform.platform === "web")
5356
const zoom = () => platform.webviewZoom?.() ?? 1
54-
const minHeight = () => (mac() ? `${40 / zoom()}px` : undefined)
57+
const titlebarZoom = () => (windows() ? Math.max(zoom(), minTitlebarZoom) : zoom())
58+
const counterZoom = () => (windows() && titlebarZoom() < 1 ? 1 / titlebarZoom() : 1)
59+
const minHeight = () => {
60+
if (mac()) return `${titlebarHeight / zoom()}px`
61+
if (windows()) return `${titlebarHeight / Math.min(titlebarZoom(), 1)}px`
62+
return undefined
63+
}
64+
const windowsControlsWidth = () => `${windowsControlsBaseWidth / Math.max(titlebarZoom(), 1)}px`
5565

5666
const [history, setHistory] = createStore({
5767
stack: [] as string[],
@@ -165,12 +175,16 @@ export function Titlebar() {
165175

166176
return (
167177
<header
168-
class="h-10 shrink-0 bg-background-base relative grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center"
178+
class="h-10 shrink-0 bg-background-base relative overflow-hidden"
169179
style={{ "min-height": minHeight() }}
170180
data-tauri-drag-region
171181
onMouseDown={drag}
172182
onDblClick={maximize}
173183
>
184+
<div
185+
class="grid h-full min-h-full w-full grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center"
186+
style={{ zoom: counterZoom() }}
187+
>
174188
<div
175189
classList={{
176190
"flex items-center min-w-0": true,
@@ -312,10 +326,11 @@ export function Titlebar() {
312326
>
313327
<div id="opencode-titlebar-right" class="flex items-center gap-1 shrink-0 justify-end" />
314328
<Show when={windows()}>
315-
{!tauriApi() && <div class="w-36 shrink-0" />}
329+
{!tauriApi() && <div class="shrink-0" style={{ width: windowsControlsWidth() }} />}
316330
<div data-tauri-decorum-tb class="flex flex-row" />
317331
</Show>
318332
</div>
333+
</div>
319334
</header>
320335
)
321336
}

packages/desktop-electron/src/main/ipc.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {
1111
WslConfig,
1212
} from "../preload/types"
1313
import { getStore } from "./store"
14-
import { setTitlebar } from "./windows"
14+
import { setTitlebar, updateTitlebar } from "./windows"
1515

1616
const pickerFilters = (ext?: string[]) => {
1717
if (!ext || ext.length === 0) return undefined
@@ -183,7 +183,12 @@ export function registerIpcHandlers(deps: Deps) {
183183
})
184184

185185
ipcMain.handle("get-zoom-factor", (event: IpcMainInvokeEvent) => event.sender.getZoomFactor())
186-
ipcMain.handle("set-zoom-factor", (event: IpcMainInvokeEvent, factor: number) => event.sender.setZoomFactor(factor))
186+
ipcMain.handle("set-zoom-factor", (event: IpcMainInvokeEvent, factor: number) => {
187+
event.sender.setZoomFactor(factor)
188+
const win = BrowserWindow.fromWebContents(event.sender)
189+
if (!win) return
190+
updateTitlebar(win)
191+
})
187192
ipcMain.handle("set-titlebar", (event: IpcMainInvokeEvent, theme: TitlebarTheme) => {
188193
const win = BrowserWindow.fromWebContents(event.sender)
189194
if (!win) return

packages/desktop-electron/src/main/windows.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ protocol.registerSchemesAsPrivileged([
2121
])
2222

2323
let backgroundColor: string | undefined
24+
const titlebarThemes = new WeakMap<BrowserWindow, Partial<TitlebarTheme>>()
25+
const titlebarHeight = 40
2426

2527
export function setBackgroundColor(color: string) {
2628
backgroundColor = color
@@ -43,18 +45,23 @@ function tone() {
4345
return nativeTheme.shouldUseDarkColors ? "dark" : "light"
4446
}
4547

46-
function overlay(theme: Partial<TitlebarTheme> = {}) {
48+
function overlay(theme: Partial<TitlebarTheme> = {}, zoom = 1) {
4749
const mode = theme.mode ?? tone()
4850
return {
4951
color: "#00000000",
5052
symbolColor: mode === "dark" ? "white" : "black",
51-
height: 40,
53+
height: Math.max(titlebarHeight, Math.round(titlebarHeight * zoom)),
5254
}
5355
}
5456

5557
export function setTitlebar(win: BrowserWindow, theme: Partial<TitlebarTheme> = {}) {
58+
titlebarThemes.set(win, theme)
59+
updateTitlebar(win)
60+
}
61+
62+
export function updateTitlebar(win: BrowserWindow) {
5663
if (process.platform !== "win32") return
57-
win.setTitleBarOverlay(overlay(theme))
64+
win.setTitleBarOverlay(overlay(titlebarThemes.get(win), win.webContents.getZoomFactor()))
5865
}
5966

6067
export function setDockIcon() {
@@ -188,6 +195,7 @@ function wireZoom(win: BrowserWindow) {
188195
win.webContents.setZoomFactor(1)
189196
win.webContents.on("zoom-changed", () => {
190197
win.webContents.setZoomFactor(1)
198+
updateTitlebar(win)
191199
})
192200
}
193201

packages/desktop-electron/src/renderer/webview-zoom.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,35 @@ const OS_NAME = (() => {
1212
})()
1313

1414
const [webviewZoom, setWebviewZoom] = createSignal(1)
15+
let requestedZoom = 1
1516

1617
const MAX_ZOOM_LEVEL = 10
1718
const MIN_ZOOM_LEVEL = 0.2
1819

1920
const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL)
2021

2122
const applyZoom = (next: number) => {
22-
setWebviewZoom(next)
23-
void window.api.setZoomFactor(next)
23+
requestedZoom = next
24+
void window.api.setZoomFactor(next).then(() => {
25+
if (requestedZoom !== next) return
26+
setWebviewZoom(next)
27+
}).catch(() => {
28+
if (requestedZoom !== next) return
29+
requestedZoom = webviewZoom()
30+
})
2431
}
2532

2633
window.addEventListener("keydown", (event) => {
2734
if (!(OS_NAME === "macos" ? event.metaKey : event.ctrlKey)) return
2835

2936
if (event.key === "-") {
3037
event.preventDefault()
31-
applyZoom(clamp(webviewZoom() - 0.2))
38+
applyZoom(clamp(requestedZoom - 0.2))
3239
return
3340
}
3441
if (event.key === "=" || event.key === "+") {
3542
event.preventDefault()
36-
applyZoom(clamp(webviewZoom() + 0.2))
43+
applyZoom(clamp(requestedZoom + 0.2))
3744
return
3845
}
3946
if (event.key === "0") {

0 commit comments

Comments
 (0)