mock: 選択中のクラスターにだけ原点の丸を出す(T-30 単位5)
単位1の選択の描画層に、選択中のクラスターとバリアントグループの原点の丸を描く。 長押しの候補になっているだけのクラスターには出さない。丸の大きさは画面上で固定する。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NznQtmVPoQAnietqwr1auL
This commit is contained in:
@@ -115,6 +115,23 @@ test("ドラッグ中に右端で自動スクロールし、Undo 1回で戻る",
|
||||
await expect(key).toHaveAttribute("transform", before!);
|
||||
});
|
||||
|
||||
test("選択中のクラスターにだけ原点の丸を出す", async ({ page }) => {
|
||||
const keys = page.locator('[data-node-type="key"]');
|
||||
const corner = (await keys.nth(0).locator(".key-face").boundingBox())!;
|
||||
const cluster = await makeCluster(page);
|
||||
const clusterId = await cluster.getAttribute("data-node-id");
|
||||
await keys.nth(5).click();
|
||||
await expect(page.locator(".origin")).toHaveCount(0);
|
||||
await page.locator(`[data-tree-id="${clusterId}"]`).first().click();
|
||||
const origin = page.locator(`[data-origin-id="${clusterId}"]`);
|
||||
await expect(origin).toHaveCount(1);
|
||||
const box = (await origin.boundingBox())!;
|
||||
expect(Math.abs(box.x + box.width / 2 - corner.x)).toBeLessThan(2);
|
||||
expect(Math.abs(box.y + box.height / 2 - corner.y)).toBeLessThan(2);
|
||||
await keys.nth(5).click();
|
||||
await expect(page.locator(".origin")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("クラスター化で画面位置を保つ", async ({ page }) => {
|
||||
const keys = page.locator('[data-node-type="key"]');
|
||||
const centers = async () =>
|
||||
|
||||
@@ -399,6 +399,7 @@ function onFocus(_id: string) {
|
||||
:nodes="focus ? [node(focus)!] : project.phy.root"
|
||||
:selected="selected"
|
||||
:variants="variants"
|
||||
:zoom="zoom"
|
||||
:candidate="longLabel ? (holdCandidates[holdIndex] ?? null) : null"
|
||||
:parent="focus ? focusParentMatrix(focus) : undefined"
|
||||
/>
|
||||
|
||||
@@ -8,6 +8,7 @@ const props = withDefaults(
|
||||
nodes: Node[];
|
||||
selected: string[];
|
||||
variants: Record<string, string>;
|
||||
zoom: number;
|
||||
candidate?: string | null;
|
||||
parent?: Affine;
|
||||
}>(),
|
||||
@@ -16,6 +17,8 @@ const props = withDefaults(
|
||||
const frames = computed(() =>
|
||||
selectionFrames(props.nodes, props.selected, props.variants, props.candidate, props.parent),
|
||||
);
|
||||
// 原点の丸は選択中のクラスター(VariantGroupを含む)にだけ出す。T-30。
|
||||
const origins = computed(() => frames.value.filter((frame) => frame.selected && frame.type !== "key"));
|
||||
const affineText = (m: Affine) => `matrix(${m.a} ${m.b} ${m.c} ${m.d} ${m.e} ${m.f})`;
|
||||
</script>
|
||||
<template>
|
||||
@@ -31,6 +34,15 @@ const affineText = (m: Affine) => `matrix(${m.a} ${m.b} ${m.c} ${m.d} ${m.e} ${m
|
||||
:width="frame.box.width"
|
||||
:height="frame.box.height"
|
||||
/>
|
||||
<circle
|
||||
v-for="frame in origins"
|
||||
:key="`origin-${frame.id}`"
|
||||
:data-origin-id="frame.id"
|
||||
class="origin"
|
||||
:cx="frame.transform.e"
|
||||
:cy="frame.transform.f"
|
||||
:r="5 / zoom"
|
||||
/>
|
||||
</g>
|
||||
</template>
|
||||
<style scoped>
|
||||
@@ -48,4 +60,10 @@ const affineText = (m: Affine) => `matrix(${m.a} ${m.b} ${m.c} ${m.d} ${m.e} ${m
|
||||
stroke: #e87912;
|
||||
stroke-width: 3.5px;
|
||||
}
|
||||
.origin {
|
||||
fill: #fff;
|
||||
stroke: #1769d2;
|
||||
stroke-width: 2px;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -235,6 +235,7 @@ export type SelectionFrame = {
|
||||
type: Node["type"];
|
||||
transform: Affine;
|
||||
box: Box;
|
||||
selected: boolean;
|
||||
candidate: boolean;
|
||||
};
|
||||
/** 可視ノードの選択枠をワールド行列とローカル矩形で返す。 */
|
||||
@@ -255,6 +256,7 @@ export function selectionFrames(
|
||||
type: node.type,
|
||||
transform,
|
||||
box: { x: 0, y: 0, width: node.size.w, height: node.size.h },
|
||||
selected: selected.includes(node.id),
|
||||
candidate: candidate === node.id,
|
||||
});
|
||||
} else if (node.type === "cluster") {
|
||||
@@ -264,6 +266,7 @@ export function selectionFrames(
|
||||
type: node.type,
|
||||
transform,
|
||||
box: localBounds(node.children, variants),
|
||||
selected: selected.includes(node.id),
|
||||
candidate: candidate === node.id,
|
||||
});
|
||||
visit(node.children, transform);
|
||||
@@ -275,6 +278,7 @@ export function selectionFrames(
|
||||
type: node.type,
|
||||
transform,
|
||||
box: localBounds(variant?.children ?? [], variants),
|
||||
selected: selected.includes(node.id),
|
||||
candidate: candidate === node.id,
|
||||
});
|
||||
if (variant) visit(variant.children, transform);
|
||||
|
||||
@@ -65,6 +65,9 @@ describe("PHY pure interaction rules", () => {
|
||||
expect(frame.transform.f).toBeCloseTo(27);
|
||||
expect(frame.transform.a).toBeCloseTo(Math.cos(Math.PI / 6));
|
||||
expect(frame.transform.b).toBeCloseTo(Math.sin(Math.PI / 6));
|
||||
expect(frame.selected).toBe(true);
|
||||
const candidateOnly = selectionFrames(roots, [], {}, "target")[0]!;
|
||||
expect([candidateOnly.selected, candidateOnly.candidate]).toEqual([false, true]);
|
||||
});
|
||||
it("removes ancestors and descendants from mixed selections", () => {
|
||||
expect(normalizeSelection(phy, ["c", "k"])).toEqual(["k"]);
|
||||
|
||||
Reference in New Issue
Block a user