/* ============================================================================= * B06_Section_UI_Cross_Center.ts * 횡단 카드 중심점 고름(356-3) — 보라 선 고름(`_Cross_Design_Pick`)의 끝 조각(키 `center`). * * · 판정 원 = 중심 십자 자리 · 노면 판정보다 위(`stackPickTargets`) · 다른 고름과 같은 칸(하나만) * · 고르면 ▲▼ = 종단 계획고(지금 길 · 종단도 듣개) · 좌우는 평면에서만(424 · ◀▶ · 가로 끌기 없음) * · 한 축 끌기(`bindAxisDrag`)는 암 경계선 세로 끌기(370-3)가 씀 * ========================================================================== */ const SVG_NS = "http://www.w3.org/2000/svg"; /** 판정 원 클래스 — 고르기 차례가 보라 선 위로 올림. */ export const CENTER_HIT_CLASS = "b06-chart__center-hit"; /** 판정 원 반지름(px). */ const HIT_RADIUS_PX = 8; /** 끌기 걸음(m). */ const DRAG_STEP_M = 0.1; /** 판정 원(중심 십자 자리). */ export function centerHit(cx: number, cy: number): SVGCircleElement { const hit = document.createElementNS(SVG_NS, "circle"); hit.setAttribute("cx", String(cx)); hit.setAttribute("cy", String(cy)); hit.setAttribute("r", String(HIT_RADIUS_PX)); hit.setAttribute("class", CENTER_HIT_CLASS); return hit; } const snapStep = (valueM: number): number => Math.round(Math.round(valueM / DRAG_STEP_M) * DRAG_STEP_M * 10) / 10; /** * 고른 판정을 한 축으로 끌기(370-3 암 경계선 세로) — 고른 뒤에만(안 고른 것을 끌면 카드 * 팬 그대로) · 걸음 0.1 m · 끄는 동안 `preview` · 놓을 때 움직였으면 한 번 `send`(안 움직이면 `preview(0)`) · * 놓은 자리 click 은 한 번 막음(같은 것 다시 누름 = 풂으로 안 번짐). `pxPerM` = 그 축 화면 px / m(layer 좌표). */ export function bindAxisDrag(input: { hit: SVGElement; layer: SVGGraphicsElement; axis: "x" | "y"; pxPerM: number; active: () => boolean; preview: (deltaM: number) => void; send: (deltaM: number) => void; }): void { const { hit, layer, axis, pxPerM, active } = input; const at = (event: PointerEvent): number => (axis === "x" ? event.clientX : event.clientY); hit.addEventListener("pointerdown", (event) => { const ctm = layer.getScreenCTM(); if (event.button !== 0 || !active() || !ctm) return; event.preventDefault(); event.stopPropagation(); // 카드 팬으로 안 번짐 const start = at(event); const scale = (axis === "x" ? ctm.a : ctm.d) * pxPerM; let deltaM = 0; const move = (next: PointerEvent): void => { deltaM = snapStep((at(next) - start) / scale); input.preview(deltaM); }; const up = (): void => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); window.removeEventListener("pointercancel", up); if (!deltaM) return input.preview(0); const swallow = (click: Event): void => click.stopPropagation(); window.addEventListener("click", swallow, { capture: true, once: true }); setTimeout(() => window.removeEventListener("click", swallow, { capture: true }), 0); input.send(deltaM); }; window.addEventListener("pointermove", move); window.addEventListener("pointerup", up); window.addEventListener("pointercancel", up); }); }