Give the scene tab a header with Fit and the eye (#245)
The scene view had no header bar of its own, and its eye sat in the top bar. Switching between Scene and Game also showed two different pictures, since nothing tied the free view to the camera. - Theme.headerPadding (4) replaces Theme.padding in the top bar and in TabHeader, for the padding and for minHeight. Both bars shrink from 40 to 32 px. Nothing else uses the new value. - The new ScenePane wraps the scene view in a header bar with Fit and the visibility eye. The eye is gone from the top bar, which keeps the menu, undo and redo. - Fit with an active camera shows the world exactly as the Game tab does at its chosen step: the same zoom, and the camera centre on the same screen pixel. The rule is the pure freeViewOfCamera in viewport/view.ts, which also mirrors the letterbox snapping. - Fit without a camera frames all content on a whole step, as before. - New hook __gm.sceneHeaderAt(name). e2e 46g opens the eye there. - The doc comment above Theme.treeIndent went away in the working tree along with this. It is committed as found. Not checked: the two views only match while they have the same size. With the tilemap panel open the scene view is narrower, and Auto may pick a smaller step there. Tests: - ./check.sh: 178 files, 2575 tests, CHECK EXIT 0 - 2 new unit tests in view.test.ts: three cameras times four steps land on the same screen point as in the Game tab, Auto stays whole - e2e 3h (2 checks): a real click on Fit at Auto and at Fit, crate, camera frame and view box match in both tabs - GM_STEPS=1-3,46: ALL PASSED, 94 checks - Counter-probes: without the centre offset the unit test fails, with Fit ignoring the camera both 3h checks fail Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a90ef4c22f
commit
30de4d35a8
15 files changed
+163
-31
No files matched your search
@@ -361,6 +361,40 @@ export async function play(t) {
|
||||
(await undoSteps()) === stepsBefore,
|
||||
JSON.stringify({ steps: await undoSteps(), stepsBefore }),
|
||||
);
|
||||
// 3h. Fit in the scene tab's header frames what the game view shows, at the game view's scale:
|
||||
// the crate and the camera frame land on the same screen pixels in both tabs, so switching
|
||||
// between them moves nothing – at auto's whole step and at fit's fractional one alike.
|
||||
const sameAsGame = async (label) => {
|
||||
const game = await pickScale(label);
|
||||
const inGame = await json(`__gm.gameRect('o1')`);
|
||||
await showTab('scene');
|
||||
const fitAt = await json(`__gm.sceneHeaderAt('fit')`);
|
||||
await click(fitAt.x, fitAt.y);
|
||||
const inScene = await json(`__gm.objectRect('o1')`);
|
||||
const cameraInScene = await json(`__gm.objectRect('o3')`);
|
||||
const view = await json('__gm.viewportRect()');
|
||||
await showTab('game');
|
||||
const near = (a, b) => ['x', 'y', 'w', 'h'].every((k) => Math.abs(a[k] - b[k]) < 0.01);
|
||||
// `objectRect` gives the centre, the game frame its top-left corner.
|
||||
const { x, y, w, h } = game.frame;
|
||||
const frameCentred = { x: x + w / 2, y: y + h / 2, w, h };
|
||||
return {
|
||||
ok: near(inGame, inScene) && near(frameCentred, cameraInScene) && near(game.view, view),
|
||||
detail: JSON.stringify({
|
||||
label,
|
||||
inGame,
|
||||
inScene,
|
||||
frame: game.frame,
|
||||
cameraInScene,
|
||||
view: game.view,
|
||||
sceneView: view,
|
||||
}),
|
||||
};
|
||||
};
|
||||
for (const label of ['Auto', 'Fit']) {
|
||||
const { ok, detail } = await sameAsGame(label);
|
||||
check(`Fit in the scene tab shows the world exactly as the game view does at ${label}`, ok, detail);
|
||||
}
|
||||
g = await pickScale('100%');
|
||||
check('and 100% can be picked back', g.mode === 1 && g.scale === 1, JSON.stringify(g));
|
||||
await showTab('scene');
|
||||
|
||||
@@ -168,7 +168,7 @@ export async function topbar(t) {
|
||||
JSON.stringify({ opened, scaled, restored }),
|
||||
);
|
||||
|
||||
// 46g. The eye beside redo: every placeable type with a switch for its symbol and its name, and a
|
||||
// 46g. The eye in the scene tab's header: every placeable type with a switch for its symbol and its name, and a
|
||||
// outline of a collider and of a tilemap's solid cells besides. Sprite, Text and Tilemap have no symbol, so that switch is
|
||||
// greyed out. Real clicks flip the scene view, and the switches are saved but no undo step.
|
||||
await json('(__gm.showTab("scene"), true)');
|
||||
@@ -204,7 +204,9 @@ export async function topbar(t) {
|
||||
scene.objects.push(Object.assign(structuredClone(o), { id: 'vis-col-obj', assetId: 'vis-col', name: 'Vis collider' }));
|
||||
__gm.state.select(null)`);
|
||||
const undoBefore = await json('__gm.state.commands.canUndo');
|
||||
await clickToolbar('visibility');
|
||||
await t.showTab('scene');
|
||||
const eye = await json(`__gm.sceneHeaderAt('visibility')`);
|
||||
await t.click(eye.x, eye.y);
|
||||
const popup = await json('__gm.visibilityPopup()');
|
||||
const entry = (list, type, part) => list?.find((e) => e.type === type && e.part === part);
|
||||
const types = [...new Set((popup ?? []).map((e) => e.type))];
|
||||
|
||||
Reference in new issue
Block a user