- B06 중심점 칸 = 버튼 없음 · 「▲▼ = 종단 계획고 · 좌우 이동은 평면에서」 · ◀▶ 키 먹어 막음 - B06_Section_UI_Cross_Center = 판정 원 · 한 축 끌기(암 경계선)만 남김 - B05 노선 좌우 host(_RouteEdit_Shift) · 고름 계산(logic/B05_Profile_RouteShift) 지움 · 페이지 · 편집기 배선 한 줄씩 걷음 - 시험 356 = 가짜 DOM 으로 중심점 고름 · 버튼 없음 · 키 · 끌기 없음 확인 · 356_4 = 중심점 좌우 걸음 뺌 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
74 lines
3.3 KiB
TypeScript
74 lines
3.3 KiB
TypeScript
/* =============================================================================
|
|
* 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);
|
|
});
|
|
}
|