mock: スイッチ・スタビの十字をトグルで出し、スタビ候補を選べるようにする(T-30 単位8)

スタビ候補はキーの長辺の長さで引き、キーごとに複数選べる。選択は ProjectFile.ui.phy.stabilizers に持つ。
プロパティ欄に switchOffset の入力欄を置く。異形キーは候補なし。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017yoUu9Xd8dhwyQdCfKfaXG
This commit is contained in:
2026-10-23 22:13:07 +09:00
co-authored by Claude Opus 5.5
parent 2c388e3bdb
commit eacf8a1581
8 changed files with 416 additions and 3 deletions
+34 -2
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import type { ProjectFile } from "../../domain/project";
import type { Node } from "../../domain/phy";
import type { Dispatch } from "../types";
@@ -12,7 +12,18 @@ import PhyBadgeLayer from "./PhyBadgeLayer.vue";
import PhyToolbar from "./PhyToolbar.vue";
import PhyProperties from "./PhyProperties.vue";
import PhyHoldOverlay from "./PhyHoldOverlay.vue";
import { normalizeSelection, nextGrid, insertionContainer, structuralNodes, visibleNodeIds } from "./logic";
import PhyMountLayer from "./PhyMountLayer.vue";
import {
normalizeSelection,
nextGrid,
insertionContainer,
structuralNodes,
visibleNodeIds,
stabilizerSelections,
selectedStabilizers,
withStabilizers,
} from "./logic";
import { stabilizerCandidates } from "../../domain/stabilizers";
import { usePhyClipboard } from "./usePhyClipboard";
import { usePhyViewport } from "./usePhyViewport";
import { usePhyCanvasPointer } from "./usePhyCanvasPointer";
@@ -296,6 +307,15 @@ function openBadge(g: Node, e: PointerEvent | MouseEvent) {
const top = r.bottom + 150 < (paneRect?.bottom ?? Infinity) ? r.bottom + 4 : r.top - 150;
popoverPos.value = overlayPoint(left, top, 210, 150);
}
const showMounts = ref(false);
const stabilizerChoice = computed(() => {
const item = prop.value;
if (item?.type !== "key") return null;
return {
candidates: stabilizerCandidates(item.size),
selected: selectedStabilizers(stabilizerSelections(props.project), item),
};
});
function onFocus(_id: string) {
if (
pointer.priorSelection().length === 1 &&
@@ -334,6 +354,8 @@ function onFocus(_id: string) {
@focus="focus = $event"
:hold-mode="holdMode"
@hold-mode="holdMode = holdMode === 'loop' ? 'list' : 'loop'"
:show-mounts="showMounts"
@mounts="showMounts = !showMounts"
/>
<PhyHoldOverlay
v-if="hold.open.value"
@@ -394,6 +416,14 @@ function onFocus(_id: string) {
@down="down"
@focus="onFocus"
/>
<PhyMountLayer
v-if="showMounts"
:nodes="focus ? [node(focus)!] : project.phy.root"
:variants="variants"
:selections="stabilizerSelections(project)"
:zoom="zoom"
:parent="focus ? focusParentMatrix(focus) : undefined"
/>
<PhySelectionLayer
:nodes="focus ? [node(focus)!] : project.phy.root"
:selected="selected"
@@ -484,6 +514,8 @@ function onFocus(_id: string) {
@change-variant="chooseVariant"
@delete-node="(id) => askDelete(id)"
@add-variant="(groupId, name) => addVariantToGroup(groupId, name)"
:stabilizers="stabilizerChoice"
@stabilizers="(keyId, ids) => dispatch('スタビ位置', (p) => withStabilizers(p, keyId, ids))"
/>
</section>
</template>