Select a range of rows with shift-click in the hierarchy (#241)
game-maker / gate (push) Successful in 3m21s
game-maker / suite (push) Failing after 9m5s

Ctrl- and shift-click only added single rows to the selection. With
many objects spread over the hierarchy, picking a run of them meant
clicking each one. Shift-click now selects everything between the
anchor and the clicked row, as in a file explorer.

- The anchor is the row last clicked without shift, plain or with
  ctrl. A second shift-click redraws the range from the same anchor.
- Ctrl+shift-click adds the range to the current selection instead
  of replacing it.
- A collapsed group inside the range brings all its members. Children
  hidden under a collapsed parent stay out, since they are not shown.
- Without an anchor from the hierarchy, the primary object picked in
  the scene view serves as anchor. With no selection at all,
  shift-click adds the single row as before.
- The range rule is a pure function in hierarchyRange.ts, so it is
  tested without a browser. HierarchyPanel keeps the anchor.

Not checked in the browser: ctrl+shift-click and the anchor taken from
the scene view. Only the code path covers them.

Tests:
- ./check.sh: 178 files, 2557 tests, CHECK EXIT 0
- e2e 1 + 52: ALL PASSED, 23 checks
- 5 new unit tests in tests/editor/hierarchy-range.test.ts
- New e2e check 52h with real clicks
- Counter-check: without the range, 52h fails with ["o2","o5"]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-09-27 10:53:57 +02:00
1 parent 53fa63441b
commit 893b81f798
5 files changed
+149 -1

No files matched your search

+23
View File
@@ -146,4 +146,27 @@ export async function hierarchyGroups(t) {
JSON.stringify({ narrowedToOther, narrowedToPrimary }),
);
await evaluate('__gm.state.select(null); __gm.hierarchyCollapseAll()');
// 52h. Shift-click takes every row from the last plain click to the clicked one; a second
// Shift-click redraws the range from the same anchor, and it runs upwards just as well.
// It starts on o2: 52g's last click hit o1, and a second one there would count as a double click.
const clickRow = async (id, modifiers) => {
const at = await rowAt(id);
await click(at.x, at.y, 'left', modifiers);
};
await clickRow('o2');
await clickRow('o5', { shift: true });
const down = await selected();
const primaryAfter = (await json('__gm.state.selection'))?.objectId;
await clickRow('o1', { shift: true });
const up = await selected();
check(
'Shift-click selects the rows in between',
same([...down].sort(), ['o2', 'o3', 'o5']) &&
primaryAfter === 'o5' &&
same([...up].sort(), ['o1', 'o2']) &&
same(await rowIds(), ['o1', 'o2', 'o3', 'o5']),
JSON.stringify({ down, primaryAfter, up, rows: await rowIds() }),
);
await evaluate('__gm.state.select(null); __gm.hierarchyCollapseAll()');
}