Let variables, fields, ports and list items be dragged into order (#310)
Variables of a behavior, the fields of a struct, the ports of a start or end node and the items of a list could only be added and removed. Changing their order meant removing and adding them again. Now each of them is dragged by the mark in front of its row. - sortBlocks (ui/blockDrag.ts) makes the rows of one column sortable. A block is every row it covers, so a struct moves with its fields and a list item with its nested list. The block is dimmed while it is carried, an accent line shows where it lands, and a scrolled panel follows the pointer at its edge. Less than 4 px stays a click. - The arithmetic is pure and lives in ui/sortable.ts: blockGap, gapLineY, dropSlot and edgeScroll. dropIndex of the tile strip moved there too, so the reorder maths sits in one file. - MoveVariableCommand, MoveStructFieldCommand and MovePortCommand build on the existing MoveCommand. Undo moves back. - Lists need no command. InlineList replaces the list as a whole, as it does for every other edit of its items. - The grip is the kind mark, for a list item its number. Moving only happens among siblings: a field stays in its struct. - On a Struct asset the move runs through WithStructTypes. Typed variables, Pack/Expand ports and split outputs follow the new order. - Values and edges hang on names, so only the order changes. Nodes running a behavior read their ports from the start node, the new order reaches them without a copy. - Not sortable by design: the variables of a placed object, the fields of a typed struct (moved on the Struct asset) and the picked ports of Variables/Properties nodes. - Test hook __gm.inspectorMark(label) finds the mark of a row. Tests: - Gate on melektaus-atx: 235 files, 3572 tests, CHECK EXIT 0 (+9). - tests/core/reorder.test.ts: a variable, a field two levels deep, a Struct asset reaching a typed variable and Pack, and start ports reaching the calling node with edges and result kept. - e2e 22: ALL PASSED, 39 checks (+4, sum 1739). 22f-iii drags a variable past a struct of several rows, then a field inside it. - Counter-probes: dropSlot by top edge instead of middle and a field move that ignores its path fail 3 unit tests. With sortBlocks switched off, the three order checks of 22f-iii fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf6043a066
commit
2c19c89878
18 files changed
+671
-83
No files matched your search
@@ -17,6 +17,8 @@ import {
|
||||
Menu,
|
||||
type MenuEntry,
|
||||
ScrollContainer,
|
||||
type SortBlock,
|
||||
sortBlocks,
|
||||
Spacer,
|
||||
TextField,
|
||||
Theme,
|
||||
@@ -40,6 +42,7 @@ import {
|
||||
import type { Project } from '../../core/model/Project';
|
||||
import { structAssetOf, typedItem, typeFields } from '../../core/model/structTypes';
|
||||
import { nextFieldName } from '../../core/commands/naming';
|
||||
import { moveItem } from '../../core/util/list';
|
||||
import { type FieldKind, NESTED_KINDS, type NestedKind } from '../../core/properties/kinds';
|
||||
import {
|
||||
type ListItem,
|
||||
@@ -312,7 +315,8 @@ export class InlineList extends VStack {
|
||||
this.more = null;
|
||||
if (access) {
|
||||
const { shown, more } = shownItems(access.value.length, this.options.limit);
|
||||
access.value.slice(0, shown).forEach((item, index) => this.itemRow(access, item, index));
|
||||
const blocks = access.value.slice(0, shown).map((item, index) => this.itemRow(access, item, index));
|
||||
sortBlocks(blocks, (from, to) => this.moveItem(from, to));
|
||||
if (more > 0) this.add(this.moreLine(access, more));
|
||||
}
|
||||
this.relayout();
|
||||
@@ -332,7 +336,16 @@ export class InlineList extends VStack {
|
||||
return line;
|
||||
}
|
||||
|
||||
private itemRow(access: ListAccess, item: ListValue[number], index: number): void {
|
||||
/** An item dragged by its number to another place (#310): the order is part of the value. */
|
||||
private moveItem(from: number, to: number): void {
|
||||
const access = this.access();
|
||||
if (!access) return;
|
||||
const next = [...access.value];
|
||||
if (moveItem(next, from, to)) access.replace(next);
|
||||
}
|
||||
|
||||
/** The item's rows, its number being where it is dragged from. */
|
||||
private itemRow(access: ListAccess, item: ListValue[number], index: number): SortBlock {
|
||||
const kind = itemKindOf(item);
|
||||
// A struct item is a stack of field rows: its row grows with it and aligns to the top.
|
||||
const tall = kind === 'struct';
|
||||
@@ -365,7 +378,10 @@ export class InlineList extends VStack {
|
||||
const widgets = this.rowWidgets[index];
|
||||
if (widgets) widgets.remove = remove;
|
||||
this.add(row);
|
||||
if (below) this.add(indented(below, INDEX_WIDTH + Theme.gap));
|
||||
if (!below) return { grip: label, rows: [row] };
|
||||
const items = indented(below, INDEX_WIDTH + Theme.gap);
|
||||
this.add(items);
|
||||
return { grip: label, rows: [row, items] };
|
||||
}
|
||||
|
||||
/** A list inside this one: its head goes where a value would, its items below, one step further in. */
|
||||
|
||||
Reference in new issue
Block a user