mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
fix(desktop): align macOS traffic lights with the vertical tabs header (#57258)
This commit is contained in:
committed by
GitHub
Unverified
parent
f3f1fc7511
commit
15be9f1939
@@ -86,6 +86,9 @@ type PlatformBase = {
|
||||
/** Whether the native desktop window is fullscreen */
|
||||
windowFullscreen?: Accessor<boolean>
|
||||
|
||||
/** Move the native macOS traffic lights' top-left corner, in window points (desktop only) */
|
||||
setTrafficLightsPosition?(position: { x: number; y: number }): Promise<void>
|
||||
|
||||
/** Get whether native pinch/Ctrl-scroll zoom gestures are enabled (desktop only) */
|
||||
getPinchZoomEnabled?(): Promise<boolean> | boolean
|
||||
|
||||
|
||||
@@ -9,11 +9,20 @@ import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { usePlatform } from "@/runtime/platform/platform"
|
||||
import { useSettings } from "@/settings/model"
|
||||
|
||||
// The desktop window sets `trafficLightPosition: { x: 14, y: 14 }`. macOS 26 and later draw 14px buttons on a 23px
|
||||
// pitch, so the zoom button ends 74px from the window edge and the buttons center 21px from the top. macOS 15 and earlier
|
||||
// end at 68px and center at 22px.
|
||||
// The desktop window sets `trafficLightPosition: { x: 14, y: 14 }`, and the titlebar moves the buttons down to share the
|
||||
// header's center line in vertical tabs. macOS 26 and later draw 14px buttons on a 23px pitch, so the zoom button ends
|
||||
// 74px from the window edge. macOS 15 and earlier end at 68px.
|
||||
export const macTrafficLightsBaseWidth = 74
|
||||
|
||||
// Vertical tabs center the 14pt traffic lights on the session header's center line, 32px from the top, and inset them as
|
||||
// far from the left edge as from the top. The center scales with zoom. Returns window points; `width` is where the zoom
|
||||
// button ends.
|
||||
export function macVerticalTrafficLights(zoom: number) {
|
||||
const inset = 32 * zoom - 7
|
||||
|
||||
return { x: inset, y: inset, width: inset + macTrafficLightsBaseWidth - 14 }
|
||||
}
|
||||
|
||||
// Matches the horizontal titlebar, whose first control starts 16px past the traffic lights.
|
||||
const macTrafficLightsGap = 16
|
||||
|
||||
@@ -73,7 +82,10 @@ function useCollapsedSlot() {
|
||||
// Lays the toggle out as if the traffic lights were the header's first control.
|
||||
width: () =>
|
||||
mac()
|
||||
? macTrafficLightsBaseWidth / (platform.webviewZoom?.() ?? 1) + macTrafficLightsGap - contentStart + slotWidth
|
||||
? macVerticalTrafficLights(platform.webviewZoom?.() ?? 1).width / (platform.webviewZoom?.() ?? 1) +
|
||||
macTrafficLightsGap -
|
||||
contentStart +
|
||||
slotWidth
|
||||
: slotWidth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useSettings } from "@/settings/model"
|
||||
import { WindowsAppMenu } from "./windows-menu"
|
||||
import { applyPath, backPath, forwardPath, type HistoryLocation } from "./history"
|
||||
import { TitlebarTabStrip } from "@/shell/titlebar/tab-strip"
|
||||
import { macTrafficLightsBaseWidth, SidebarToggle } from "@/shell/sidebar-toggle"
|
||||
import { macTrafficLightsBaseWidth, macVerticalTrafficLights, SidebarToggle } from "@/shell/sidebar-toggle"
|
||||
import { SidebarInbox } from "@/shell/titlebar/inbox"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
@@ -46,6 +46,9 @@ const windowsControlsBaseWidth = 138 // 3 native Windows caption buttons at 46px
|
||||
|
||||
const macTrafficLightsTopClearance = 28
|
||||
|
||||
// Matches the desktop window's `trafficLightPosition`.
|
||||
const macTrafficLightsDefaultPosition = { x: 14, y: 14 }
|
||||
|
||||
// iOS blurs page content just below the status bar in Home Screen web apps, so with a top safe area the phone
|
||||
// titlebar row stays 16px clear of it, as ChatGPT's phone header does. Without one, 8px matches the content gap below.
|
||||
const mobileTopClearance = "max(8px, min(16px, env(safe-area-inset-top, 0px) * 1000))"
|
||||
@@ -81,6 +84,15 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
|
||||
|
||||
const windowsControlsWidth = () => `${windowsControlsBaseWidth / Math.max(titlebarZoom(), 1)}px`
|
||||
|
||||
// The vertical sidebar header's center line scales with zoom, so the 14pt traffic lights follow it. The horizontal
|
||||
// titlebar stays at the window's default position.
|
||||
createEffect(() => {
|
||||
if (!mac()) return
|
||||
void platform.setTrafficLightsPosition?.(
|
||||
macVerticalTabs() ? macVerticalTrafficLights(zoom()) : macTrafficLightsDefaultPosition,
|
||||
)
|
||||
})
|
||||
|
||||
const [history, setHistory] = createStore<{
|
||||
stack: HistoryLocation[]
|
||||
index: number
|
||||
@@ -790,7 +802,9 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
|
||||
{/* The sidebar's 10px start padding already covers part of the traffic lights. */}
|
||||
<div
|
||||
class="shrink-0"
|
||||
style={{ width: `calc(${macTrafficLightsBaseWidth / zoom()}px - 10px)` }}
|
||||
style={{
|
||||
width: `calc(${macVerticalTrafficLights(zoom()).width / zoom()}px - 10px)`,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Show>
|
||||
|
||||
@@ -2,7 +2,14 @@ import { app, BrowserWindow } from "electron"
|
||||
import { Effect } from "effect"
|
||||
import { WindowRpcs } from "../../shared/ipc-rpc"
|
||||
import { IpcPortHandoff } from "../ipc-transport"
|
||||
import { getPinchZoomEnabled, setPinchZoomEnabled, setTitlebar, setWindowThemeReady, updateTitlebar } from "../windows"
|
||||
import {
|
||||
getPinchZoomEnabled,
|
||||
setPinchZoomEnabled,
|
||||
setTitlebar,
|
||||
setTrafficLightsPosition,
|
||||
setWindowThemeReady,
|
||||
updateTitlebar,
|
||||
} from "../windows"
|
||||
import { focusWindow, showWindow } from "../windows/focus"
|
||||
import { sender } from "./context"
|
||||
|
||||
@@ -43,6 +50,12 @@ export const windowHandlers = WindowRpcs.toLayer(
|
||||
|
||||
if (win) setTitlebar(win, theme)
|
||||
}),
|
||||
WindowSetTrafficLightsPosition: (position, context) =>
|
||||
Effect.sync(() => {
|
||||
const win = BrowserWindow.fromWebContents(sender(handoff, context))
|
||||
|
||||
if (win) setTrafficLightsPosition(win, position)
|
||||
}),
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -83,6 +83,11 @@ export function setTitlebar(win: BrowserWindow, theme: Partial<TitlebarTheme> =
|
||||
updateTitlebar(win)
|
||||
}
|
||||
|
||||
export function setTrafficLightsPosition(win: BrowserWindow, position: { x: number; y: number }) {
|
||||
if (process.platform !== "darwin") return
|
||||
win.setWindowButtonPosition({ x: Math.round(position.x), y: Math.round(position.y) })
|
||||
}
|
||||
|
||||
export function updateTitlebar(win: BrowserWindow) {
|
||||
if (process.platform !== "win32") return
|
||||
win.setTitleBarOverlay(overlay(titlebarThemes.get(win), win.webContents.getZoomFactor()))
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
setDockIcon,
|
||||
setPinchZoomEnabled,
|
||||
setTitlebar,
|
||||
setTrafficLightsPosition,
|
||||
setZoomFactor,
|
||||
updateTitlebar,
|
||||
windowAppearance,
|
||||
@@ -60,6 +61,7 @@ export {
|
||||
setDockIcon,
|
||||
setPinchZoomEnabled,
|
||||
setTitlebar,
|
||||
setTrafficLightsPosition,
|
||||
setZoomFactor,
|
||||
updateTitlebar,
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ export type ElectronAPI = {
|
||||
onPinchZoomEnabledChanged(cb: (enabled: boolean) => void): () => void
|
||||
onZoomFactorChanged(cb: (factor: number) => void): () => void
|
||||
setTitlebar(theme: TitlebarTheme): Promise<void>
|
||||
setTrafficLightsPosition(position: { x: number; y: number }): Promise<void>
|
||||
runDesktopMenuAction(action: DesktopMenuAction): Promise<void>
|
||||
setBackgroundColor(color: string): Promise<void>
|
||||
exportDebugLogs(): Promise<string>
|
||||
|
||||
@@ -85,6 +85,7 @@ export const api: ElectronAPI = {
|
||||
onPinchZoomEnabledChanged: (cb) => listen("WindowPinchZoomChanged", (event) => cb(event.enabled)),
|
||||
onZoomFactorChanged: (cb) => listen("WindowZoomChanged", (event) => cb(event.factor)),
|
||||
setTitlebar: (theme) => invoke("WindowSetTitlebar", { theme }),
|
||||
setTrafficLightsPosition: (position) => invoke("WindowSetTrafficLightsPosition", position),
|
||||
runDesktopMenuAction: (action) => invoke("MenuRunAction", { action }),
|
||||
setBackgroundColor: (color) => invoke("AppSetBackgroundColor", { color }),
|
||||
exportDebugLogs: () => invoke("AppExportDebugLogs"),
|
||||
|
||||
@@ -36,6 +36,7 @@ export function createDesktopPlatform(api: ElectronAPI, windowState: DesktopWind
|
||||
},
|
||||
webviewZoom,
|
||||
windowFullscreen,
|
||||
setTrafficLightsPosition: (position) => api.setTrafficLightsPosition(position),
|
||||
getPinchZoomEnabled: () => api.getPinchZoomEnabled(),
|
||||
setPinchZoomEnabled,
|
||||
onDragCancel: (callback) => {
|
||||
|
||||
@@ -34,6 +34,10 @@ export const WindowSetTitlebar = Rpc.make("WindowSetTitlebar", {
|
||||
},
|
||||
})
|
||||
|
||||
export const WindowSetTrafficLightsPosition = Rpc.make("WindowSetTrafficLightsPosition", {
|
||||
payload: { x: Schema.Number, y: Schema.Number },
|
||||
})
|
||||
|
||||
export const WindowRpcs = RpcGroup.make(
|
||||
WindowThemeReady,
|
||||
WindowGetFocused,
|
||||
@@ -45,4 +49,5 @@ export const WindowRpcs = RpcGroup.make(
|
||||
WindowGetPinchZoomEnabled,
|
||||
WindowSetPinchZoomEnabled,
|
||||
WindowSetTitlebar,
|
||||
WindowSetTrafficLightsPosition,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user