mock: バリアントのバッジをノードより後に描き、キーの下に隠れないようにする

バッジは NodeSvg の再帰の中で各バリアントグループの <g> に描いていたため、
後から描かれる兄弟のキーに隠れていた。選択の描画層の後に PhyBadgeLayer を置き、
位置は純粋関数 variantBadges でワールド座標に直して正立で描く。
フォーカス中、フォーカス外のバッジは薄く描き、クリックを受けない。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NznQtmVPoQAnietqwr1auL
This commit is contained in:
2026-10-22 14:24:49 +09:00
co-authored by Claude Opus 5.5
parent 812e2edfc2
commit 0697e7a3b4
6 changed files with 157 additions and 85 deletions
+31
View File
@@ -288,6 +288,37 @@ export function selectionFrames(
visit(roots, parent);
return frames;
}
export type VariantBadge = {
group: Extract<Node, { type: "variantGroup" }>;
x: number;
y: number;
size: number;
};
/** 表示中のVariantGroupのバッジを、OBBの右上に正立で置く。大きさは画面上で固定する。 */
export function variantBadges(
roots: Node[],
variants: Record<string, string>,
zoom: number,
parent: Affine = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 },
): VariantBadge[] {
const badges: VariantBadge[] = [];
const size = 20 / zoom;
const visit = (nodes: Node[], ancestor: Affine) => {
for (const node of nodes) {
const transform = compose(ancestor, matrix(node.transform));
if (node.type === "cluster") visit(node.children, transform);
else if (node.type === "variantGroup") {
const variant = node.children.find((item) => item.id === (variants[node.id] || node.default));
const box = localBounds(variant?.children ?? [], variants);
const corner = apply(transform, { x: box.x + box.width + 4 / zoom, y: box.y - size - 4 / zoom });
badges.push({ group: node, x: corner.x, y: corner.y, size });
if (variant) visit(variant.children, transform);
}
}
};
visit(roots, parent);
return badges;
}
/** ワールド位置を貼り先の親ローカルへ戻し、親軸で1刻みずらす。T-11/T-27。 */
export function pasteLocalTransform(sourceWorld: Affine, parentWorld: Affine, step: number): Transform {
const local = compose(inverse(parentWorld), sourceWorld);