Files
Aislo/B05_Profile/B05_Profile_UI_RouteEdit_Footprint.ts
T

94 lines
3.3 KiB
TypeScript

/* =============================================================================
* 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 "./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<FootprintStrokeKind, [string, string]> = {
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();
}