Drag a whole selection and join existing groups in the hierarchy (#241)
Three follow-ups to the hierarchy groups. Dragging a row that belonged to a multi-selection moved only that one row. The group dialog could only create a new group. The group row drew its folder in front of the label, unlike every other row in the hierarchy. - Dragging a selected row moves every selected object. This holds for reordering, dropping into a group or between its members, dragging out of a group and dropping onto an object as new parent. - The moved objects land side by side in hierarchy order, whichever row was grabbed. The whole move is one undo step. - A selected child of a selected object travels with its parent, not on its own. Dropping the selection onto its own rows does nothing. - Dragging a row that is not selected still moves only that row. - hierarchyDrop.moveObjects replaces the single-object rule. One object is the case with one element, so the old drop tests hold unchanged. - The group dialog has a "Group" dropdown: New group or an existing group. Picking a group greys out the name and turns the button into "Add". A group that holds all chosen objects already is not offered. - Rename keeps using the dialog without the dropdown. - ListRow draws an end icon at the very right and the hint left of it. Only the hierarchy uses iconAtEnd, so other lists do not change. - New test hook __gm.hierarchyHint() gives the centre of a row's hint. Not clicked in the browser: dragging a selection in the container tab and onto an object as parent. Only the unit tests cover these. Tests: - ./check.sh: 178 files, 2564 tests, CHECK EXIT 0 - e2e 1 + 52: ALL PASSED, 27 checks - 7 new unit tests in hierarchy-drop.test.ts, 4 new e2e checks in 52i and 52j with real clicks and a real drag - 4 counter-probes bite: drop rule ignores the selection (5 unit tests fail), icon column not kept free, dialog ignores the picked group, panel ignores the selection (only o2 lands in the group) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
893b81f798
commit
2eeabf7c23
10 files changed
+327
-64
No files matched your search
@@ -77,6 +77,13 @@ export class ListRow extends Widget {
|
||||
return { x: p.x, y: p.y };
|
||||
}
|
||||
|
||||
/** Global centre of the hint; null for rows without one (test hook). */
|
||||
hintGlobal(): { x: number; y: number } | null {
|
||||
if (!this.item.hint) return null;
|
||||
const p = this.hint.getGlobalPosition();
|
||||
return { x: p.x + this.hint.width / 2, y: p.y + this.hint.height / 2 };
|
||||
}
|
||||
|
||||
/** Whether a click at this local x lands on the chevron rather than on the row. */
|
||||
hitsChevron(localX: number): boolean {
|
||||
if (!this.chevronSlot || this.item.expanded === undefined) return false;
|
||||
@@ -115,13 +122,13 @@ export class ListRow extends Widget {
|
||||
mark.position.set(right - ICON_SIZE / 2, this.h / 2);
|
||||
right -= ICON_SIZE;
|
||||
}
|
||||
const marksW = this.w - Theme.padding - right;
|
||||
this.hint.text = this.item.hint ?? '';
|
||||
let hintW = (this.hint.text ? this.hint.width + Theme.padding : 0) + marksW;
|
||||
// An icon at the end stands in the same column on every row; a hint goes in front of it.
|
||||
if (iconAtEnd) {
|
||||
this.icon.position.set(this.w - Theme.padding - hintW - ICON_SIZE / 2, this.h / 2);
|
||||
hintW += ICON_SIZE + Theme.padding;
|
||||
this.icon.position.set(right - ICON_SIZE / 2, this.h / 2);
|
||||
right -= ICON_SIZE + Theme.padding;
|
||||
}
|
||||
this.hint.text = this.item.hint ?? '';
|
||||
const hintW = (this.hint.text ? this.hint.width + Theme.padding : 0) + this.w - Theme.padding - right;
|
||||
this.labelText.text = truncate(this.item.label, this.labelText.style, this.w - x - hintW - Theme.padding);
|
||||
this.labelText.position.set(x, centreY(this.h, this.labelText.height));
|
||||
this.hint.position.set(Math.round(right - this.hint.width), centreY(this.h, this.hint.height));
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface ListItem {
|
||||
overridden?: boolean;
|
||||
/** SVG file drawn in front of the label, 16 units around its origin, `currentColor` muted. */
|
||||
icon?: string;
|
||||
/** Draws `icon` at the right edge, where a hint would stand, instead of in front of the label. */
|
||||
/** Draws `icon` at the right edge instead of in front of the label; a hint then stands left of it. */
|
||||
iconAtEnd?: boolean;
|
||||
/** Small pictures (SVG files) at the right edge, right of the hint – states of the row, like a scene's start mark. */
|
||||
marks?: readonly string[];
|
||||
@@ -231,6 +231,11 @@ export class ListView extends Widget {
|
||||
return this.rows.find((r) => r.item.id === id)?.iconGlobal() ?? null;
|
||||
}
|
||||
|
||||
/** Global centre of a row's hint; null when the row has none (test hook). */
|
||||
hintGlobal(id: string): { x: number; y: number } | null {
|
||||
return this.rows.find((r) => r.item.id === id)?.hintGlobal() ?? null;
|
||||
}
|
||||
|
||||
/** Global centre of a row's chevron; null when the row has none. */
|
||||
chevronGlobal(id: string): { x: number; y: number } | null {
|
||||
return this.rows.find((r) => r.item.id === id)?.chevronGlobal() ?? null;
|
||||
|
||||
Reference in new issue
Block a user