diff --git a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Draw.ts b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Draw.ts index b6a0d0916..ec52b41e3 100644 --- a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Draw.ts +++ b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Draw.ts @@ -360,18 +360,19 @@ export function createPlanDraw(params: PlanDrawParams): PlanDraw { else context.moveTo(sx, sy); }); context.stroke(); - drawStationTicks( - context, - line.vertices.map(([x, y]) => ({ x, y })), - { - intervalM: PLAN_STATION_INTERVAL_M, - pxPerMeter: map.pxPerMeter, - toScreen: (x, y) => map.toScreen([x, y]), - uprightRad: map.uprightRad, - chainages: stationChainages(lineLength(line.vertices), PLAN_STATION_INTERVAL_M), - }, - ); - segmentLabels(context, map, line.vertices); + if (map.stations) + drawStationTicks( + context, + line.vertices.map(([x, y]) => ({ x, y })), + { + intervalM: PLAN_STATION_INTERVAL_M, + pxPerMeter: map.pxPerMeter, + toScreen: (x, y) => map.toScreen([x, y]), + uprightRad: map.uprightRad, + chainages: stationChainages(lineLength(line.vertices), PLAN_STATION_INTERVAL_M), + }, + ); + if (map.slopes) segmentLabels(context, map, line.vertices); }); // 꺾임점 context.fillStyle = "#ffffff"; diff --git a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Layers.ts b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Layers.ts index d2eaa253d..6d695d82e 100644 --- a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Layers.ts +++ b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Layers.ts @@ -44,20 +44,18 @@ export type PlanLayerKey = "satellite" | "contour" | "stream" | "cadastral" | "r interface LayerDef { key: PlanLayerKey; - /** 아이콘 — 없으면 글자 단추(`glyph`). */ - icon?: IconName; - glyph?: string; + icon: IconName; title: string; on: boolean; } const LAYERS: readonly LayerDef[] = [ - { key: "satellite", glyph: "위성", title: "위성사진", on: true }, + { key: "satellite", icon: "satellite", title: "위성사진", on: true }, { key: "contour", icon: "mountain", title: "도엽 등고선(굵은 선 = 계곡선 25 m)", on: true }, - { key: "stream", glyph: "세류", title: "세류선(가는 파랑) · 하천(굵은 파랑)", on: true }, - { key: "cadastral", glyph: "지적", title: "지적도(참고용 · 법적 효력 없음)", on: false }, + { key: "stream", icon: "droplet", title: "세류선(가는 파랑) · 하천(굵은 파랑)", on: true }, + { key: "cadastral", icon: "borderAll", title: "지적도(참고용 · 법적 효력 없음)", on: false }, { key: "road", icon: "road", title: "기존 도로(도엽 도로중심선)", on: false }, - { key: "slope", glyph: "경사", title: "급경사 색(15° · 35° · 40° 이상)", on: false }, + { key: "slope", icon: "angle", title: "급경사 색(15° · 35° · 40° 이상)", on: false }, ]; const PREF_KEY = "aislo:pref:d03.layers"; /** 등고선을 받아 올 그은 선 둘레(m). */ @@ -128,7 +126,6 @@ export function createPlanLayers(opts: { for (const def of LAYERS) { const button = createIconButton({ icon: def.icon, - glyph: def.glyph, title: def.title, pressed: on[def.key], onClick: () => toggle(def.key), diff --git a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Map.ts b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Map.ts index 3fb0d9adf..a4d0de066 100644 --- a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Map.ts +++ b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Map.ts @@ -1,7 +1,7 @@ /* ============================================================================= * D03_PlanAnalysis_UI_Map.ts - * D03 메인 평면(PLAN 530-3 · 562-3) — 위성 바탕 · 위에 겹(바탕 겹 569-2 · 그리기 · 거리재기) · 도구줄 여섯 - * (되돌리기 · 다시하기 · 3D · 좌회전 · 우회전 · 거리재기). + * D03 메인 평면(PLAN 530-3 · 562-3) — 위성 바탕 · 위에 겹(바탕 겹 569-2 · 그리기 · 거리재기) · 도구줄 아홉 + * (공용 `createMapTools` 568-3 — [?] · 되돌리기 · 다시하기 · 3D · 좌회전 · 우회전 · 측점 · 기울기 · 거리재기). * * 셈 · 그리기는 B 부품 그대로(import 만) — 좌표 `createMetricProjector` · `screenToMetric` · 맞춤 * `computeRouteView` · 휠 확대 · 가운데 단추 옮기기 `bindRouteEditNavigation` · @@ -10,9 +10,8 @@ * 겹은 돌리기 전 그림 좌표로 그리고 받음 — 누른 자리는 `unrotate` 로 되돌려 넘김. * ========================================================================== */ -import { createIconButton } from "@ui/ui_template_button"; +import { createMapTools, mapToolButton } from "@ui/ui_template_map_tools"; import { themeColor } from "@ui/ui_template_palette"; -import { createToolbar } from "@ui/ui_template_toolbar"; import type { VWorldMeta } from "../B04_PreProcess/B04_PreProcess_Api_Fetch"; import { computeMapRect, @@ -56,6 +55,9 @@ export interface ScreenMap { pxPerMeter: number; /** B04 그리기 틀(`drawPreparedLayer` 등이 받음). */ view: ViewState; + /** 측점 눈금 · 구간 길이 · 기울기 글 켬(도구줄 측점 · 기울기). */ + stations: boolean; + slopes: boolean; /** 글자를 세울 각(라디안 · 안 돌렸으면 0). */ uprightRad: number; /** (x, y) 둘레로 글자를 눈높이로 세워 그림(돌린 지도). */ @@ -129,36 +131,44 @@ export function createPlanMap(events: PlanMapEvents): PlanMap { const root = document.createElement("div"); root.className = "d03-plan__map"; const canvas = document.createElement("canvas"); - const tool = ( - act: string, - icon: Parameters[0]["icon"], - title: string, - onClick?: () => void, - ): HTMLButtonElement => { - const button = createIconButton({ icon, title, onClick }); - button.dataset.act = act; + let marks = { stations: true, slopes: true }; + const markButton = (act: "stations" | "slopes", title: string): HTMLButtonElement => { + const button = mapToolButton(act, { + title, + pressed: true, + onClick: () => { + marks = { ...marks, [act]: !marks[act] }; + button.setAttribute("aria-pressed", String(marks[act])); + draw(); + }, + }); return button; }; - const undoButton = tool("undo", "undo", "되돌리기(Ctrl+Z)", events.onUndo); - const redoButton = tool("redo", "redo", "다시하기(Ctrl+Y)", events.onRedo); - const viewButton = tool("view3d", "cube", "3D 보기", events.on3d); - const measureButton = tool("measure", "ruler", "거리재기", events.onMeasure); - measureButton.setAttribute("aria-pressed", "false"); - const toolbar = createToolbar({ - glass: true, - size: 32, + const undoButton = mapToolButton("undo", { onClick: events.onUndo }); + const redoButton = mapToolButton("redo", { onClick: events.onRedo }); + const viewButton = mapToolButton("view3d", { onClick: events.on3d }); + const measureButton = mapToolButton("measure", { onClick: events.onMeasure }); + const tools = createMapTools({ label: "평면 도구", - items: [ - undoButton, - redoButton, - "divider", - viewButton, - tool("rotate-ccw", "rotateCcw", "왼쪽으로 돌림(15°)"), - tool("rotate-cw", "rotateCw", "오른쪽으로 돌림(15°)"), - "divider", - measureButton, + hint: { + lines: [ + "누름 = 점 찍기", + "Enter = 긋기 끝", + "Esc = 긋기 취소", + "Backspace = 마지막 점 빼기", + "오른쪽 누름 = 마크 지움", + "가운데 끌기 = 옮기기", + "휠 = 확대 · 축소", + "Ctrl+휠 = 돌리기", + ], + }, + groups: [ + [undoButton, redoButton], + [viewButton, "rotate-ccw", "rotate-cw"], + [markButton("stations", "측점"), markButton("slopes", "구간 길이 · 기울기"), measureButton], ], }); + const toolbar = tools.root; toolbar.classList.add("d03-plan__tools"); root.append(canvas, toolbar); const context = canvas.getContext("2d")!; @@ -187,6 +197,7 @@ export function createPlanMap(events: PlanMapEvents): PlanMap { toMetric: (px, py) => screenToMetric(meta!, view, px, py), pxPerMeter: projector.pxPerMeter, view, + ...marks, uprightRad: rotation.uprightRad(), upright: (ctx, x, y, paint) => { const angle = rotation.uprightRad(); diff --git a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css index 1df8f3989..950185186 100644 --- a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css +++ b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css @@ -29,6 +29,7 @@ position: absolute; top: var(--spacing-8); right: var(--spacing-8); + align-items: flex-end; } /* 569-2 바탕 겹 단추 — 왼쪽 위 · 급경사 범례 — 왼쪽 아래 */ diff --git a/resources/tester/test_553_4_de_parts.py b/resources/tester/test_553_4_de_parts.py index 89db18e28..667385454 100644 --- a/resources/tester/test_553_4_de_parts.py +++ b/resources/tester/test_553_4_de_parts.py @@ -33,13 +33,34 @@ def test_d03_parts(): css = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css") draw = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Draw.ts") tools = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Map.ts") + layers = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Layers.ts") assert 'createElement("progress")' not in ts and "createProgressBar()" in ts # 562-3 — 날 단추 · 입력 · 고르기 없음 · 도구줄 · 제약 칸 · 연도 고름은 부품 - for src in (ts, draw, tools): + for src in (ts, draw, tools, layers): for raw in ('createElement("button")', 'createElement("input")', 'createElement("select")'): assert raw not in src, raw - assert "createToolbar({" in tools and "createIconButton(" in tools - assert '"rotateCcw"' in tools and '"rotateCw"' in tools and "leftPan" not in tools + # 568-3 — 도구줄 = 공용 지도 도구줄 · 아홉([?] · 되돌리기 · 다시하기 · 3D · 돌리기 둘 · 측점 · 기울기 · 재기) + assert "createMapTools({" in tools and "hint:" in tools and "leftPan" not in tools + assert "createToolbar(" not in tools and "createIconButton(" not in tools + for act in ("undo", "redo", "view3d", "measure", "stations", "slopes"): + assert f'mapToolButton("{act}"' in tools or f'markButton("{act}"' in tools, act + assert '"rotate-ccw", "rotate-cw"' in tools + for b05_only in ("footprint", "station-snap", "contour-minor"): + assert f'"{b05_only}"' not in tools, b05_only + assert "map.stations" in draw and "map.slopes" in draw + # 569-2 — 바탕 겹 단추(왼쪽 위 아이콘 줄) · 처음 켬 셋 · 취향 기억 + assert "createToolbar({" in layers and "createIconButton({" in layers + assert '"aislo:pref:d03.layers"' in layers and "writeByKey(PREF_KEY" in layers + keys = re.findall(r'\{ key: "(\w+)", icon: "\w+", title: "[^"]+", on: (true|false) \}', layers) + assert keys == [ + ("satellite", "true"), + ("contour", "true"), + ("stream", "true"), + ("cadastral", "false"), + ("road", "false"), + ("slope", "false"), + ], keys + assert ".d03-plan__layers" in css and ".d03-plan__legend" in css assert "createSegment({" in draw and draw.count("createInputField(") == 2 assert "createSelectField(" in ts # 조각 연도 assert 'createElement("select")' not in three and "createSelectField" in three