Show empty asset groups on request, and add by the heading (#361)
game-maker / gate (push) Successful in 3m47s
game-maker / suite (push) Successful in 15m1s

The asset panel only listed the categories and types that hold assets.
To make the first asset of a kind you had to know its category in the
`+` menu. A switch now lists all of them, and a right-click on a
heading offers just what sits under it.

- General settings has "Show empty asset categories and types", off by
  default and kept in this browser. Applying it refreshes the panel at
  once through a new `onGeneralSettings` listener.
- `groupAssets(assets, empty)` gives every category and type when
  asked. The empty ones stand faint with a count of 0, and an empty
  type has no chevron, since there is nothing to unfold.
- `ListItem.faint` draws a row's label, hint and icons at half opacity,
  like a disabled widget.
- A right-click on a category heading offers the types of that
  category without a submenu, on a type heading only that type. The
  empty space below the rows still opens the whole `+` menu.
  `ListView.onListMenu` passes the heading's id for this.
- Every menu of new assets says "Create asset" above its entries, so a
  click on a type reads as making one. `MenuOptions.title` draws that
  muted line. It is not an entry and matches no search.

Tests:
- Unit asset-groups and view-tween: 23 green, 2 new. Counter-test
  with the empty flag ignored: the new groups test fails.
- e2e 04 (melektaus-atx): ALL PASSED, 67 checks, 4 new and 1
  rewritten. Counter-test with the heading id dropped: the 3 menu
  checks fail. The title is checked on the `+` button and on a type
  heading (2 checks extended), the rerun passed with 67 checks.
- Gate (melektaus-atx): CHECK EXIT 0, 272 files, 4212 tests. It ran
  before the menu title. Afterwards only the menu and asset-groups
  units, typecheck and lint ran, all clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-09 06:51:59 +02:00
1 parent cf96442835
commit 5cb7ef7bed
11 files changed
+269 -38

No files matched your search

+116 -8
View File
@@ -56,6 +56,7 @@ export async function assets(t) {
await showTab('scene');
await addMenuOnRightClick(t);
await emptyGroupsOnSwitch(t);
// 4b. Drag the Crate asset from the panel into the scene view. Driven step by step rather than
// with `drag()`, because the preview only exists while the button is still down.
@@ -108,10 +109,11 @@ export async function assets(t) {
const rows = await json('__gm.menuRows()');
const opens = await json('__gm.menuSubmenus()');
const menuBox = await json('__gm.overlayRect()');
const title = await json('__gm.menuTitle()');
check(
'the + menu lists the categories of the asset panel, each opening a submenu',
String(rows) === String(CATEGORIES) && opens.every(Boolean),
JSON.stringify({ rows, opens }),
'the + menu lists the categories of the asset panel, each opening a submenu, under "Create asset"',
String(rows) === String(CATEGORIES) && opens.every(Boolean) && title === 'Create asset',
JSON.stringify({ rows, opens, title }),
);
const offered = {};
for (const category of CATEGORIES) {
@@ -523,8 +525,9 @@ export async function assets(t) {
}
/**
* 4a-ii. Where no menu of its own opens – on a group heading or on the empty space below the rows –
* a right-click brings up the + menu (#244). The heading stays folded as it was.
* 4a-ii. Where no menu of its own opens, a right-click brings up the + menu (#244): on the empty
* space below the rows all of it, on a category heading the types of that category, on a group
* heading only its type (#361). The heading stays folded as it was.
*/
async function addMenuOnRightClick(t) {
const { check, json, shot, click, clickText, press, undo, showTab } = t;
@@ -535,10 +538,33 @@ async function addMenuOnRightClick(t) {
const foldedBefore = await sceneFolded();
await click(heading.x, heading.y, 'right');
const onHeading = await json('__gm.menuRows()');
const headingTitle = await json('__gm.menuTitle()');
check(
'a right-click on a group heading opens the + menu and leaves the group as it was',
addRows(onHeading) && (await sceneFolded()) === foldedBefore,
JSON.stringify({ onHeading, foldedBefore }),
'a right-click on a group heading offers only its type, under "Create asset", and leaves the group as it was',
String(onHeading) === 'Scene' &&
headingTitle === 'Create asset' &&
(await sceneFolded()) === foldedBefore,
JSON.stringify({ onHeading, headingTitle, foldedBefore }),
);
await press('Escape');
const basics = await json("__gm.assetRowShown('category:Basics')");
await click(basics.x, basics.y, 'right');
const onCategory = {
rows: await json('__gm.menuRows()'),
submenus: await json('__gm.menuSubmenus()'),
};
onCategory.filedUnder = await json(
`${JSON.stringify(onCategory.rows)}.map((label) => __gm.addMenuCategory(label))`,
);
check(
'a right-click on a category heading offers the types of that category, sorted, without submenus',
onCategory.rows.length >= 4 &&
onCategory.rows.includes('Scene') &&
onCategory.filedUnder.every((c) => c === 'Basics') &&
String(onCategory.rows) === String([...onCategory.rows].sort()) &&
onCategory.submenus.every((open) => !open) &&
(await json('__gm.assetCategories()')).find((c) => c.category === 'Basics').expanded,
JSON.stringify(onCategory),
);
await press('Escape');
const groups = await json('__gm.assetGroups()');
@@ -573,3 +599,85 @@ async function addMenuOnRightClick(t) {
await undo();
await showTab('scene');
}
/**
* 4a-iii. A switch in "General settings…" lists every category and type, the empty ones faint
* (#361); off again, only what holds assets is left. A right-click on an empty type's heading
* creates the first asset of it.
*/
async function emptyGroupsOnSwitch(t) {
const { check, json, shot, click, clickText, clickToolbar, sleep, undo, showTab } = t;
const flip = async () => {
await clickToolbar('menu');
await clickText('General settings…');
const box = await json('__gm.generalSettingsEmptyGroupsAt()');
await click(box.x, box.y);
await clickText('Apply');
await sleep(100);
};
const shape = (groups) => JSON.stringify(groups.map((g) => [g.type, g.ids]));
const before = await json('__gm.assetGroups()');
await flip();
const all = await json('__gm.assetGroups()');
const categories = await json('__gm.assetCategories()');
// One that an entry creates at once – a texture, a font or a sound opens a picker instead.
const empty = all.find(
(g) => g.ids.length === 0 && !['texture', 'font', 'sound', 'pixelart'].includes(g.type),
);
const full = all.find((g) => g.ids.length > 0);
const emptyCategory = CATEGORIES.find((c) =>
all.filter((g) => g.category === c).every((g) => g.ids.length === 0),
);
const seen = {
stored: await json(`JSON.parse(localStorage.getItem('game-maker.general-settings') ?? 'null')`),
emptyAlpha: await json(`__gm.assetRowAlpha('group:${empty?.type}')`),
fullAlpha: await json(`__gm.assetRowAlpha('group:${full?.type}')`),
categoryAlpha: emptyCategory ? await json(`__gm.assetRowAlpha('category:${emptyCategory}')`) : null,
filledCategoryAlpha: await json(`__gm.assetRowAlpha('category:${full?.category}')`),
};
check(
'the switch lists every category and type, the empty ones faint, the rest as before',
all.length === ASSET_TYPE_COUNT &&
String(categories.map((c) => c.category)) === String(CATEGORIES) &&
before.every(
(g) => JSON.stringify(all.find((a) => a.type === g.type)?.ids) === JSON.stringify(g.ids),
) &&
seen.stored?.showEmptyAssetGroups === true &&
Boolean(empty) &&
seen.emptyAlpha === 0.5 &&
seen.fullAlpha === 1 &&
seen.filledCategoryAlpha === 1 &&
(!emptyCategory || seen.categoryAlpha === 0.5),
JSON.stringify({ ...seen, empty: empty?.type, emptyCategory, all: all.length }),
);
await shot('11d-empty-asset-groups');
// Scrolled into view first: with every type listed the panel is longer than it is high.
const heading = await json(`__gm.assetRow('group:${empty.type}')`);
await click(heading.x, heading.y, 'right');
const offered = await json('__gm.menuRows()');
await clickText(offered[0]);
const made = await json('__gm.state.project.assets.at(-1)');
const after = {
offered,
made: made && { type: made.type, name: made.name },
alpha: await json(`__gm.assetRowAlpha('group:${empty.type}')`),
};
check(
'a right-click on an empty type offers just that type, and its first asset makes the heading solid',
offered.length === 1 && made?.type === empty.type && after.alpha === 1,
JSON.stringify(after),
);
await undo();
await showTab('scene');
await flip();
const off = await json('__gm.assetGroups()');
check(
'switched off again, only the types with assets are listed',
shape(off) === shape(before) &&
(await json(`JSON.parse(localStorage.getItem('game-maker.general-settings'))`)).showEmptyAssetGroups ===
false,
JSON.stringify({ before: shape(before), off: shape(off) }),
);
}