An inventory grid could not scroll, because nothing could cut a child off at an edge. A game also had no way to hear the mouse wheel. Both are needed for a scrolling list, a dialog text or a window with more content than room. - A new object type Clip area has width and height and turns, scales and mirrors with its parent. The size gizmo drags width and height. - Everything below a clip area, on every level, is drawn only inside its rectangle, in the Scene view as in the game. Nested areas cut to their intersection. The rule works on a convex polygon, so a turned area cuts right as well. - On click, Is hovered and On mouse wheel do not hit a cut-off part. The Scene view does not pick it either. - In the Scene view the area is a dashed frame that is grabbed only at its edge, so a click inside lands on the content. The eye under Shape hides the frame. In the game the area is invisible. - While an object below an area or one of its parents is selected, the Scene view shows the cut-off part at 30 % opacity and it can be grabbed there. The rule lives in core/properties/clip.ts. - The Properties node knows the type Clip area. - The new event node On mouse wheel fires by the rule of On click. Its delta counts in notches, positive is down, and a frame's turns arrive as one value. It also gives x and y. Limits: - Only sprites and texts show the faded rest. Tilemaps and particles are cut and can be grabbed when open, but are not shown faded. - Masks by texture shape are out, as noted in the issue. Tests: - ./check.sh: 2773 tests, CHECK EXIT 0. - Full e2e run (local): ALL PASSED, 1549 checks, measured. - tests/core/clip-areas.test.ts: 12 tests for the cut rule, scene and game, save and load, hits, the wheel with and without owner, the notch conversion and the open rule. - e2e 58, 5 checks with real mouse and wheel: the scene cuts (74 of 74 red inside, 0 outside), a click on the cut part picks nothing, the selected rest is faded and grabbable, the game cuts, one notch logs 1. - e2e 04 and 46 count the object types and the eye rows. They now include Clip area. - Counter-checks: 6 in unit tests and 3 in the browser, all fail as expected. The originals were restored and checked with cmp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
110 lines
4.2 KiB
TypeScript
110 lines
4.2 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',
|
||
'clip',
|
||
'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);
|
||
});
|
||
});
|