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:
@@ -1,47 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { Node } from "../../domain/phy";
|
||||
import { localBounds } from "./logic";
|
||||
defineOptions({ name: "NodeSvg" });
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
nodes: Node[];
|
||||
selected: string[];
|
||||
variants: Record<string, string>;
|
||||
cut: string[];
|
||||
dim: boolean;
|
||||
zoom: number;
|
||||
angle: number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
down: [e: PointerEvent, n: Node, el: SVGGElement];
|
||||
badge: [g: Node, e: PointerEvent];
|
||||
focus: [id: string];
|
||||
}>();
|
||||
function bounds(n: Node) {
|
||||
return n.type === "cluster"
|
||||
? localBounds(n.children, props.variants)
|
||||
: n.type === "variantGroup"
|
||||
? localBounds(
|
||||
n.children.find((v) => v.id === (props.variants[n.id] ?? n.default))?.children ?? [],
|
||||
props.variants,
|
||||
)
|
||||
: { x: 0, y: 0, width: 0, height: 0 };
|
||||
}
|
||||
function badgeBox(n: Node) {
|
||||
const b = bounds(n);
|
||||
const size = 20 / props.zoom;
|
||||
return {
|
||||
x: b.x + b.width + 4 / props.zoom,
|
||||
y: b.y - size - 4 / props.zoom,
|
||||
size,
|
||||
angle: -(props.angle + n.transform.rotation),
|
||||
};
|
||||
}
|
||||
/** バッジは名前を書かず、表示中のバリアント名はツールチップで示す。T-30。 */
|
||||
function badgeTitle(n: Extract<Node, { type: "variantGroup" }>) {
|
||||
const shown = props.variants[n.id] || n.default;
|
||||
const name = n.children.find((v) => v.id === shown)?.name ?? "";
|
||||
return `${n.name}: ${name}${shown === n.default ? "(既定)" : ""}`;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<g
|
||||
@@ -111,10 +81,7 @@ function badgeTitle(n: Extract<Node, { type: "variantGroup" }>) {
|
||||
:variants="variants"
|
||||
:cut="cut"
|
||||
:dim="dim"
|
||||
:zoom="zoom"
|
||||
:angle="angle + n.transform.rotation"
|
||||
@down="(e, c, el) => emit('down', e, c, el)"
|
||||
@badge="(g, e) => emit('badge', g, e)"
|
||||
@focus="(id) => emit('focus', id)"
|
||||
/><text x="0" y="-.12" class="group-label">{{ n.name }}</text></g
|
||||
>
|
||||
@@ -127,30 +94,9 @@ function badgeTitle(n: Extract<Node, { type: "variantGroup" }>) {
|
||||
:variants="variants"
|
||||
:cut="cut"
|
||||
:dim="dim"
|
||||
:zoom="zoom"
|
||||
:angle="angle + n.transform.rotation"
|
||||
@down="(e, c, el) => emit('down', e, c, el)"
|
||||
@badge="(g, e) => emit('badge', g, e)"
|
||||
@focus="(id) => emit('focus', id)"
|
||||
/><g
|
||||
:transform="`rotate(${badgeBox(n).angle} ${badgeBox(n).x} ${badgeBox(n).y})`"
|
||||
class="badge"
|
||||
@click.stop="emit('badge', n, $event)"
|
||||
><rect
|
||||
:x="badgeBox(n).x"
|
||||
:y="badgeBox(n).y"
|
||||
:width="badgeBox(n).size"
|
||||
:height="badgeBox(n).size"
|
||||
:rx="3 / zoom"
|
||||
@click.stop="emit('badge', n, $event)"
|
||||
/><text
|
||||
:x="badgeBox(n).x + badgeBox(n).size / 2"
|
||||
:y="badgeBox(n).y + badgeBox(n).size / 2"
|
||||
:style="{ fontSize: `${12 / zoom}px` }"
|
||||
>⇄</text
|
||||
><title>{{ badgeTitle(n) }}</title></g
|
||||
></g
|
||||
>
|
||||
/></g>
|
||||
</g>
|
||||
</template>
|
||||
<style scoped>
|
||||
@@ -205,21 +151,4 @@ function badgeTitle(n: Extract<Node, { type: "variantGroup" }>) {
|
||||
.group-label {
|
||||
fill: #222;
|
||||
}
|
||||
.badge {
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
.badge rect {
|
||||
fill: #fff3c2;
|
||||
stroke: #8a691b;
|
||||
stroke-width: 1px;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
.badge text {
|
||||
font-size: 12px;
|
||||
fill: #302709;
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user