The scene view gets crowded once a scene holds many behaviors, colliders and tilemaps. A new eye button in the topbar opens a list of all placeable asset kinds, and each row has toggles for what the scene view draws for that kind. - The popup lists all 9 placeable kinds from Behavior to Tilemap, with the columns Symbol, Name and Shape. It stays open while switching. - Sprite, Text and Tilemap draw no symbol, so their symbol toggle is greyed out. Their name toggle shows a new centred name tag, which is off by default. - Shape exists for Collider and Tilemap. A hidden collider outline is neither drawn nor clickable. A hidden tilemap shape removes the collision outlines and the paint tint, the tiles stay visible. - A hidden symbol takes no slot in the ring and draws no dashed link to its parent. A name that stays on moves to the centre of the object. - The setting lives in the project as `sceneVisibility`, not in localStorage. Older projects load with the defaults, which match the view as it was. - Switching has no undo step, like the game scale. It only changes the view. A change made during Play survives Stop. - The rules sit in `src/core/model/sceneVisibility.ts`, the popup in `src/editor/panels/SceneVisibilityPopup.ts`. Four new SVG icons. Not covered by a check: symbol off with name on for a kind with a symbol, Text and Tilemap with names, and the paint tint with shape off. Tests: - ./check.sh: 173 files, 2515 tests, CHECK EXIT 0 - e2e 46: ALL PASSED, 17 checks. 46g clicks the popup, lays a small tilemap over the crate and removes it again. - e2e 8 alone on a fresh seed: ALL PASSED, 23 checks. A red check in the partial run 2,8,46 came from the module selection. - 8 new unit tests in tests/editor/scene-visibility.test.ts. - 4 counter-probes, each fails its test: load ignores the saved value, Play skips the saved model, scene view ignores collider shape, scene view ignores tilemap shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
4.1 KiB
TypeScript
109 lines
4.1 KiB
TypeScript
/**
|
||
* What the scene view shows of each placeable type – symbol, name, a collider's outline. It lives
|
||
* in the project like the game scale: saved, but never an undo step.
|
||
*/
|
||
import { describe, expect, it } from 'vitest';
|
||
import { createEmptyProject } from '../../src/core/model/Project';
|
||
import {
|
||
defaultSceneVisibility,
|
||
sceneVisibilityOf,
|
||
shows,
|
||
VISIBILITY_TYPES,
|
||
visibilityParts,
|
||
} from '../../src/core/model/sceneVisibility';
|
||
import { deserializeProject, serializeProject } from '../../src/core/store/serialize';
|
||
import { EditorState } from '../../src/editor/state';
|
||
|
||
function loaded() {
|
||
const state = new EditorState();
|
||
state.loadProject(createEmptyProject('p', 'P'), 1);
|
||
return state;
|
||
}
|
||
|
||
describe('scene visibility', () => {
|
||
it('lists every placeable type, Behavior to Tilemap', () => {
|
||
expect(VISIBILITY_TYPES).toEqual([
|
||
'behavior',
|
||
'camera',
|
||
'collider',
|
||
'container',
|
||
'particles',
|
||
'rigidbody',
|
||
'sprite',
|
||
'text',
|
||
'tilemap',
|
||
]);
|
||
});
|
||
|
||
it('gives a symbol switch only to types with a symbol, and an outline switch to what collides', () => {
|
||
expect(visibilityParts('behavior')).toEqual(['symbol', 'label']);
|
||
expect(visibilityParts('collider')).toEqual(['symbol', 'label', 'shape']);
|
||
expect(visibilityParts('sprite')).toEqual(['label']);
|
||
expect(visibilityParts('tilemap')).toEqual(['label', 'shape']);
|
||
});
|
||
|
||
it('shows what it always showed in a new project and in one written before there was any', () => {
|
||
for (const visibility of [
|
||
createEmptyProject('p', 'P').sceneVisibility,
|
||
deserializeProject({ id: 'p', name: 'P', assets: [] }).sceneVisibility,
|
||
]) {
|
||
expect(shows(visibility, 'behavior', 'symbol')).toBe(true);
|
||
expect(shows(visibility, 'behavior', 'label')).toBe(true);
|
||
expect(shows(visibility, 'collider', 'shape')).toBe(true);
|
||
expect(shows(visibility, 'tilemap', 'shape')).toBe(true);
|
||
expect(shows(visibility, 'sprite', 'label')).toBe(false);
|
||
expect(shows(visibility, 'sprite', 'shape')).toBe(true);
|
||
}
|
||
});
|
||
|
||
it('survives a save and a load', () => {
|
||
const project = createEmptyProject('p', 'P');
|
||
project.sceneVisibility.collider.shape = false;
|
||
project.sceneVisibility.tilemap.shape = false;
|
||
project.sceneVisibility.rigidbody.label = false;
|
||
project.sceneVisibility.sprite.label = true;
|
||
const back = deserializeProject(JSON.parse(JSON.stringify(serializeProject(project)))).sceneVisibility;
|
||
expect(back.collider.shape).toBe(false);
|
||
expect(back.tilemap.shape).toBe(false);
|
||
expect(back.rigidbody.label).toBe(false);
|
||
expect(back.sprite.label).toBe(true);
|
||
expect(back.behavior).toEqual(defaultSceneVisibility().behavior);
|
||
});
|
||
|
||
it('reads untrusted JSON part by part, the rest as the defaults', () => {
|
||
const read = sceneVisibilityOf({
|
||
sceneVisibility: { camera: { symbol: false, label: 'no' }, texture: { label: true }, text: 3 },
|
||
});
|
||
expect(read.camera).toEqual({ symbol: false, label: true, shape: true });
|
||
expect(read.text).toEqual(defaultSceneVisibility().text);
|
||
expect('texture' in read).toBe(false);
|
||
expect(sceneVisibilityOf(null)).toEqual(defaultSceneVisibility());
|
||
});
|
||
|
||
it('never hides what a type does not have to hide', () => {
|
||
const visibility = defaultSceneVisibility();
|
||
visibility.sprite.symbol = false;
|
||
visibility.sprite.shape = false;
|
||
expect(shows(visibility, 'sprite', 'shape')).toBe(true);
|
||
visibility.behavior.shape = false;
|
||
expect(shows(visibility, 'behavior', 'shape')).toBe(true);
|
||
});
|
||
|
||
it('saves a switch without an undo step', () => {
|
||
const state = loaded();
|
||
state.setSceneVisibility('collider', 'shape', false);
|
||
expect(state.project.sceneVisibility.collider.shape).toBe(false);
|
||
expect(state.saveStatus).toBe('dirty');
|
||
expect(state.commands.canUndo).toBe(false);
|
||
});
|
||
|
||
it('keeps a switch flipped while playing after Stop', () => {
|
||
const state = loaded();
|
||
state.enterPlay();
|
||
state.setSceneVisibility('behavior', 'label', false);
|
||
expect(state.project.sceneVisibility.behavior.label).toBe(false);
|
||
state.exitPlay();
|
||
expect(state.project.sceneVisibility.behavior.label).toBe(false);
|
||
});
|
||
});
|