Split the debug hook into one file per topic (#215)
debugHook.ts had grown to 1097 lines of unrelated test hooks. They now live in seven files under src/editor/debug/, grouped by topic. debugHook.ts only assembles them and is down to 86 lines. - The shape of window.__gm is unchanged for tools/. Measured in the browser before and after: 120 top-level keys and 313 leaves with their arity, byte-identical JSON. - The Text import is explicit in the helpers. Without it the name fell back to the DOM type and instanceof would never have matched. Findings: - The comment for vectorArt sat above pixelArt. It is back in place. - An orphaned duplicate comment on assetRow is gone. Tests: - ./check.sh: 145 files, 2192 tests, CHECK EXIT 0 (unchanged) - e2e 1-16,37,39-43,47,48: ALL PASSED, 734 checks - The shape probe turns red against a deliberately misnested hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b830f9c747
commit
ed29b2f3dc
9 files changed
+1114
-1031
No files matched your search
@@ -0,0 +1,197 @@
|
||||
import { findAssetOf, findObjectHolder } from '../../core/model/Project';
|
||||
import { resolveObjects } from '../../core/model/instance';
|
||||
import { findLayer, layerZ } from '../../core/model/tilemap';
|
||||
import { currentTilemapWidgets, paintedLayerId, paintedTilemap } from '../panels/TilemapPanel';
|
||||
import type { TilemapTool } from '../state';
|
||||
import { rectOf } from '../textProbe';
|
||||
import { centreOf, type HookContext } from './hookHelpers';
|
||||
|
||||
/** The Scene and Game tabs, the tilemap painted in the scene view, and the Container tab. */
|
||||
export function sceneHooks({ state, editor }: HookContext) {
|
||||
const { viewport, gameView, containerEditor } = editor;
|
||||
/**
|
||||
* A holder's objects the way the editor sees them: the content of placed containers is resolved
|
||||
* from the template, so it is nowhere in `holder.objects` (see `model/instance.ts`).
|
||||
*/
|
||||
const objectsOf = (holderId: string) => {
|
||||
const holder = findObjectHolder(state.project, holderId);
|
||||
return holder ? resolveObjects(state.project, holder) : [];
|
||||
};
|
||||
return {
|
||||
sceneObjects: () => (state.activeScene ? objectsOf(state.activeScene.id) : []),
|
||||
viewportCenter: () => centreOf(viewport),
|
||||
/** Pans the Scene tab to a world point – a step's own lane, away from what earlier steps left. */
|
||||
lookAt: (x: number, y: number) => viewport.lookAt(x, y),
|
||||
/** Its drawn box – a step that wants an empty spot must stay inside what the console left over. */
|
||||
viewportRect: () => {
|
||||
const p = viewport.getGlobalPosition();
|
||||
return { x: p.x, y: p.y, w: viewport.w, h: viewport.h };
|
||||
},
|
||||
/**
|
||||
* A point in the viewport that no object covers – steps click there to deselect. A fixed
|
||||
* offset from the centre lands on an object as soon as the viewport changes size.
|
||||
*/
|
||||
emptySpot: () => {
|
||||
const p = viewport.getGlobalPosition();
|
||||
const scene = state.activeScene;
|
||||
const taken = scene
|
||||
? objectsOf(scene.id)
|
||||
.map((object) => viewport.objectRect(object.id))
|
||||
.filter((rect): rect is NonNullable<typeof rect> => rect !== null)
|
||||
: [];
|
||||
const clear = (x: number, y: number) =>
|
||||
!taken.some((r) => x >= r.x - 12 && x <= r.x + r.w + 12 && y >= r.y - 12 && y <= r.y + r.h + 12);
|
||||
for (let y = p.y + viewport.h - 24; y > p.y + 24; y -= 24) {
|
||||
for (let x = p.x + 24; x < p.x + viewport.w - 24; x += 24) if (clear(x, y)) return { x, y };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
viewportInfo: () => viewport.debugInfo(),
|
||||
/** A full redraw of the Scene tab – what a pan, which only moves the camera, is compared with. */
|
||||
viewportRedraw: () => viewport.redraw(),
|
||||
/** Where a dragged asset would land in the Scene tab, and why not when it may not. */
|
||||
dropPreview: () => viewport.dropInfo(),
|
||||
gameInfo: () => gameView.debugInfo(),
|
||||
/** Selection outlines in the Scene tab: a switched-off object is invisible but still framed. */
|
||||
gizmos: () => viewport.gizmoInfo(),
|
||||
/** The corner handles of the polygon collider being edited, in global pixels (#151). */
|
||||
polygonCorners: () => viewport.cornerInfo(),
|
||||
/** The rays the editor run cast in the last moment, as the Scene tab draws them, in world units (#153). */
|
||||
rays: () => viewport.rayInfo(),
|
||||
layerOf: (id: string) => viewport.layerOf(id),
|
||||
/** The icon a particle source wears in the Scene tab, with its name and its owner link. */
|
||||
sceneIcon: (id: string) => viewport.iconInfo(id),
|
||||
drawOrder: () => viewport.drawOrder(),
|
||||
objectScreen: (id: string) => viewport.objectScreen(id),
|
||||
objectRect: (id: string) => viewport.objectRect(id),
|
||||
/** What a click at a screen point picks from in the Scene tab, front to back (#203). */
|
||||
sceneStack: (x: number, y: number) => viewport.stackInfo(x, y),
|
||||
/** The same for the Game tab – through the camera, so what the player sees. */
|
||||
gameScreen: (id: string) => gameView.objectScreen(id),
|
||||
gameRect: (id: string) => gameView.objectRect(id),
|
||||
gameCenter: () => centreOf(gameView),
|
||||
/** Which typeface a text ended up drawn in, and how wide it made it. */
|
||||
textInfo: (id: string) => viewport.textInfo(id),
|
||||
gameTextInfo: (id: string) => gameView.textInfo(id),
|
||||
tilemap: {
|
||||
/** What the scene view painted a map with, and on which layer (#128). */
|
||||
paint: () => ({ ...state.tilemapPaint }),
|
||||
/** The cells of one layer as the model holds them: `"x,y"` to tile id. */
|
||||
cells: (assetId: string, layerId: string) => {
|
||||
const asset = findAssetOf(state.project, assetId, 'tilemap');
|
||||
const layer = asset && findLayer(asset, layerId);
|
||||
return layer ? { ...layer.cells } : null;
|
||||
},
|
||||
layers: (assetId: string) =>
|
||||
findAssetOf(state.project, assetId, 'tilemap')?.layers.map((layer) => ({
|
||||
id: layer.id,
|
||||
name: layer.name,
|
||||
hidden: layer.hidden === true,
|
||||
z: layerZ(layer),
|
||||
cells: Object.keys(layer.cells).length,
|
||||
})) ?? null,
|
||||
/** What the scene view drew: meshes (one draw call each), cells and visible layers. */
|
||||
info: (objectId: string) => viewport.tilemapInfo(objectId),
|
||||
gameInfo: (objectId: string) => gameView.tilemapInfo(objectId),
|
||||
/** Where the scene view outlines the map as blocking: the merged rectangles, in cells (#132). */
|
||||
solids: (objectId: string) => viewport.solidInfo(objectId),
|
||||
/**
|
||||
* The painting panel beside the scene view (#142): whether it is on screen, its box, the
|
||||
* scene view's box next to it and its splitter – null boxes while it is hidden.
|
||||
*/
|
||||
panel: () => {
|
||||
// The editor hides the panel itself on every other tab, so its own flag is the whole answer.
|
||||
const shown = editor.tilemapPanel.visible;
|
||||
return {
|
||||
shown,
|
||||
title: editor.tilemapPanel.title,
|
||||
panel: shown ? rectOf(editor.tilemapPanel) : null,
|
||||
splitter: shown ? centreOf(editor.tilemapSplitter) : null,
|
||||
viewport: rectOf(viewport),
|
||||
};
|
||||
},
|
||||
/** Where the widgets of the painting panel sit: their texts are on screen elsewhere too. */
|
||||
toolAt: (tool: string) => {
|
||||
const button = currentTilemapWidgets()?.tools.get(tool as TilemapTool);
|
||||
return button ? centreOf(button) : null;
|
||||
},
|
||||
tileButtonAt: (assetId: string) =>
|
||||
currentTilemapWidgets()?.palette?.swatchGlobal(assetId)?.centre ?? null,
|
||||
/** A tile's box in the palette: its centre, where its picture sits and the palette's columns (#139). */
|
||||
tileSwatch: (assetId: string) => {
|
||||
const palette = currentTilemapWidgets()?.palette;
|
||||
const swatch = palette?.swatchGlobal(assetId);
|
||||
return palette && swatch ? { ...swatch, columns: palette.columns } : null;
|
||||
},
|
||||
/** The plus in the layer list's header. */
|
||||
addLayerAt: () => currentTilemapWidgets()?.layers.addGlobal() ?? null,
|
||||
/**
|
||||
* Where the painted layer's name stands in its row – a double-click there edits it (#138) –
|
||||
* with the name the row shows and the field it is being edited in, while it is.
|
||||
*/
|
||||
layerNameAt: () => {
|
||||
const widgets = currentTilemapWidgets();
|
||||
const target = paintedTilemap(state);
|
||||
if (!widgets || !target) return null;
|
||||
const layerId = paintedLayerId(state, target.asset);
|
||||
const at = widgets.layers.nameGlobal(layerId);
|
||||
const editing = widgets.layers.renameField;
|
||||
return at
|
||||
? {
|
||||
...at,
|
||||
value: findLayer(target.asset, layerId)?.name ?? '',
|
||||
editing: editing ? { ...centreOf(editing), value: editing.value } : null,
|
||||
}
|
||||
: null;
|
||||
},
|
||||
/** The Z field in the painted layer's row, with what it holds (#174). */
|
||||
layerZAt: () => {
|
||||
const widgets = currentTilemapWidgets();
|
||||
const target = paintedTilemap(state);
|
||||
const field = widgets && target && widgets.layers.valueField(paintedLayerId(state, target.asset));
|
||||
return field ? { ...centreOf(field), value: field.value } : null;
|
||||
},
|
||||
/** The duplicate mark on the painted layer's row. */
|
||||
duplicateLayerAt: () => {
|
||||
const widgets = currentTilemapWidgets();
|
||||
const target = paintedTilemap(state);
|
||||
return widgets && target ? widgets.layers.duplicateGlobal(paintedLayerId(state, target.asset)) : null;
|
||||
},
|
||||
focusAt: () => {
|
||||
const button = currentTilemapWidgets()?.focus;
|
||||
return button ? { ...centreOf(button), label: button.text } : null;
|
||||
},
|
||||
/** One row of the layer list, top-down as drawn: the layer, where to press it, and its eye. */
|
||||
layerRowAt: (index: number) => {
|
||||
const list = currentTilemapWidgets()?.layers;
|
||||
const id = list?.shownIds[index];
|
||||
// On the name: the middle of the row may be the Z field, which a press would scrub.
|
||||
const name = id ? list.nameGlobal(id) : null;
|
||||
if (!list || !id || !name) return null;
|
||||
// `y` is the row's own line, so a check can tell whether it is inside the panel.
|
||||
return { id, y: name.y, name, visible: list.eyeGlobal(id) };
|
||||
},
|
||||
/** The cross on the painted layer's row; `enabled` is false where the row has none (the last layer). */
|
||||
removeLayerAt: () => {
|
||||
const widgets = currentTilemapWidgets();
|
||||
const target = paintedTilemap(state);
|
||||
if (!widgets || !target) return null;
|
||||
const layerId = paintedLayerId(state, target.asset);
|
||||
const at = widgets.layers.removeGlobal(layerId);
|
||||
const row = widgets.layers.rowGlobal(layerId);
|
||||
return at ? { ...at, enabled: true } : row ? { x: row.x, y: row.y, enabled: false } : null;
|
||||
},
|
||||
/** Where a cell sits on screen – for a click that paints it or a pixel that proves it. */
|
||||
cellAt: (objectId: string, x: number, y: number) => viewport.cellScreen(objectId, x, y),
|
||||
gameCellAt: (objectId: string, x: number, y: number) => gameView.cellScreen(objectId, x, y),
|
||||
},
|
||||
container: {
|
||||
current: () => containerEditor.currentAssetId,
|
||||
center: () => centreOf(containerEditor),
|
||||
rect: () => containerEditor.canvasRect(),
|
||||
/** Where a template object is drawn, so a check can click or sample it. */
|
||||
objectScreen: (id: string) => containerEditor.objectScreen(id),
|
||||
dropPreview: () => containerEditor.dropInfo(),
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user