Plugins in data/plugins are loaded at startup and can add a tab with a read-only node board, and the demo plugin shows every asset as a node (#113)
game-maker / gate (push) Successful in 1m38s
game-maker / suite (push) Successful in 5m43s

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-17 19:00:03 +02:00
1 parent 2a390e52ff
commit 57a97ba94f
22 files changed
+1153 -57

No files matched your search

+38 -5
View File
@@ -43,6 +43,7 @@ import type { PlayFrom } from './settings';
import type { EditorState } from './state';
import { TextureEditor } from './texture/TextureEditor';
import { installFileImport } from './fileImport';
import type { PluginRecord } from './plugins/loader';
export type CentreTab =
| 'scene'
@@ -56,6 +57,10 @@ export type CentreTab =
| 'container'
| 'list';
/** A tab a plugin added; the prefix keeps it apart from every built-in one. */
export type PluginTabId = `plugin:${string}`;
export type TabId = CentreTab | PluginTabId;
const TAB_ORDER = [
'scene',
'game',
@@ -124,7 +129,7 @@ export class Editor implements PlayHost {
readonly assets: AssetPanel;
readonly inspector: Inspector;
readonly console = new ConsolePanel();
tab: CentreTab = 'scene';
tab: TabId = 'scene';
/** Frame rate, refreshed twice a second; null until the first measurement. */
fps: number | null = null;
private readonly play: PlaySession;
@@ -136,8 +141,11 @@ export class Editor implements PlayHost {
readonly consoleSplitter = new Splitter('y');
private readonly workspace: HStack;
private readonly centre: VStack;
private readonly tabs: TabStrip<CentreTab>;
private readonly tabs: TabStrip<TabId>;
private readonly panes: Record<CentreTab, Widget>;
private readonly pluginPanes = new Map<PluginTabId, Widget>();
/** Every file in `data/plugins/` and what became of it; empty until the plugins are loaded. */
plugins: PluginRecord[] = [];
private readonly toolbar: Toolbar;
/** Drawn-frame count at the last measurement, not a counter of its own. */
private fpsFrames = 0;
@@ -205,7 +213,10 @@ export class Editor implements PlayHost {
this.leftColumn.add(this.hierarchy, 1);
this.centre = new VStack();
this.tabs = new TabStrip(TAB_ORDER, TAB_LABELS, (tab) => this.showTab(tab));
this.tabs = new TabStrip<TabId>(
TAB_ORDER.map((tab) => ({ tab, label: TAB_LABELS[tab] })),
(tab) => this.showTab(tab),
);
this.tabs.minHeight = Theme.controlHeight + 8;
// Written out per tab rather than derived, because a `Record<CentreTab, …>` is what makes
// adding a tab a type error everywhere it has to be handled instead of a silent omission.
@@ -308,7 +319,28 @@ export class Editor implements PlayHost {
state.on('mode', () => this.editorRun.invalidate());
}
showTab(tab: CentreTab): void {
/** Whether `id` names a tab already – a built-in one by its own id, a plugin's by the id it gave. */
hasTab(id: string): boolean {
return Object.hasOwn(TAB_LABELS, id) || this.pluginPanes.has(`plugin:${id}`);
}
/** Appends a plugin's tab after the built-in ones; `hasTab` has said the id is free. */
addPluginTab(id: string, label: string, content: Widget): void {
const tab: PluginTabId = `plugin:${id}`;
this.pluginPanes.set(tab, content);
content.visible = false;
this.centre.addChildAt(content, this.centre.getChildIndex(this.consoleSplitter));
content.flex = 1;
this.tabs.addTab(tab, label);
this.centre.relayout();
}
/** What a plugin put into its tab, for the check scripts. */
pluginPane(id: string): Widget | undefined {
return this.pluginPanes.get(`plugin:${id}`);
}
showTab(tab: TabId): void {
this.tab = tab;
// The hierarchy lists what the visible tab edits; every other tab is about the scene.
this.hierarchy.showObjectsOf(
@@ -317,6 +349,7 @@ export class Editor implements PlayHost {
: () => this.state.activeScene,
);
for (const key of TAB_ORDER) this.panes[key].visible = key === tab;
for (const [key, pane] of this.pluginPanes) pane.visible = key === tab;
this.tabs.setActive(tab);
this.centre.relayout();
ui().focus.validate();
@@ -326,7 +359,7 @@ export class Editor implements PlayHost {
* Global x-extent of the tab buttons in the row, from the widget box – not `getBounds()`, which
* on an untoggled ghost tab sees nothing but the label (the background is not drawn at all).
*/
tabRow(): { shown: TabPlace<CentreTab>[]; hidden: CentreTab[] } {
tabRow(): { shown: TabPlace<TabId>[]; hidden: TabId[] } {
return { shown: this.tabs.places(), hidden: this.tabs.hidden() };
}