A game is easier to judge and to play without the editor around it. The Game tab and the game link now both have a button that puts the game view itself into fullscreen, not just the browser window. - The Game tab header has a fullscreen button on the right. It hides toolbar, panels, tab headers and console and keeps the zoom from the picker. A running game keeps running. - A corner button at the top right, or Esc, leaves fullscreen and puts the view back into the tab where it was. - The game link shows the same corner button. Over a running game it hides after 2 s without mouse movement and returns on the next move. - A click on the corner button never reaches the game. - Stop ends fullscreen. Stop switches back to the Scene tab, and any switch away from the Game tab ends fullscreen, so no empty screen is left behind. - src/viewport/fullscreen.ts wraps the Fullscreen API and holds the rule for when the button shows. FullscreenCorner.ts is the button used by both pages. Two SVG icons are new. - Editor.showGameFullscreen moves the GameViewport into the root while fullscreen is on. Tests: - ./check.sh: 195 files, 2815 tests, exit 0 (3 new unit tests). - e2e 60: 2 new checks for the corner button on the game link. - e2e 61 (new): 4 checks for fullscreen in the editor. - Full e2e run (local): ALL PASSED, 1580 checks. - Counter-checks fail as expected: rule without "no game running", no stopPropagation (60h), no end on tab switch (61c), no reparenting of the view (61a). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
1.7 KiB
TypeScript
45 lines
1.7 KiB
TypeScript
/**
|
||
* Fullscreen for the game view (#267): the browser's Fullscreen API on the whole page, which is the
|
||
* one canvas. What the canvas then shows – only the game – is up to the page that asked.
|
||
*/
|
||
|
||
/** How long the corner button stays after the mouse last moved over a running game. */
|
||
export const CORNER_BUTTON_IDLE_SECONDS = 2;
|
||
|
||
/**
|
||
* Whether the corner button shows: always while no game runs, and over a running game only while
|
||
* the mouse moves or rests on the button – it would otherwise sit over the picture for good.
|
||
*/
|
||
export function showsCornerButton(state: {
|
||
running: boolean;
|
||
idleSeconds: number;
|
||
overButton: boolean;
|
||
}): boolean {
|
||
return !state.running || state.overButton || state.idleSeconds < CORNER_BUTTON_IDLE_SECONDS;
|
||
}
|
||
|
||
export function isFullscreen(): boolean {
|
||
return document.fullscreenElement !== null;
|
||
}
|
||
|
||
/** Enters or leaves fullscreen; a browser that refuses leaves everything as it was. */
|
||
export async function toggleFullscreen(): Promise<void> {
|
||
try {
|
||
if (isFullscreen()) await document.exitFullscreen();
|
||
else await document.documentElement.requestFullscreen();
|
||
} catch (error) {
|
||
console.warn('[fullscreen]', error);
|
||
}
|
||
}
|
||
|
||
export function leaveFullscreen(): void {
|
||
if (isFullscreen()) void document.exitFullscreen().catch((error) => console.warn('[fullscreen]', error));
|
||
}
|
||
|
||
/** Calls `handler` whenever fullscreen starts or ends, by the button or by Esc; returns the call that stops it. */
|
||
export function onFullscreenChange(handler: (on: boolean) => void): () => void {
|
||
const listener = (): void => handler(isFullscreen());
|
||
document.addEventListener('fullscreenchange', listener);
|
||
return () => document.removeEventListener('fullscreenchange', listener);
|
||
}
|