Open a scene framed, and thin out two inspector sections (#295)
Three small things asked for in one issue. A scene came up wherever the view happened to stand, and the inspector carried a button and a list that are better reached somewhere else. - A scene comes up framed as if Fit had been clicked: after a project is loaded and when another scene is opened. From then on the view belongs to the user. - An empty scene has nothing to frame and stands at the origin, one pixel per unit. A scene the running game loads is not framed anew. - The Container tab is left out. It keeps the view of each container as before (#171). - The Unpack button and the two lines under it are gone from the inspector of a placed container. Unpack container stays in the right-click menu of the hierarchy and of the scene view. - The corners of a polygon collider are no row in the inspector any more. They are edited on the outline in the scene view. The new schema option noRow keeps a property out of the inspector while it stays a property. Tests: - Gate on melektaus-atx: 219 files, 3401 tests, CHECK EXIT 0. - One new unit test in tests/core/polygon.test.ts: the corners are a property of the collider and no row. - Three new e2e checks: a loaded project is framed like Fit (module 03), an opened scene is (04), and after a real reload a click on Fit moves nothing (09). - Module 09 unpacks through the right-click menu now and checks that the inspector offers no button. - Full suite on melektaus-atx: 1695 checks, all pass. Smokes 24, 19 and 21, all pass. - No counter-probe evaluated, and nothing looked at after the build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ca5b2db449
commit
963f8a152a
10 files changed
+106
-23
No files matched your search
@@ -132,6 +132,7 @@ export function initialOf(def: PropertyDef): PropertyValue {
|
||||
|
||||
/** Whether the inspector shows the row, given the values of the bag it is on. */
|
||||
export function isRowShown(def: PropertyDef, values: PropertyBag): boolean {
|
||||
if ('noRow' in def && def.noRow) return false;
|
||||
if (
|
||||
'shownFor' in def &&
|
||||
def.shownFor &&
|
||||
@@ -197,8 +198,9 @@ export type PropertyDef =
|
||||
* A list declares nothing about its items: each one carries its own kind (see `listItems.ts`),
|
||||
* and the graph types a list as a list, so one `list` port fits them all. An `itemType` makes
|
||||
* every item a struct of that Struct asset (#254), each marked with it (`ITEM_TYPE_KEY`).
|
||||
* `noRow` keeps it out of the inspector: its items are edited somewhere else (#295).
|
||||
*/
|
||||
| { key: string; label: string; kind: 'list'; default: ListValue; itemType?: string }
|
||||
| { key: string; label: string; kind: 'list'; default: ListValue; itemType?: string; noRow?: boolean }
|
||||
/**
|
||||
* `fields` is the declaration; the value carries exactly those keys, which is what lets a struct
|
||||
* keep its shape while single fields are written (`mergeStruct`). With a `structType` the fields
|
||||
@@ -605,11 +607,13 @@ const ASSET_SCHEMAS: Record<AssetType, PropertySchema> = {
|
||||
{ key: 'width', label: 'Width', kind: 'number', default: 100, min: 1, step: 1 },
|
||||
{ key: 'height', label: 'Height', kind: 'number', default: 100, min: 1, step: 1 },
|
||||
{ key: 'radius', label: 'Radius', kind: 'number', default: 50, min: 1, step: 1 },
|
||||
// A polygon's corners in the collider's own units, edited on the outline in the scene (#151).
|
||||
// A polygon's corners in the collider's own units, edited on the outline in the scene (#151) –
|
||||
// and only there: the inspector does not list them (#295).
|
||||
{
|
||||
key: 'points',
|
||||
label: 'Points',
|
||||
kind: 'list',
|
||||
noRow: true,
|
||||
default: [
|
||||
{ x: 0, y: -50 },
|
||||
{ x: 50, y: 50 },
|
||||
|
||||
@@ -37,6 +37,10 @@ export class ContainerCanvas extends SceneViewport {
|
||||
return asset?.type === 'container' ? asset : undefined;
|
||||
}
|
||||
|
||||
protected override framesOnOpen(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** A container has no background of its own – painting one would invent a scene it is not in. */
|
||||
protected override paintsBackground(): boolean {
|
||||
return false;
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
RevertPositionToTemplateCommand,
|
||||
} from '../../core/commands/applyCommands';
|
||||
import type { ValuePath } from '../../core/properties/valuePath';
|
||||
import { UnpackContainerCommand } from '../../core/commands/containerCommands';
|
||||
import { ResizeTilemapEdgeCommand } from '../../core/commands/tilemapCommands';
|
||||
import { TILEMAP_COLUMNS_KEY, TILEMAP_EDGES, TILEMAP_ROWS_KEY } from '../../core/model/tilemap';
|
||||
import { findUsages } from '../../core/model/usages';
|
||||
@@ -323,7 +322,7 @@ function assetLink(host: InspectorHost, key: string, text: string, asset: Asset)
|
||||
|
||||
/**
|
||||
* What a placed container holds. While it follows its template the content cannot be changed here –
|
||||
* unpacking writes it into the scene as ordinary objects, which cuts the link for good.
|
||||
* unpacking (its right-click menu) writes it into the scene as ordinary objects.
|
||||
*/
|
||||
function contentSection(
|
||||
host: InspectorHost,
|
||||
@@ -344,15 +343,6 @@ function contentSection(
|
||||
);
|
||||
if (templateCount === 0) return;
|
||||
addNote(host, 'Its content is listed below it in the hierarchy.', 'small');
|
||||
addGap(host);
|
||||
host.body.add(
|
||||
new Button('Unpack', {
|
||||
variant: 'ghost',
|
||||
onClick: () => host.state.execute(new UnpackContainerCommand(holderId, object.id)),
|
||||
}),
|
||||
);
|
||||
addNote(host, 'Copies the content into the scene and stops', 'small');
|
||||
addNote(host, 'following the template.', 'small');
|
||||
}
|
||||
|
||||
/** Pushes the current model values into the rows. False when the object is gone (rebuild). */
|
||||
|
||||
@@ -40,6 +40,12 @@ export abstract class FreeViewport extends WorldViewport<EditorState> {
|
||||
* placed and where the preview places its root, so content far off to one side would hide it.
|
||||
*/
|
||||
fit(): void {
|
||||
this.frameContent();
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
/** Points the camera at everything drawn, without drawing. */
|
||||
protected frameContent(): void {
|
||||
const { items } = this.collectFrame();
|
||||
const boxes = items.map((item) => {
|
||||
const e = footprintExtent(item.type, item.props, item.m, item.frame);
|
||||
@@ -55,7 +61,6 @@ export abstract class FreeViewport extends WorldViewport<EditorState> {
|
||||
Math.min((this.w - FIT_MARGIN * 2) / width, (this.h - FIT_MARGIN * 2) / height) * this.ratio(),
|
||||
),
|
||||
};
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
/** Centres the view on a world point, keeping the zoom (test hook). */
|
||||
|
||||
@@ -190,6 +190,7 @@ export class SceneViewport extends FreeViewport {
|
||||
|
||||
state.on('load', () => {
|
||||
this.resetView();
|
||||
this.framedFor = null;
|
||||
this.redraw();
|
||||
});
|
||||
state.on('pick', () => this.redraw());
|
||||
@@ -213,9 +214,33 @@ export class SceneViewport extends FreeViewport {
|
||||
return;
|
||||
}
|
||||
this.missedRedraw = false;
|
||||
this.frameOpened();
|
||||
super.redraw();
|
||||
}
|
||||
|
||||
/** The scene the view was last framed for; null until the first one after a load. */
|
||||
private framedFor: string | null = null;
|
||||
|
||||
/**
|
||||
* A scene comes up framed as if Fit had been clicked (#295) – after a load and when another one
|
||||
* is opened. From then on the view is the user's. A scene loaded by the running game is not one
|
||||
* opened: the view is back on the edited scene when the game stops. An empty scene has nothing
|
||||
* to frame and comes up at the origin, one pixel per unit.
|
||||
*/
|
||||
private frameOpened(): void {
|
||||
if (!this.framesOnOpen() || this.state.mode !== 'edit' || this.w <= 0 || this.h <= 0) return;
|
||||
const id = this.holder()?.id ?? '';
|
||||
if (id === this.framedFor) return;
|
||||
this.framedFor = id;
|
||||
this.resetView();
|
||||
if (this.collectFrame().items.length > 0) this.frameScene();
|
||||
}
|
||||
|
||||
/** The Container tab keeps the view of each container instead (#171). */
|
||||
protected framesOnOpen(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Coming back on screen: draws what was skipped while hidden. */
|
||||
catchUp(): void {
|
||||
if (this.missedRedraw) this.redraw();
|
||||
@@ -272,12 +297,16 @@ export class SceneViewport extends FreeViewport {
|
||||
* switching between the two tabs moves nothing. Without one, frames the whole content.
|
||||
*/
|
||||
fitScene(): void {
|
||||
const camera = cameraOfItems(this.collectFrame().items);
|
||||
if (!camera) return this.fit();
|
||||
this.view = freeViewOfCamera(camera, this.w, this.h, this.state.gameScale, this.ratio());
|
||||
this.frameScene();
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
private frameScene(): void {
|
||||
const camera = cameraOfItems(this.collectFrame().items);
|
||||
if (camera) this.view = freeViewOfCamera(camera, this.w, this.h, this.state.gameScale, this.ratio());
|
||||
else this.frameContent();
|
||||
}
|
||||
|
||||
/** Where Ctrl+V puts objects: under the pointer while it is over this view, else nowhere in particular. */
|
||||
pasteHere(): boolean {
|
||||
const holder = this.holder();
|
||||
|
||||
Reference in new issue
Block a user