Show empty asset groups on request, and add by the heading (#361)
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:
1 parent
cf96442835
commit
5cb7ef7bed
11 files changed
+269
-38
No files matched your search
+116
-8
@@ -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) }),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user