Initial commit: Game Maker phase 1
Canvas-only 2D game editor/runtime built on PixiJS v8 + Vite + TypeScript. - core: asset model (Scene/Camera/Sprite), scene objects with sparse overrides, property schema + resolve, command stack (undo/redo), serialization and project store interface (16 unit tests) - ui: custom in-canvas widget toolkit (panels, fields, color picker, list/scroll, popup) with a hidden <input> keyboard bridge - editor: asset/hierarchy panels, schema-driven inspector, toolbar - viewport: box rendering, camera frame, selection gizmo, drag/zoom/pan - runtime: world/entity instantiation for play mode, input, camera follow - api: PHP JSON project store with revision counter (409 on conflict) - tools: CDP-based e2e smoke test Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
commit
5a46124454
65 files changed
+7193
No files matched your search
@@ -0,0 +1,99 @@
|
||||
import type { Application } from 'pixi.js';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
ColorField,
|
||||
Dialog,
|
||||
HStack,
|
||||
Label,
|
||||
ListView,
|
||||
Menu,
|
||||
NumberField,
|
||||
Panel,
|
||||
ScrollContainer,
|
||||
TextField,
|
||||
Theme,
|
||||
VStack,
|
||||
toHex,
|
||||
} from './ui';
|
||||
|
||||
/**
|
||||
* Developer page (`?playground`) that shows every widget of the toolkit for manual
|
||||
* testing: focus cycling, typing, scrubbing, color picking, scrolling, popups.
|
||||
*/
|
||||
export function mountPlayground(app: Application, root: VStack): void {
|
||||
const status = new Label('Interact with the widgets – events are logged here.', { style: 'muted', padding: Theme.padding });
|
||||
const log = (message: string) => status.setText(message);
|
||||
|
||||
const row = new HStack({ gap: Theme.padding, padding: Theme.padding });
|
||||
|
||||
// Column 1: inputs
|
||||
const inputs = new Panel({ title: 'Inputs' });
|
||||
inputs.content.add(new Label('Text field'));
|
||||
inputs.content.add(new TextField('Crate', { placeholder: 'Name', onInput: (v) => log(`text input: ${v}`), onChange: (v) => log(`text change: ${v}`) }));
|
||||
inputs.content.add(new Label('Number field (drag to scrub)'));
|
||||
inputs.content.add(new NumberField(100, { min: 1, step: 1, onInput: (v) => log(`number input: ${v}`), onChange: (v) => log(`number change: ${v}`) }));
|
||||
inputs.content.add(new NumberField(1, { min: 0.1, max: 10, step: 0.1, onChange: (v) => log(`zoom change: ${v}`) }));
|
||||
inputs.content.add(new Label('Color field'));
|
||||
inputs.content.add(new ColorField(0xe0553f, { onInput: (c) => log(`color input: ${toHex(c)}`), onChange: (c) => log(`color change: ${toHex(c)}`) }));
|
||||
const checkRow = new HStack({ gap: Theme.gap, align: 'center' });
|
||||
const checkbox = new Checkbox(true);
|
||||
checkbox.onChange = (v) => log(`checkbox: ${v}`);
|
||||
checkRow.add(checkbox);
|
||||
checkRow.add(new Label('Checkbox'), 1);
|
||||
inputs.content.add(checkRow);
|
||||
row.add(inputs, 1);
|
||||
|
||||
// Column 2: buttons + popups
|
||||
const actions = new Panel({ title: 'Buttons & popups' });
|
||||
actions.content.add(new Button('Default', { onClick: () => log('default clicked') }));
|
||||
actions.content.add(new Button('Primary', { variant: 'primary', onClick: () => log('primary clicked') }));
|
||||
const toggle = new Button('Toggle me');
|
||||
toggle.onClick = () => { toggle.setToggled(!toggle.toggled); log(`toggled: ${toggle.toggled}`); };
|
||||
actions.content.add(toggle);
|
||||
actions.content.add(new Button('Ghost', { variant: 'ghost', onClick: () => log('ghost clicked') }));
|
||||
const disabled = new Button('Disabled');
|
||||
disabled.setEnabled(false);
|
||||
actions.content.add(disabled);
|
||||
const menuButton = new Button('Open menu…');
|
||||
menuButton.onClick = () => {
|
||||
const g = menuButton.getGlobalPosition();
|
||||
new Menu([
|
||||
{ label: 'Scene', onSelect: () => log('menu: scene') },
|
||||
{ label: 'Camera', onSelect: () => log('menu: camera') },
|
||||
{ label: 'Sprite', onSelect: () => log('menu: sprite') },
|
||||
{ label: 'Delete', danger: true, onSelect: () => log('menu: delete') },
|
||||
]).open(g.x, g.y + menuButton.h + 2);
|
||||
};
|
||||
actions.content.add(menuButton);
|
||||
actions.content.add(new Button('Open dialog…', {
|
||||
onClick: () => new Dialog('Delete asset?', 'Every placed instance is removed as well.', [
|
||||
{ label: 'Cancel' },
|
||||
{ label: 'Delete', danger: true, onClick: () => log('dialog: deleted') },
|
||||
]).openCentered(),
|
||||
}));
|
||||
row.add(actions, 1);
|
||||
|
||||
// Column 3: list in a scroll container
|
||||
const listPanel = new Panel({ title: 'List (scroll)', padding: 0 });
|
||||
const scroll = new ScrollContainer();
|
||||
const list = new ListView();
|
||||
list.setItems(Array.from({ length: 40 }, (_, i) => ({
|
||||
id: `item-${i}`,
|
||||
label: `Item ${i + 1}${i % 7 === 0 ? ' with a rather long name that must truncate' : ''}`,
|
||||
hint: i % 3 === 0 ? 'sprite' : i % 3 === 1 ? 'camera' : 'scene',
|
||||
swatch: i % 3 === 0 ? (0x3fa0e0 + i * 0x040201) & 0xffffff : undefined,
|
||||
depth: i % 5 === 4 ? 1 : 0,
|
||||
})));
|
||||
list.onSelect = (id) => log(`list select: ${id}`);
|
||||
list.onActivate = (id) => log(`list activate: ${id}`);
|
||||
list.onContextMenu = (id, x, y) => new Menu([{ label: `Context for ${id}`, onSelect: () => log(`context: ${id}`) }]).open(x, y);
|
||||
scroll.setContent(list);
|
||||
listPanel.content.add(scroll, 1);
|
||||
row.add(listPanel, 1);
|
||||
|
||||
root.add(new Label('Game Maker – UI playground', { style: 'bodyBold', padding: Theme.padding }));
|
||||
root.add(row, 1);
|
||||
root.add(status);
|
||||
root.resize(app.screen.width, app.screen.height);
|
||||
}
|
||||
Reference in new issue
Block a user