Keep workspace focus mode scoped and easy to exit (#2151)
* fix(workspace): keep focus mode scoped and easy to exit Route focus mode through the active workspace so persisted state cannot hide chrome on settings or other screens. Add a visible exit control and keep desktop window chrome aligned and visually quiet. * fix(app): clarify muted chrome semantics
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
applyMacWindowControlsUpdate,
|
||||
applyWindowControlsOverlayUpdate,
|
||||
createWindowControlsOverlayState,
|
||||
DEFAULT_WINDOW_HEIGHT,
|
||||
@@ -70,10 +71,12 @@ describe("window-manager", () => {
|
||||
readWindowControlsOverlayUpdate({
|
||||
height: 48,
|
||||
backgroundColor: "#181B1A",
|
||||
trafficLightOffsetY: -5,
|
||||
}),
|
||||
).toEqual({
|
||||
height: 48,
|
||||
backgroundColor: "#181B1A",
|
||||
trafficLightOffsetY: -5,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,6 +85,13 @@ describe("window-manager", () => {
|
||||
expect(readWindowControlsOverlayUpdate({})).toBeNull();
|
||||
expect(readWindowControlsOverlayUpdate({ height: 0 })).toBeNull();
|
||||
expect(readWindowControlsOverlayUpdate({ backgroundColor: 12 })).toBeNull();
|
||||
expect(readWindowControlsOverlayUpdate({ trafficLightOffsetY: -11 })).toBeNull();
|
||||
});
|
||||
|
||||
it("preserves fractional traffic-light offsets", () => {
|
||||
expect(readWindowControlsOverlayUpdate({ trafficLightOffsetY: 1.5 })).toEqual({
|
||||
trafficLightOffsetY: 1.5,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -139,6 +149,24 @@ describe("window-manager", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyMacWindowControlsUpdate", () => {
|
||||
it("uses the focus and normal traffic-light positions", () => {
|
||||
const setWindowButtonPosition = vi.fn();
|
||||
|
||||
applyMacWindowControlsUpdate({
|
||||
win: { setWindowButtonPosition },
|
||||
update: { trafficLightOffsetY: -5 },
|
||||
});
|
||||
applyMacWindowControlsUpdate({
|
||||
win: { setWindowButtonPosition },
|
||||
update: { trafficLightOffsetY: 0.5 },
|
||||
});
|
||||
|
||||
expect(setWindowButtonPosition).toHaveBeenNthCalledWith(1, { x: 16, y: 9 });
|
||||
expect(setWindowButtonPosition).toHaveBeenNthCalledWith(2, { x: 16, y: 14.5 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMainWindowChromeOptions", () => {
|
||||
it("uses frameless hidden title bars with overlay on windows", () => {
|
||||
expect(
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
import type { WindowState, WindowStateStore } from "../settings/window-state.js";
|
||||
|
||||
const WINDOW_STATE_SAVE_DEBOUNCE_MS = 400;
|
||||
const MAC_TRAFFIC_LIGHT_POSITION = { x: 16, y: 14 } as const;
|
||||
const MAX_TRAFFIC_LIGHT_OFFSET_Y = 10;
|
||||
|
||||
export function readBadgeCount(input: unknown): number {
|
||||
if (typeof input !== "number" || !Number.isSafeInteger(input) || input < 0) {
|
||||
@@ -27,6 +29,7 @@ export interface WindowControlsOverlayUpdate {
|
||||
height?: number;
|
||||
backgroundColor?: string;
|
||||
foregroundColor?: string;
|
||||
trafficLightOffsetY?: number;
|
||||
}
|
||||
|
||||
export interface WindowControlsOverlayState {
|
||||
@@ -79,7 +82,7 @@ export function getMainWindowChromeOptions(input: {
|
||||
return {
|
||||
titleBarStyle: "hidden",
|
||||
titleBarOverlay: true,
|
||||
trafficLightPosition: { x: 16, y: 14 },
|
||||
trafficLightPosition: MAC_TRAFFIC_LIGHT_POSITION,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,6 +131,14 @@ function readOverlayColor(input: unknown): string | null {
|
||||
return input;
|
||||
}
|
||||
|
||||
function readTrafficLightOffsetY(input: unknown): number | null {
|
||||
if (typeof input !== "number" || !Number.isFinite(input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Math.abs(input) <= MAX_TRAFFIC_LIGHT_OFFSET_Y ? input : null;
|
||||
}
|
||||
|
||||
export function readWindowControlsOverlayUpdate(
|
||||
input: unknown,
|
||||
): WindowControlsOverlayUpdate | null {
|
||||
@@ -139,8 +150,14 @@ export function readWindowControlsOverlayUpdate(
|
||||
const height = readFiniteOverlayHeight(candidate.height);
|
||||
const backgroundColor = readOverlayColor(candidate.backgroundColor);
|
||||
const foregroundColor = readOverlayColor(candidate.foregroundColor);
|
||||
const trafficLightOffsetY = readTrafficLightOffsetY(candidate.trafficLightOffsetY);
|
||||
|
||||
if (height === null && backgroundColor === null && foregroundColor === null) {
|
||||
if (
|
||||
height === null &&
|
||||
backgroundColor === null &&
|
||||
foregroundColor === null &&
|
||||
trafficLightOffsetY === null
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -148,6 +165,7 @@ export function readWindowControlsOverlayUpdate(
|
||||
...(height !== null ? { height } : {}),
|
||||
...(backgroundColor !== null ? { backgroundColor } : {}),
|
||||
...(foregroundColor !== null ? { foregroundColor } : {}),
|
||||
...(trafficLightOffsetY !== null ? { trafficLightOffsetY } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -176,6 +194,20 @@ export function applyWindowControlsOverlayUpdate(input: {
|
||||
return next;
|
||||
}
|
||||
|
||||
export function applyMacWindowControlsUpdate(input: {
|
||||
win: Pick<BrowserWindow, "setWindowButtonPosition">;
|
||||
update: WindowControlsOverlayUpdate;
|
||||
}): void {
|
||||
if (input.update.trafficLightOffsetY === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
input.win.setWindowButtonPosition({
|
||||
x: MAC_TRAFFIC_LIGHT_POSITION.x,
|
||||
y: MAC_TRAFFIC_LIGHT_POSITION.y + input.update.trafficLightOffsetY,
|
||||
});
|
||||
}
|
||||
|
||||
export function registerWindowManager(): void {
|
||||
const overlayStateByWindow = new WeakMap<BrowserWindow, WindowControlsOverlayState>();
|
||||
|
||||
@@ -230,6 +262,7 @@ export function registerWindowManager(): void {
|
||||
}
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
applyMacWindowControlsUpdate({ win, update: nextUpdate });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user