game-maker / suite (push) Successful in 9m49s
Behavior and Container draw an icon because isSymbolType() is true for them, but that predicate does not mean "draws an icon" — it means "is editor decoration". Four places read it that way: collectWorldItems, GameViewport. collectFrame and syncDrawOrder all drop symbols out of the running world and the game picture, and placementOf() gives them an upright unscaled box. Listing 'particles' there would have handed the source an icon and taken away its particles, in the scene view as well as in the game. So the two questions are two functions now. hasSceneIcon() says a type draws an icon; isSymbolType() keeps meaning decoration. The icon itself is a SceneViewport decoration, which is editor-only for free because GameViewport never syncs decorations. A ring (r 10, #c26a1f) with four sparks, upright and unscaled like the other symbols, its middle left open — unlike Behavior and Container a source draws something of its own, and the field is densest exactly there. The name sits beside it and a dashed line runs to the parent, same colour. Found while writing the tests: the source's grab box scaled with its transform while the icon did not, so the two came apart. placementOf() returns only the translation for 'particles', making the box the upright 24 x 24 that covers its icon. The source's own transform still feeds every particle untouched. 824 checks (821 + 3). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
157 lines
4.7 KiB
TypeScript
157 lines
4.7 KiB
TypeScript
/** Design tokens for the canvas UI. Sizes only one widget uses may stay local to it. */
|
|
import { TextStyle } from 'pixi.js';
|
|
|
|
export const Theme = {
|
|
font: 'Inter, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
|
monoFont: '"JetBrains Mono", Consolas, "Courier New", monospace',
|
|
fontSize: 13,
|
|
smallFontSize: 11,
|
|
viewportLabelSize: 10,
|
|
rowHeight: 26,
|
|
controlHeight: 24,
|
|
padding: 8,
|
|
gap: 6,
|
|
radius: 4,
|
|
fieldPadding: 6,
|
|
checkboxSize: 16,
|
|
swatchSize: 12,
|
|
scrollbarWidth: 6,
|
|
/** Horizontal indent per nesting level in tree lists. */
|
|
treeIndent: 14,
|
|
hoverCursor: 'default',
|
|
colors: {
|
|
background: 0x1b1d22,
|
|
panel: 0x24272e,
|
|
panelHeader: 0x2c3038,
|
|
border: 0x383c46,
|
|
control: 0x16181d,
|
|
controlHover: 0x1e2127,
|
|
controlActive: 0x16181d,
|
|
accent: 0x4c8dff,
|
|
accentText: 0xffffff,
|
|
text: 0xe6e8ee,
|
|
textMuted: 0x8a90a0,
|
|
selection: 0x2b4a80,
|
|
danger: 0xe0553f,
|
|
viewport: 0x121316,
|
|
gizmo: 0x4c8dff,
|
|
/** Behavior symbols and owner links. */
|
|
behavior: 0xb066ff,
|
|
/** Collider outlines: solid (blocking) and trigger (events only). */
|
|
collider: 0x4fd18b,
|
|
trigger: 0xf2c84b,
|
|
container: 0x3fb8c9,
|
|
/** Particle source icon and owner links; the same orange the graph gives the category. */
|
|
particles: 0xc26a1f,
|
|
marquee: 0x4c8dff,
|
|
/** A value deviating from what it follows: an override on a placed object, a non-default on an asset. */
|
|
override: 0xf2c84b,
|
|
/** Node graph: canvas, node chrome, per-category title colors, port colors, live highlight. */
|
|
graphBackground: 0x15171b,
|
|
node: 0x262a32,
|
|
nodeBorder: 0x3b404b,
|
|
nodeSelected: 0x4c8dff,
|
|
categoryEvent: 0xc0392b,
|
|
categoryFlow: 0x7f8c8d,
|
|
categoryData: 0x2e8b57,
|
|
categoryVariable: 0xb8860b,
|
|
categoryAction: 0x2f6fd6,
|
|
categoryState: 0x8e5ad6,
|
|
categoryShader: 0x1f8f8f,
|
|
categoryParticles: 0xc26a1f,
|
|
portExec: 0xffffff,
|
|
portNumber: 0x6fcf97,
|
|
portBoolean: 0xe57373,
|
|
portString: 0xe6a8ff,
|
|
portObject: 0x56ccf2,
|
|
portList: 0xf2a75c,
|
|
portStruct: 0xd4c25a,
|
|
portVec3: 0xff9ad5,
|
|
portAny: 0xbdbdbd,
|
|
/** Ring around the port a running edge drag would connect to. */
|
|
portTarget: 0xffffff,
|
|
hot: 0xffc857,
|
|
},
|
|
} as const;
|
|
|
|
const styleCache = new Map<string, TextStyle>();
|
|
|
|
export type TextStyleToken =
|
|
| 'body'
|
|
| 'bodyBold'
|
|
| 'muted'
|
|
| 'override'
|
|
| 'small'
|
|
| 'warning'
|
|
| 'live'
|
|
| 'title'
|
|
| 'mono'
|
|
| 'monoMuted'
|
|
| 'monoWarn'
|
|
| 'monoError'
|
|
| 'accent'
|
|
| 'viewportLabel'
|
|
| 'viewportLabelMuted';
|
|
|
|
/** Shared TextStyle instances so Pixi can cache glyph measurements per token. */
|
|
export function textStyle(token: TextStyleToken = 'body'): TextStyle {
|
|
let style = styleCache.get(token);
|
|
if (style) return style;
|
|
const base = { fontFamily: Theme.font, fontSize: Theme.fontSize, fill: Theme.colors.text };
|
|
switch (token) {
|
|
case 'body':
|
|
style = new TextStyle(base);
|
|
break;
|
|
case 'bodyBold':
|
|
style = new TextStyle({ ...base, fontWeight: '600' });
|
|
break;
|
|
case 'muted':
|
|
style = new TextStyle({ ...base, fill: Theme.colors.textMuted });
|
|
break;
|
|
case 'override':
|
|
style = new TextStyle({ ...base, fill: Theme.colors.override });
|
|
break;
|
|
case 'small':
|
|
style = new TextStyle({ ...base, fontSize: Theme.smallFontSize, fill: Theme.colors.textMuted });
|
|
break;
|
|
case 'warning':
|
|
style = new TextStyle({ ...base, fontSize: Theme.smallFontSize, fill: Theme.colors.danger });
|
|
break;
|
|
case 'live':
|
|
style = new TextStyle({ ...base, fontSize: Theme.smallFontSize, fill: Theme.colors.hot });
|
|
break;
|
|
case 'title':
|
|
style = new TextStyle({
|
|
...base,
|
|
fontSize: Theme.smallFontSize,
|
|
fontWeight: '700',
|
|
letterSpacing: 0.5,
|
|
fill: Theme.colors.textMuted,
|
|
});
|
|
break;
|
|
case 'mono':
|
|
style = new TextStyle({ ...base, fontFamily: Theme.monoFont });
|
|
break;
|
|
case 'monoMuted':
|
|
style = new TextStyle({ ...base, fontFamily: Theme.monoFont, fill: Theme.colors.textMuted });
|
|
break;
|
|
case 'monoWarn':
|
|
style = new TextStyle({ ...base, fontFamily: Theme.monoFont, fill: Theme.colors.override });
|
|
break;
|
|
case 'monoError':
|
|
style = new TextStyle({ ...base, fontFamily: Theme.monoFont, fill: Theme.colors.danger });
|
|
break;
|
|
case 'accent':
|
|
style = new TextStyle({ ...base, fill: Theme.colors.accentText, fontWeight: '600' });
|
|
break;
|
|
case 'viewportLabel':
|
|
style = new TextStyle({ ...base, fontSize: Theme.viewportLabelSize });
|
|
break;
|
|
case 'viewportLabelMuted':
|
|
style = new TextStyle({ ...base, fontSize: Theme.viewportLabelSize, fill: Theme.colors.textMuted });
|
|
break;
|
|
}
|
|
styleCache.set(token, style);
|
|
return style;
|
|
}
|