Open the add menu with a right-click in the asset panel (#244)
New assets could only be added through the + button. A right-click on a category heading or on the empty space below the rows now opens the same menu at the pointer. - ListView gets the callback onListMenu. It fires on a right-click on empty space or on a heading. Only the asset panel has headings, so other lists are not affected. - AssetPanel opens its + menu from there. - A right-click on a heading does not fold or unfold the category. A right-click on empty space clears the selection like a left-click. - A right-click on an asset still opens the menu of that asset. - The empty list now says "No assets - press + or right-click". Tests: - ./check.sh: 178 files, 2573 tests, CHECK EXIT 0 - e2e 1-4: ALL PASSED, 134 checks - 3 new checks in e2e 04 with real right-clicks: menu on the "Scenes" heading (21 types, heading stays folded), menu on empty space, and "Behavior" from that menu creates one, undone again. - Counter-probe: without the menu both right-click checks fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
38f009e035
commit
e0b471c354
4 files changed
+58
-3
No files matched your search
@@ -43,7 +43,7 @@ npm run build # writes dist/ and regenerates index.html; the folder is wh
|
|||||||
The browser suite and the three example games need a machine with a GPU:
|
The browser suite and the three example games need a machine with a GPU:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # the editor, 1452 checks (GM_STEPS=20 for one step)
|
node tools/seed-e2e.mjs && node tools/e2e.mjs # the editor, 1455 checks (GM_STEPS=20 for one step)
|
||||||
# every tool signs in first – see the Accounts page
|
# every tool signs in first – see the Accounts page
|
||||||
node tools/seed-jumprun.mjs && node tools/smoke-jumprun.mjs
|
node tools/seed-jumprun.mjs && node tools/smoke-jumprun.mjs
|
||||||
node tools/seed-tetris.mjs && node tools/smoke-tetris.mjs
|
node tools/seed-tetris.mjs && node tools/smoke-tetris.mjs
|
||||||
|
|||||||
@@ -80,10 +80,11 @@ export class AssetPanel extends Panel {
|
|||||||
add.onClick = () => new Menu(this.addMenuEntries()).openBelow(add);
|
add.onClick = () => new Menu(this.addMenuEntries()).openBelow(add);
|
||||||
this.header.add(add);
|
this.header.add(add);
|
||||||
|
|
||||||
this.list.emptyText = 'No assets – press +';
|
this.list.emptyText = 'No assets – press + or right-click';
|
||||||
this.list.onSelect = (id) => state.select(id ? { kind: 'asset', assetId: id } : null);
|
this.list.onSelect = (id) => state.select(id ? { kind: 'asset', assetId: id } : null);
|
||||||
this.list.onActivate = (id) => this.activate(id);
|
this.list.onActivate = (id) => this.activate(id);
|
||||||
this.list.onContextMenu = (id, x, y) => this.openContextMenu(id, x, y);
|
this.list.onContextMenu = (id, x, y) => this.openContextMenu(id, x, y);
|
||||||
|
this.list.onListMenu = (x, y) => new Menu(this.addMenuEntries()).open(x, y);
|
||||||
this.list.onDragOut = (id) => this.startPlacementDrag(id);
|
this.list.onDragOut = (id) => this.startPlacementDrag(id);
|
||||||
this.list.onToggle = (id, expand) => this.setFolded(id.slice(HEADING.length) as AssetType, !expand);
|
this.list.onToggle = (id, expand) => this.setFolded(id.slice(HEADING.length) as AssetType, !expand);
|
||||||
this.scroll.setContent(this.list);
|
this.scroll.setContent(this.list);
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ export class ListView extends Widget {
|
|||||||
onActivate?: (id: string) => void;
|
onActivate?: (id: string) => void;
|
||||||
/** Returning the opened menu keeps its row marked for as long as it is up. */
|
/** Returning the opened menu keeps its row marked for as long as it is up. */
|
||||||
onContextMenu?: (id: string, globalX: number, globalY: number) => Popup | void;
|
onContextMenu?: (id: string, globalX: number, globalY: number) => Popup | void;
|
||||||
|
/** A right-click on the empty space below the rows or on a heading, which have no menu of their own. */
|
||||||
|
onListMenu?: (globalX: number, globalY: number) => void;
|
||||||
/** Called when a dragged row is released somewhere that could take it. */
|
/** Called when a dragged row is released somewhere that could take it. */
|
||||||
onDrop?: (draggedId: string, spot: DropSpot) => void;
|
onDrop?: (draggedId: string, spot: DropSpot) => void;
|
||||||
/**
|
/**
|
||||||
@@ -121,7 +123,9 @@ export class ListView extends Widget {
|
|||||||
this.pingMarker.visible = false;
|
this.pingMarker.visible = false;
|
||||||
this.addChild(this.emptyLabel, this.pingMarker, this.dropMarker);
|
this.addChild(this.emptyLabel, this.pingMarker, this.dropMarker);
|
||||||
this.on('pointerdown', (e: FederatedPointerEvent) => {
|
this.on('pointerdown', (e: FederatedPointerEvent) => {
|
||||||
if (e.target === this) this.select(null);
|
if (e.target !== this) return;
|
||||||
|
this.select(null);
|
||||||
|
if (e.button === 2) this.onListMenu?.(e.global.x, e.global.y);
|
||||||
});
|
});
|
||||||
this.on('globalpointermove', (e: FederatedPointerEvent) => this.onDragMove(e));
|
this.on('globalpointermove', (e: FederatedPointerEvent) => this.onDragMove(e));
|
||||||
this.onPointerRelease(() => this.onDragEnd());
|
this.onPointerRelease(() => this.onDragEnd());
|
||||||
@@ -294,6 +298,7 @@ export class ListView extends Widget {
|
|||||||
const modifiers: PickModifiers = { ctrl: e.ctrlKey || e.metaKey, shift: e.shiftKey };
|
const modifiers: PickModifiers = { ctrl: e.ctrlKey || e.metaKey, shift: e.shiftKey };
|
||||||
if (item.heading) {
|
if (item.heading) {
|
||||||
if (e.button === 0) this.onToggle?.(item.id, !item.expanded);
|
if (e.button === 0) this.onToggle?.(item.id, !item.expanded);
|
||||||
|
else if (e.button === 2) this.onListMenu?.(e.global.x, e.global.y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The chevron only folds the row – it never selects, so folding keeps the selection put.
|
// The chevron only folds the row – it never selects, so folding keeps the selection put.
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ export async function assets(t) {
|
|||||||
await shot('11-context-menu');
|
await shot('11-context-menu');
|
||||||
await press('Escape');
|
await press('Escape');
|
||||||
|
|
||||||
|
await addMenuOnRightClick(t);
|
||||||
|
|
||||||
// 4b. Drag the Crate asset from the panel into the scene view. Driven step by step rather than
|
// 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.
|
// with `drag()`, because the preview only exists while the button is still down.
|
||||||
const from = await json("__gm.assetRow('crate')");
|
const from = await json("__gm.assetRow('crate')");
|
||||||
@@ -443,3 +445,50 @@ export async function assets(t) {
|
|||||||
JSON.stringify({ first, typeOfFirst, rowAt, iconAt, ink, typeWords }),
|
JSON.stringify({ first, typeOfFirst, rowAt, iconAt, ink, typeWords }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
async function addMenuOnRightClick(t) {
|
||||||
|
const { check, json, shot, click, clickText, press, undo, showTab } = t;
|
||||||
|
const addRows = (rows) =>
|
||||||
|
Array.isArray(rows) && rows.filter(Boolean).length === 21 && rows.includes('Sprite');
|
||||||
|
const heading = await json("__gm.assetRowShown('group:scene')");
|
||||||
|
const sceneFolded = async () =>
|
||||||
|
!(await json('__gm.assetGroups()')).find((g) => g.type === 'scene').expanded;
|
||||||
|
const foldedBefore = await sceneFolded();
|
||||||
|
await click(heading.x, heading.y, 'right');
|
||||||
|
const onHeading = await json('__gm.menuRows()');
|
||||||
|
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 }),
|
||||||
|
);
|
||||||
|
await press('Escape');
|
||||||
|
const groups = await json('__gm.assetGroups()');
|
||||||
|
const lastGroup = groups.at(-1);
|
||||||
|
const lastShown = await json(
|
||||||
|
`__gm.assetRowShown(${JSON.stringify(lastGroup.expanded ? lastGroup.ids.at(-1) : `group:${lastGroup.type}`)})`,
|
||||||
|
);
|
||||||
|
const panel = await json('__gm.assetsRect()');
|
||||||
|
const empty = { x: panel.x + panel.w / 2, y: panel.y + panel.h - 12 };
|
||||||
|
await click(empty.x, empty.y, 'right');
|
||||||
|
const onEmpty = await json('__gm.menuRows()');
|
||||||
|
check(
|
||||||
|
'a right-click on the empty space below the rows opens the + menu',
|
||||||
|
empty.y > lastShown.y + 20 && addRows(onEmpty),
|
||||||
|
JSON.stringify({ empty, lastShown, onEmpty }),
|
||||||
|
);
|
||||||
|
await shot('11c-add-menu-on-right-click');
|
||||||
|
const before = (await json('__gm.state.project.assets')).length;
|
||||||
|
await clickText('Behavior');
|
||||||
|
const fromMenu = await json('__gm.state.project.assets.at(-1)');
|
||||||
|
check(
|
||||||
|
'an entry of that menu creates the asset like the + button does',
|
||||||
|
(await json('__gm.state.project.assets')).length === before + 1 && fromMenu.type === 'behavior',
|
||||||
|
JSON.stringify(fromMenu && { type: fromMenu.type, name: fromMenu.name }),
|
||||||
|
);
|
||||||
|
await undo();
|
||||||
|
await showTab('scene');
|
||||||
|
}
|
||||||
Reference in new issue
Block a user