Left-align menu entries, add a Dropdown field, scrollable and searchable menus
Menu entries are left-aligned and live in a ScrollContainer capped at 320 px, so a
long list (the parent picker holds every object of the scene) scrolls instead of
running off the screen. `Menu(items, { searchable: true })` puts a filter box above
the scroll area; Enter takes the first match. Filtering forces a relayout because
two queries with the same hit count leave the height unchanged, which would keep
the stale row positions.
Fields that open a menu are a new `Dropdown` widget: the value sits flush left and
the chevron is drawn flush right instead of being written into the label. A menu
opened below a field is never narrower than that field.
New debug hooks `overlayTexts()` / `overlayRect()` make the popup contents
assertable; e2e step 11 covers the cap, the scrolling and the search box.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d54a620ebd
commit
15ffcd5739
16 files changed
+297
-39
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { type Application, type Container, Text } from 'pixi.js';
|
||||
import { ui, Widget } from '../ui';
|
||||
import type { Editor } from './Editor';
|
||||
import type { EditorState } from './state';
|
||||
|
||||
@@ -32,9 +33,36 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
const p = node.getGlobalPosition();
|
||||
return { x: p.x + node.width / 2, y: p.y + node.height / 2 };
|
||||
};
|
||||
/**
|
||||
* The visible text of the overlay layer, in drawing order – everything an open popup shows.
|
||||
* Menus hide filtered-out entries, so this is also what a search box currently leaves.
|
||||
*/
|
||||
const overlayTexts = (): string[] => {
|
||||
const out: string[] = [];
|
||||
const walk = (node: Container): void => {
|
||||
if (!node.visible) return;
|
||||
if (node instanceof Text) {
|
||||
if (node.text) out.push(node.text);
|
||||
return;
|
||||
}
|
||||
for (const child of node.children) walk(child);
|
||||
};
|
||||
walk(ui().overlay);
|
||||
return out;
|
||||
};
|
||||
/** Screen rectangle of the open popup (the backdrop is not a widget), or null when none is. */
|
||||
const overlayRect = (): { x: number; y: number; w: number; h: number } | null => {
|
||||
const popups = ui().overlay.children.filter((node): node is Widget => node instanceof Widget);
|
||||
const popup = popups[popups.length - 1];
|
||||
if (!popup) return null;
|
||||
const p = popup.getGlobalPosition();
|
||||
return { x: p.x, y: p.y, w: popup.w, h: popup.h };
|
||||
};
|
||||
(window as unknown as { __gm: unknown }).__gm = {
|
||||
state,
|
||||
locate,
|
||||
overlayTexts,
|
||||
overlayRect,
|
||||
viewportCenter: () => centreOf(viewport),
|
||||
viewportInfo: () => viewport.debugInfo(),
|
||||
gameInfo: () => gameView.debugInfo(),
|
||||
|
||||
Reference in new issue
Block a user