Files
Aislo/B05_Profile/B05_Profile_UI_RouteEdit_Footprint.ts
T
eomsangdonandClaude Opus 5.5 29bf8c998c feat(381-2 B05 평면): [측점] [기울기] 켬끔 단추(절/성토선 왼쪽 · 기본 켬 · 페이지 동안)
- 새 _Marks 켬끔 모듈 · 다시 그림은 _Footprint scheduleRedraw 공유
- [측점] 끔 = 눈금 · 번호 · 눈금 집기 없음 · 시점 · 종점 이름표 남김
- [기울기] 끔 = 기울기 라벨 · 높이 요청 없음
- 시험 test_381_2_routeedit_marks · 도구줄 차례 시험에 두 단추

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
2026-10-17 14:44:40 +09:00

94 lines
3.5 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 "./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";
/** 갈래 → 색 토큰 · 대체 색 · 굵기(px · 비탈 끝이 더 굵음 · 요구 241-10). */
const STROKE_STYLE: Record<FootprintStrokeKind, [string, string, number]> = {
road: ["--b05-footprint-road", "#475569", 1.8],
cut: ["--b05-footprint-cut", "#e11d48", 2.6],
fill: ["--b05-footprint-fill", "#0d9488", 2.6],
slope: ["--b05-footprint-road", "#475569", 2.6],
};
let shown = true;
let redraw: (() => void) | null = null;
let frame = 0;
let listening: { projectId: string; stop: () => void } | null = null;
/** 마지막 장면을 다음 프레임에 한 번 다시 그림 — [측점] · [기울기] 켬끔(`_Marks`)도 씀. */
export 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([]);
for (const { kind, points } of strokes) {
const [token, fallback, width] = STROKE_STYLE[kind];
context.lineWidth = width;
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();
}