Files
feuerwerk/src/viewport/FullscreenCorner.ts
T
adminandClaude Opus 5.5 cab1c0177f
game-maker / gate (push) Successful in 4m10s
game-maker / suite (push) Failing after 11m53s
Show only the game view in fullscreen, in the editor and the link (#267)
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>
2026-09-29 14:44:14 +02:00

79 lines
2.7 KiB
TypeScript

import type { Container, FederatedPointerEvent } from 'pixi.js';
import { Button, requestRender, Theme, UI_ICON_SVG } from '../ui';
import {
CORNER_BUTTON_IDLE_SECONDS,
isFullscreen,
onFullscreenChange,
showsCornerButton,
toggleFullscreen,
} from './fullscreen';
/**
* The fullscreen button in the top right corner of a game on screen (#267): on the player page
* always, in the editor while its game view is in fullscreen. Over a running game it steps aside
* once the mouse rests (`showsCornerButton`).
*/
export class FullscreenCorner extends Button {
private active = true;
private running = false;
private lastMove = performance.now();
private hideTimer = 0;
private readonly stopChange: () => void;
constructor(host: Container) {
super('', {
width: Theme.controlHeight,
icon: isFullscreen() ? UI_ICON_SVG.fullscreenExit : UI_ICON_SVG.fullscreen,
onClick: () => void toggleFullscreen(),
});
this.focusable = false;
this.resize(Theme.controlHeight, Theme.controlHeight);
// The button lies on the game: a press on it must neither start the game nor reach it as a click.
const keep = (e: FederatedPointerEvent): void => e.stopPropagation();
this.on('pointerdown', keep);
this.on('pointerup', keep);
this.on('pointerover', () => this.refresh());
this.on('pointerout', () => this.refresh());
host.on('globalpointermove', () => {
this.lastMove = performance.now();
this.refresh();
});
this.stopChange = onFullscreenChange((on) =>
this.setIcon(on ? UI_ICON_SVG.fullscreenExit : UI_ICON_SVG.fullscreen),
);
}
override destroy(options?: Parameters<Button['destroy']>[0]): void {
this.stopChange();
window.clearTimeout(this.hideTimer);
super.destroy(options);
}
/** `active`: whether the host wants the button at all; `running`: whether a game is on screen. */
sync(active: boolean, running: boolean): void {
this.active = active;
this.running = running;
this.refresh();
}
/** Top right, a gap in from both edges of a host `w` wide. */
place(w: number): void {
this.position.set(Math.round(w - this.w - Theme.gap * 2), Theme.gap * 2);
}
private refresh(): void {
window.clearTimeout(this.hideTimer);
const idleSeconds = (performance.now() - this.lastMove) / 1000;
const show =
this.active && showsCornerButton({ running: this.running, idleSeconds, overButton: this.hovered });
if (show !== this.visible) {
this.visible = show;
requestRender();
}
if (show && this.running && !this.hovered) {
const left = CORNER_BUTTON_IDLE_SECONDS - idleSeconds;
this.hideTimer = window.setTimeout(() => this.refresh(), Math.max(0, left * 1000) + 20);
}
}
}