Show lists in the inspector and variables in their own tab (#252)
The List tab could show one list at a time and was the only place to see its items. A list now shows its items right where it lives, and a new Variables tab shows all variables of an asset or placed object. - InlineList draws the items of a list under its row in the inspector, in struct fields and in the Variables tab. A list inside a list or a struct field is drawn the same way, one step indented. - The index column is the indent. Items sit under their list without an own margin, and the index column is 16 px wide instead of 28 px. - Above 20 items (INLINE_LIMIT) the rest collapses into "... N more - open in tab". During a run the rows show the values from the game. - ListHead puts "N items" and "+ Item" in one row. "N items" opens the list in a tab. A separate Open button squeezed the counter to zero width in the narrow inspector column. - VariablesEditor shows every variable of a behavior asset, a variables asset or a placed behavior. On an asset it edits the declarations. On a placed object an edit only overrides that object, with the usual reset and push buttons. - Opening a list inside a tab adds a new tab and keeps the open one. valuesKey gives each list its own document, so a list that is already open is shown instead of opened twice. - Titles read "Pickup · Variables" or "Listy · nums · [3]". The tab closes when its asset or object is deleted. - Lists on node inputs stay inline only, a node owns no variables. - The List tab, ListEditor and the __gm.list hook are gone. Tests: - ./check.sh: 181 files, 2645 tests, CHECK EXIT 0 - GM_STEPS=1,21,22,32: ALL PASSED, 123 checks - e2e 21 is rewritten for inline lists and the new tab, 22 and 32 are adapted. Sum in README: 1486 (+6 in 21, -1 in 22), counted by hand. - Counter-probes fail as expected: sub-lists not told apart (1 unit test), sub-list replacing the open tab, all items drawn inline (60 rows instead of 20), no live values (0 rows at 24 items in a run). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
92fad6b9e3
commit
4857bb75e6
26 files changed
+1203
-427
No files matched your search
+21
-33
@@ -57,11 +57,10 @@ export type Selection =
|
||||
| null;
|
||||
|
||||
/**
|
||||
* Which list the List tab edits. Deliberately not a `Selection`: a list is not something one
|
||||
* selects, it is a value inside something already selected, and exactly three places hold one.
|
||||
* `path` leads to a list nested inside the value `key` names (#239): a key per struct field, an
|
||||
* index per list item – `[3, 'drops']` is the list in field `drops` of item 3. A struct item needs
|
||||
* no step of its own to be edited, because the List tab renders it inline.
|
||||
* Where a list lives. Deliberately not a `Selection`: a list is not something one selects, it is a
|
||||
* value inside something already selected, and exactly three places hold one. `path` leads to a
|
||||
* list nested inside the value `key` names (#239): a key per struct field, an index per list item –
|
||||
* `[3, 'drops']` is the list in field `drops` of item 3.
|
||||
*/
|
||||
export type ListSlot = { path?: ValuePath } & (
|
||||
| { kind: 'asset'; assetId: string; key: string }
|
||||
@@ -69,20 +68,12 @@ export type ListSlot = { path?: ValuePath } & (
|
||||
| { kind: 'node'; assetId: string; nodeId: string; key: string }
|
||||
);
|
||||
|
||||
function sameSlot(a: ListSlot | null, b: ListSlot | null): boolean {
|
||||
if (!a || !b) return a === b;
|
||||
const pa = a.path ?? [];
|
||||
const pb = b.path ?? [];
|
||||
if (pa.length !== pb.length || pa.some((step, i) => step !== pb[i])) return false;
|
||||
switch (a.kind) {
|
||||
case 'asset':
|
||||
return b.kind === 'asset' && b.assetId === a.assetId && b.key === a.key;
|
||||
case 'object':
|
||||
return b.kind === 'object' && b.holderId === a.holderId && b.objectId === a.objectId && b.key === a.key;
|
||||
case 'node':
|
||||
return b.kind === 'node' && b.assetId === a.assetId && b.nodeId === a.nodeId && b.key === a.key;
|
||||
}
|
||||
}
|
||||
/** Whose variables a Variables tab shows (#252): a behavior or Variables asset, or a placed behavior. */
|
||||
export type VariablesOwner =
|
||||
{ kind: 'asset'; assetId: string } | { kind: 'object'; holderId: string; objectId: string };
|
||||
|
||||
/** What a Variables tab shows: every variable of an owner, or one list at any depth. */
|
||||
export type ValuesTarget = { kind: 'variables'; owner: VariablesOwner } | { kind: 'list'; slot: ListSlot };
|
||||
|
||||
/** Builds an object selection; the last id is the primary one unless given explicitly. */
|
||||
export function objectSelection(holderId: string, objectIds: readonly string[], primary?: string): Selection {
|
||||
@@ -188,8 +179,8 @@ export type EditorEvent =
|
||||
/** A different project was loaded. */
|
||||
| 'load'
|
||||
| 'selection'
|
||||
/** The List tab was pointed at another list (or at none). */
|
||||
| 'list'
|
||||
/** A Variables tab was asked for (`openedValues`). */
|
||||
| 'values'
|
||||
/** An asset was opened (double-click, Enter, or just created): its tab should show. */
|
||||
| 'open'
|
||||
/** An asset should light up where it is listed (`pingedAssetId`), without being selected. */
|
||||
@@ -214,7 +205,8 @@ export class EditorState {
|
||||
revision = 0;
|
||||
commands = new CommandStack(this.project);
|
||||
selection: Selection = null;
|
||||
listSlot: ListSlot | null = null;
|
||||
/** The last target asked for by `openValues`; the tabs themselves are the editor's. */
|
||||
openedValues: ValuesTarget | null = null;
|
||||
mode: EditorMode = 'edit';
|
||||
/**
|
||||
* A sub-state of play rather than a third mode: the runtime copy, the keyboard and unsaved edits all
|
||||
@@ -265,7 +257,7 @@ export class EditorState {
|
||||
this.revision = revision;
|
||||
this.commands = new CommandStack(project);
|
||||
this.selection = null;
|
||||
this.listSlot = null;
|
||||
this.openedValues = null;
|
||||
this.clipboard = null;
|
||||
this.saveStatus = 'saved';
|
||||
this.emit('load');
|
||||
@@ -426,22 +418,18 @@ export class EditorState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Points the List tab at a list. No tab knows the `Editor`, so this is how the button on a
|
||||
* list row gets there: the state changes, the editor switches.
|
||||
* Asks for a Variables tab on `target` – a new one, or the one already showing it. No tab knows
|
||||
* the `Editor`, so this is how the button on a list or a VARIABLES header gets there.
|
||||
*/
|
||||
openList(slot: ListSlot | null): void {
|
||||
if (sameSlot(this.listSlot, slot)) {
|
||||
if (slot) this.emit('list');
|
||||
return;
|
||||
}
|
||||
this.listSlot = slot;
|
||||
this.emit('list');
|
||||
openValues(target: ValuesTarget): void {
|
||||
this.openedValues = target;
|
||||
this.emit('values');
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the asset and asks for its tab. Selecting alone never switches tabs (a click in the
|
||||
* Assets panel is a look at the inspector); this is the way a double-click or a fresh asset gets
|
||||
* there, and like `openList` it works without knowing the `Editor`.
|
||||
* there, and like `openValues` it works without knowing the `Editor`.
|
||||
*/
|
||||
openAsset(assetId: string): void {
|
||||
this.select({ kind: 'asset', assetId });
|
||||
|
||||
Reference in new issue
Block a user