Searching a list with For each needs the test result wired back into break on the left of the loop node. The wires then run across the node and the graph reads badly. Find … Match keeps the whole search flowing left to right, while the test in between stays a free graph. - Find takes exec and list and offers item and index. It sets them to each element in turn and has no exec output of its own. - Match reads its bool input match once per element. The first true value continues at found with item and index of the hit. No hit or an empty list continues at not found with an empty item and index -1. - The pair is derived from the wiring and stored nowhere. A Match belongs to the Find whose item or index its test is built from. Copy, paste and undo therefore need no extra code (findBlock.ts). - An unpaired Find or Match, two Matches on one Find and a Match fed by two Finds show a warning on the node. At run time the flow ends at an unpaired Find and the console says so. - Adding Find from the palette places its Match 480 px to the right in the same undo step. - The graph draws a quiet frame behind Find, Match and the nodes of the test. Literals feeding the test from outside stay outside the frame. - The test consists of data nodes only, like the break of For each. Tests: - ./check.sh: 184 files, 2694 tests, CHECK EXIT 0 - e2e GM_STEPS=1-6: ALL PASSED, 272 checks - New find-match.test.ts with 12 tests, 2 catalog cases, e2e 6z-v with 3 checks using a real mouse. - Counter-checks: ignoring the test fails 4 unit tests, no ambiguity check fails 2, framing everything fails 1. In the browser, no frame and no Match from the palette each fail their check. - Check total in README: 1517. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
import type { Graphics } from 'pixi.js';
|
|
import { blockOf, FIND } from '../../core/graph/findBlock';
|
|
import type { Graph } from '../../core/graph/Graph';
|
|
import { Theme } from '../../ui/Theme';
|
|
import { type Bounds, type Box, boundsOf } from '../framing';
|
|
|
|
/** Room between a block's frame and the nodes it holds. */
|
|
export const BLOCK_PAD = 14;
|
|
|
|
export interface BlockFrame {
|
|
find: string;
|
|
bounds: Bounds;
|
|
}
|
|
|
|
/** One frame per closed `Find` … `Match` block, around every node of it that is drawn. */
|
|
export function blockFrames(graph: Graph, boxOf: (id: string) => Box | null): BlockFrame[] {
|
|
const frames: BlockFrame[] = [];
|
|
for (const node of graph.nodes) {
|
|
if (node.type !== FIND) continue;
|
|
const boxes = (blockOf(graph, node) ?? []).map(boxOf).filter((b): b is Box => b !== null);
|
|
const b = boundsOf(boxes);
|
|
if (!b) continue;
|
|
frames.push({
|
|
find: node.id,
|
|
bounds: {
|
|
minX: b.minX - BLOCK_PAD,
|
|
minY: b.minY - BLOCK_PAD,
|
|
maxX: b.maxX + BLOCK_PAD,
|
|
maxY: b.maxY + BLOCK_PAD,
|
|
},
|
|
});
|
|
}
|
|
return frames;
|
|
}
|
|
|
|
export function drawBlockFrames(g: Graphics, frames: readonly BlockFrame[]): void {
|
|
const color = Theme.colors.categoryFlow;
|
|
g.clear();
|
|
for (const { bounds: b } of frames)
|
|
g.roundRect(b.minX, b.minY, b.maxX - b.minX, b.maxY - b.minY, 8)
|
|
.fill({ color, alpha: 0.1 })
|
|
.stroke({ width: 1, color, alpha: 0.6 });
|
|
}
|