diff --git a/electron/main/index.ts b/electron/main/index.ts index 858f80c..7dcd85d 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, dialog, ipcMain, session } from 'electron' +import { app, BrowserWindow, dialog, ipcMain, session, systemPreferences } from 'electron' import type { IpcMainInvokeEvent } from 'electron' import { mkdir, readFile, writeFile } from 'node:fs/promises' import { execFile } from 'node:child_process' @@ -227,6 +227,32 @@ async function pickOpenPath(): Promise { } function registerIpcHandlers(): void { + /* + * `titleBarStyle: 'hiddenInset'` gives the renderer the drag region + * (`-webkit-app-region: drag` on the TopBar, see TopBar.module.css) but + * NOT the OS's usual double-click-to-zoom behaviour that a real title + * bar has for free — Chromium's drag region is a plain mouse listener, + * not an NSWindow title bar, so nothing tells macOS to react to the + * second click. The renderer's own dblclick handler calls this so we + * can honour the user's actual System Settings > Desktop & Dock choice + * ("Zoom", "Minimize" or "None") instead of hardcoding one. + */ + ipcMain.handle('window:titlebarDoubleClick', (evt) => { + if (process.platform !== 'darwin') return + const win = BrowserWindow.fromWebContents(evt.sender) + if (!win) return + const action = systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string') + if (action === 'Minimize') { + win.minimize() + } else if (action === 'None') { + // no-op, matches the system setting + } else { + // Default action ('Maximize', or unset) mirrors a native title bar. + if (win.isMaximized()) win.unmaximize() + else win.maximize() + } + }) + ipcMain.handle('dialog:save', async (evt, defaultName: string) => { /* * Parent the modal to the window that MADE the call, not to whatever diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 4bba3fc..eff60b6 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -268,7 +268,9 @@ const api = { window: { /** Represented file + edited flag: macOS proxy icon and title-bar dot. */ setDocument: (p: { path: string | null; edited: boolean }): void => - ipcRenderer.send('window:document', p) + ipcRenderer.send('window:document', p), + titlebarDoubleClick: (): Promise => + ipcRenderer.invoke('window:titlebarDoubleClick') }, /** Errors the main process caught instead of dying from. */ onMainError: (cb: (msg: string) => void): (() => void) => onChannel('app:main-error', cb), diff --git a/src/components/layout/TopBar.module.css b/src/components/layout/TopBar.module.css index f53debd..aefa754 100644 --- a/src/components/layout/TopBar.module.css +++ b/src/components/layout/TopBar.module.css @@ -22,6 +22,16 @@ */ position: relative; z-index: var(--dp-z-chrome, 20); + -webkit-app-region: drag; +} + +.root button, +.root input, +.root select, +.root a, +.root [role='tab'], +.root [role='option'] { + -webkit-app-region: no-drag; } .left { diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx index 351fa1c..a11900d 100644 --- a/src/components/layout/TopBar.tsx +++ b/src/components/layout/TopBar.tsx @@ -230,8 +230,22 @@ export function TopBar() { const [verificationOpen, setVerificationOpen] = useState(false) const [testKind, setTestKind] = useState(null) + /* + * The drag region (`-webkit-app-region: drag` in TopBar.module.css) + * substitutes for the title bar `hiddenInset` removes, but it's just a + * mouse listener to Chromium — macOS never learns to zoom the window on + * a second click there the way it would for a real NSWindow title bar. + * Skip it entirely over interactive descendants so double-clicking a + * button/tab doesn't ALSO toggle the window size. + */ + const onRootDoubleClick = (e: React.MouseEvent): void => { + const target = e.target as HTMLElement + if (target.closest('button, input, select, a, [role="tab"], [role="option"]')) return + void window.daisy.window.titlebarDoubleClick() + } + return ( -
+
DAISYPATCHER