fix(window): show the window buttons in full screen on macOS (#2015)

Native full screen moves a window's buttons into the bar revealed with the menu bar, and a project tab shown in its group's full-screen Space has none, so the title bar's slot for them stayed empty. The title bar now draws them there while the window fills the screen: close closes that project's tab, the green button leaves full screen, and minimize is disabled.
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-07 23:08:25 +08:00
1 джерело 257fb0f50f
коміт 429b8f0efe
16 files changed
+205 -7

No files matched your search

+87
Переглянути файл
@@ -0,0 +1,87 @@
/**
* macOS window buttons in the title bar's slot while the window fills the screen. Native full
* screen moves the system's buttons into the bar revealed with the menu bar, and a project tab
* shown in its group's full-screen Space has none, so the slot kept for them would be empty.
* Close closes this project's tab, the green button leaves full screen, minimize is disabled.
* Like the system's, they take no keyboard focus.
*/
import { useEffect, useState } from "react";
import clsx from "clsx";
import { t } from "@/platform/i18n";
import { commands, onIdeEvent } from "@/platform/ipc";
import { Icon } from "@/ui";
import styles from "./TitleBar.module.css";
type AppWindow = import("@tauri-apps/api/window").Window;
function useFullscreenWindow(): { fullscreen: boolean; active: boolean } {
const [fullscreen, setFullscreen] = useState(false);
const [active, setActive] = useState(() => document.hasFocus());
useEffect(() => {
let disposed = false;
const offs: (() => void)[] = [];
const sync = () => void commands.windowIsFullscreen().then((on) => !disposed && setFullscreen(on));
sync();
offs.push(onIdeEvent("windowFullscreenChanged", sync));
void import("@tauri-apps/api/window").then(async ({ getCurrentWindow }) => {
const w: AppWindow = getCurrentWindow();
const listeners = await Promise.all([
w.onResized(sync),
w.onFocusChanged(({ payload }) => {
setActive(payload);
sync();
}),
]);
if (disposed) listeners.forEach((off) => off());
else offs.push(...listeners);
});
return () => {
disposed = true;
offs.forEach((off) => off());
};
}, []);
return { fullscreen, active };
}
function closeWindow() {
void import("@tauri-apps/api/window").then(({ getCurrentWindow }) => getCurrentWindow().close());
}
export function MacWindowButtons() {
const { fullscreen, active } = useFullscreenWindow();
if (!fullscreen) return null;
return (
<div
className={clsx(styles.macButtons, !active && styles.macButtonsInactive)}
onMouseDown={(e) => e.preventDefault()}
>
<button
type="button"
tabIndex={-1}
className={clsx(styles.macButton, styles.macClose)}
aria-label={t("window.caption.close")}
onClick={closeWindow}
>
<Icon name="mac-window-close" size={8} />
</button>
<button
type="button"
tabIndex={-1}
className={clsx(styles.macButton, styles.macMinimize)}
aria-label={t("window.caption.minimize")}
disabled
>
<Icon name="mac-window-minimize" size={8} />
</button>
<button
type="button"
tabIndex={-1}
className={clsx(styles.macButton, styles.macZoom)}
aria-label={t("window.caption.exitFullScreen")}
onClick={() => void commands.windowToggleFullscreen()}
>
<Icon name="mac-window-exit-full-screen" size={8} />
</button>
</div>
);
}
+49
Переглянути файл
@@ -195,3 +195,52 @@
background: var(--caption-close-pressed);
color: var(--fg-caption-close-hover);
}
/* macOS window buttons drawn in the traffic-light slot while the window fills the screen. */
.macButtons {
position: fixed;
top: 0;
left: 0;
height: var(--h-titlebar);
display: flex;
align-items: center;
gap: 8px;
padding-left: 12px;
zoom: calc(1 / var(--titlebar-zoom, 1));
z-index: 1;
}
.macButton {
width: 12px;
height: 12px;
padding: 0;
border: none;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--mac-window-glyph);
box-shadow: inset 0 0 0 0.5px var(--mac-window-edge);
}
.macButton > * {
opacity: 0;
}
.macButtons:hover .macButton:not(:disabled) > * {
opacity: 1;
}
.macClose {
background: var(--mac-window-close);
}
.macMinimize {
background: var(--mac-window-minimize);
}
.macZoom {
background: var(--mac-window-zoom);
}
.macButton:active:not(:disabled) {
filter: brightness(0.8);
}
/* Disabled in full screen, and every button of an inactive window, are grey as AppKit draws them. */
.macButton:disabled,
.macButtonsInactive:not(:hover) .macButton {
background: var(--mac-window-inactive);
}
+3
Переглянути файл
@@ -21,6 +21,7 @@ import {
widgetTitle,
} from "./mainToolbar";
import { WindowControls } from "./WindowControls";
import { MacWindowButtons } from "./MacWindowButtons";
import { usePlatform } from "@/platform/platformInfo";
import { projectInitials } from "./projectColor";
import { keymapItems, themeItems, themePreview, type ThemePreview, VIEW_MODE_ITEMS } from "./quickSwitch";
@@ -301,6 +302,7 @@ export function TitleBar({
if (bare)
return (
<header ref={setBar} className={className} {...windowDragRegion} data-titlebar="bare">
{overlay && <MacWindowButtons />}
{frameless && (
<>
<MainMenu />
@@ -323,6 +325,7 @@ export function TitleBar({
showContextMenu(e, menu);
}}
>
{overlay && <MacWindowButtons />}
{frameless && !minimal && <MainMenu onExpandedChange={setMenuExpanded} />}
{title && (
<span className={styles.windowTitle} {...windowDragRegion}>