diff --git a/src/editor/debug/uiHooks.ts b/src/editor/debug/uiHooks.ts index 65953314..339defff 100644 --- a/src/editor/debug/uiHooks.ts +++ b/src/editor/debug/uiHooks.ts @@ -64,8 +64,8 @@ export function uiHooks({ app, state, editor }: HookContext) { */ const overlayTexts = (): string[] => { const out: string[] = []; - eachText(ui().overlay, (node) => { - if (node.text) out.push(node.text); + eachText(ui().overlay, (text) => { + if (text) out.push(text); }); return out; }; diff --git a/src/editor/textProbe.ts b/src/editor/textProbe.ts index 6ec1c87f..ad518b19 100644 --- a/src/editor/textProbe.ts +++ b/src/editor/textProbe.ts @@ -1,5 +1,6 @@ import { type Application, type Container, Graphics, Rectangle, Text } from 'pixi.js'; import { Button, Widget } from '../ui'; +import { pictureText } from '../viewport/textPicture'; /** The global box of a widget: where it starts, and how large it is. */ export function rectOf(widget: { getGlobalPosition(): { x: number; y: number }; w: number; h: number }): { @@ -36,7 +37,7 @@ export interface IconHit { * it the sign-in screen needs, since it exists before there is an editor to ask. */ export function textProbe(app: Application): { - eachText: (root: Container, visit: (node: Text, point: { x: number; y: number }) => void) => void; + eachText: (root: Container, visit: (text: string, point: { x: number; y: number }) => void) => void; locateAll: (text: string) => { x: number; y: number }[]; locate: (text: string, last?: boolean) => { x: number; y: number } | null; locateIcons: (name: string) => IconHit[]; @@ -76,14 +77,19 @@ export function textProbe(app: Application): { }; walk(root, null, null); }; - const eachText = (root: Container, visit: (node: Text, point: { x: number; y: number }) => void): void => { + const eachText = ( + root: Container, + visit: (text: string, point: { x: number; y: number }) => void, + ): void => { eachShown(root, (node, clip) => { - if (!(node instanceof Text)) return false; + // A text object in the game is a picture of its glyphs (#169), not a Pixi `Text`. + const text = node instanceof Text ? node.text : pictureText(node); + if (text === undefined) return false; // Global bounds, not `width`/`height`: those are unscaled, and inside the graph canvas // (which is scaled by the zoom) that put the point right of the text it was meant to hit. const box = node.getBounds(); const point = { x: box.x + box.width / 2, y: box.y + box.height / 2 }; - if (!clip || clip.contains(point.x, point.y)) visit(node, point); + if (!clip || clip.contains(point.x, point.y)) visit(text, point); return true; }); }; @@ -105,8 +111,8 @@ export function textProbe(app: Application): { /** Centres of every visible text equal to `text`, in drawing order (the topmost last). */ const locateAll = (text: string): { x: number; y: number }[] => { const hits: { x: number; y: number }[] = []; - eachText(app.stage, (node, point) => { - if (node.text === text) hits.push(point); + eachText(app.stage, (shown, point) => { + if (shown === text) hits.push(point); }); return hits; }; diff --git a/src/viewport/textPicture.ts b/src/viewport/textPicture.ts index 1155464e..606e62fd 100644 --- a/src/viewport/textPicture.ts +++ b/src/viewport/textPicture.ts @@ -70,6 +70,17 @@ export function sharpCoverage( return { data, width: w, height: h }; } +/** + * The text each picture shows, by its mesh. The mesh is a sprite quad and carries no text, so the test + * hook reads a game text through here – otherwise `locate` finds only the editor's own labels. + */ +const shownTexts = new WeakMap(); + +/** The text a node on screen draws as a `TextPicture`, or undefined for any other node. */ +export function pictureText(node: object): string | undefined { + return shownTexts.get(node as Mesh); +} + export class TextPicture { readonly mesh: Mesh = newSpriteMesh(); private readonly style = new TextStyle({ fontFamily: Theme.font, fontSize: 32, fill: 0xffffff }); @@ -88,6 +99,7 @@ export class TextPicture { const color = Number(item.props.color ?? 0xffffff); const resolution = textResolution(scale, item.fontSmooth); const key = JSON.stringify([text, family, size, color, resolution]); + shownTexts.set(this.mesh, text); const stale = key !== this.rasterKey; const previous = stale ? this.texture : null; if (stale) {