/* ============================================================================= * B05_Profile_UI_RouteEdit_Footprint.ts * 노선 편집 평면에 **평면 발자국**(도로 끝 · 비탈 끝)을 그린다(PLAN 346-2). * * 값 = 가게 키 `footprint`(계산 묶음 footprint 단계가 씀) · 선 갈래 = `logic/_Footprint_Strokes`. * 가게 값이 바뀌거나 [발자국] 단추를 누르면 마지막 장면을 한 번 다시 그린다(프레임마다 한 번) — * 편집기 본체가 그릴 때마다 `drawFootprint` 가 다시 그릴 길을 새로 받아 둔다. * 켬끔은 이 페이지 동안만(기본 켬 · 계정 설정 아님). * ========================================================================== */ import { draftProjectId, useDesignStore } from "../A00_Common/design/design_store"; import type { Vertex } from "./logic/B05_Profile_UI_RouteEdit_Curve"; import { type FootprintStrokeKind, type FootprintValue, footprintStrokes, } from "./logic/B05_Profile_Footprint_Strokes"; import "./B05_Profile_UI_Style_RouteEdit_Footprint.css"; /** 갈래 → 색 토큰 · 대체 색. */ const STROKE_STYLE: Record = { road: ["--b05-footprint-road", "#475569"], cut: ["--b05-footprint-cut", "#dc2626"], fill: ["--b05-footprint-fill", "#2563eb"], slope: ["--b05-footprint-road", "#475569"], }; const STROKE_WIDTH = 1.4; let shown = true; let redraw: (() => void) | null = null; let frame = 0; let listening: { projectId: string; stop: () => void } | null = null; function scheduleRedraw(): void { if (frame) return; frame = requestAnimationFrame(() => { frame = 0; redraw?.(); }); } function listen(projectId: string): void { if (listening?.projectId === projectId) return; listening?.stop(); const stop = useDesignStore(projectId, null).subscribe("footprint", scheduleRedraw); listening = { projectId, stop }; } /** [발자국] 단추 — 누르면 켬끔 · 다시 그림. */ export function bindFootprintToggle(button: HTMLButtonElement): void { const paint = (): void => { button.classList.toggle("is-active", shown); button.setAttribute("aria-pressed", String(shown)); }; paint(); button.addEventListener("click", () => { shown = !shown; paint(); scheduleRedraw(); }); } /** 발자국을 그린다(회전 · 등고선 위 · 노선 아래 자리에서 부름). `again` = 장면을 다시 그리는 길. */ export function drawFootprint( context: CanvasRenderingContext2D, toScreen: (vertex: Vertex) => [number, number], style: CSSStyleDeclaration, again: () => void, ): void { redraw = again; const projectId = draftProjectId(); listen(projectId); if (!shown) return; const strokes = footprintStrokes( useDesignStore(projectId, null).get("footprint") as FootprintValue | null, ); if (!strokes.length) return; context.save(); context.setLineDash([]); context.lineWidth = STROKE_WIDTH; for (const { kind, points } of strokes) { const [token, fallback] = STROKE_STYLE[kind]; context.strokeStyle = style.getPropertyValue(token).trim() || fallback; context.beginPath(); points.forEach((vertex, index) => { const [x, y] = toScreen(vertex); if (index === 0) context.moveTo(x, y); else context.lineTo(x, y); }); context.stroke(); } context.restore(); }