Camera view frame, viewport clipping, overlay layer for editor symbols

- Camera assets get width/height properties; play mode renders only the
  camera frame (letterboxed, masked, culled) and nothing without a camera
- Remove demo arrow-key/camera-follow systems from play mode
- Clip world and gizmos to the viewport (selection frame no longer leaks
  over the left panel)
- Blur focused widget on pointerdown outside it so inspector edits commit
  before selection changes
- Separate objects/overlay layers so camera symbols always render on top
- Add tools/seed-demo.mjs for reproducible E2E runs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-07 11:13:08 +02:00
1 parent 5a46124454
commit aaa8154adf
11 files changed
+307 -142

No files matched your search

+14
View File
@@ -29,6 +29,20 @@ export class FocusManager {
this.focus(null);
}
/**
* A pointer went down on `target`. If that is outside the focused widget, the widget
* loses focus *before* the press is handled, so a text field commits its edit before
* whatever the click triggers (selection change, inspector rebuild, …) can destroy it.
*/
blurIfOutside(target: Container | null): void {
const w = this.focusedWidget;
if (!w) return;
for (let node = target; node; node = node.parent) {
if (node === w) return;
}
this.focus(null);
}
/** Drop focus if the focused widget was removed from the tree or hidden. */
validate(): void {
const w = this.focusedWidget;
+4 -1
View File
@@ -1,4 +1,4 @@
import type { Application, Container } from 'pixi.js';
import type { Application, Container, FederatedPointerEvent } from 'pixi.js';
import { FocusManager } from './FocusManager';
import { KeyboardBridge } from './KeyboardBridge';
@@ -18,6 +18,9 @@ let context: UIContext | null = null;
export function initUI(app: Application, root: Container, overlay: Container): UIContext {
context = { app, focus: new FocusManager(root), keyboard: new KeyboardBridge(), overlay };
// Capture phase on the stage runs before any widget handler: a press outside the focused
// widget blurs it (committing text edits) before the press can change selection.
app.stage.addEventListener('pointerdown', (e) => context?.focus.blurIfOutside((e as FederatedPointerEvent).target as Container), { capture: true });
// Clicking the canvas must not steal DOM focus from the keyboard bridge input.
app.canvas.addEventListener('mousedown', (e) => e.preventDefault());
app.canvas.addEventListener('contextmenu', (e) => e.preventDefault());