feat(D03): 예정노선 구간 고르기 넓힘 · 속성 판 · 끌어 고르기 창/걸침 (PLAN 585-2 · 요구 445 A4 · A5)

- 고름 = 구간 묶음 + 넓이 — 선 누름 = 구간 · 다시 누름 = 선 → 같은 해 → 구간 · Shift+누름 = 더함 · 뺌
- 빈 곳 끌기 = 오른쪽으로 창(실선 · 다 든 구간) · 왼쪽으로 걸침(점선 · 걸친 구간까지) · 돌린 지도도 보이는 화면 기준
- 지도 위 속성 판(새 _UI_Props · 공용 떠 있는 판) — 넓이 · 연도(여러 구간 한 번에) · 구간 줄 · 방향 바꾸기 · 선 지움 · 마크 지움
- 좌측 구역 · 연도 = 해 줄 · 선 줄 · 구간 줄 누름 = 고르기(해 고침은 속성 판 한 곳) · 고른 선 위 끌기 = 꺾임점 더해 옮김
- Delete = 고른 마크 · 선 통째 고름이면 그 선 지움
- 시험: 585-2 새 지킴 시험(창 · 걸침 · 돌린 화면 · 여러 구간 연도)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 18:46:01 +09:00
co-authored by Claude Opus 5.5
parent f5645c256d
commit e6951b5c73
7 changed files with 734 additions and 201 deletions
+19 -9
View File
@@ -64,16 +64,20 @@ export interface ScreenMap {
uprightRad: number;
/** (x, y) 둘레로 글자를 눈높이로 세워 그림(돌린 지도). */
upright: (context: CanvasRenderingContext2D, x: number, y: number, paint: () => void) => void;
/** 그림 px(돌리기 전) ↔ 보이는 화면 px(585-2 끌어 고르기 상자). */
rerotate: (px: number, py: number) => [number, number];
unrotate: (px: number, py: number) => [number, number];
}
/** 평면 위 겹 — 그리기 · 누름(참 = 받음 · 0 왼쪽 · 2 오른쪽) · 마우스 곁(왼쪽 끌기 중도) · 놓음 · 나감. */
/** 평면 위 겹 — 그리기 · 누름(참 = 받음 · 0 왼쪽 · 2 오른쪽 · shift = Shift 누름) · 마우스 곁(왼쪽 끌기 중도) ·
* 놓음 · 나감. */
export interface PlanMapLayer {
/** 바탕 겹(아래 캔버스 · 마우스 움직임엔 안 다시 그림). */
base?: boolean;
draw: (context: CanvasRenderingContext2D, map: ScreenMap) => void;
press?: (px: number, py: number, map: ScreenMap, button: 0 | 2) => boolean;
press?: (px: number, py: number, map: ScreenMap, button: 0 | 2, shift: boolean) => boolean;
hover?: (px: number, py: number, map: ScreenMap) => void;
release?: () => void;
release?: (map: ScreenMap) => void;
leave?: () => void;
}
@@ -166,10 +170,13 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
"Enter = 긋기 끝",
"Esc = 긋기 취소",
"Backspace = 마지막 점 빼기",
"선 누름 = 고름 · 고른 선 위 누름 = 꺾임점 더함",
"선 누름 = 구간 고름 · 다시 누름 = 선 → 같은 해로 넓힘",
"Shift+누름 = 구간 더함 · 뺌",
"빈 곳 오른쪽으로 끌기 = 창 안 구간 · 왼쪽으로 = 걸친 구간까지",
"고른 선 위 끌기 = 꺾임점 더해 옮김",
"꺾임점 · 마크 끌기 = 옮김",
"오른쪽 누름 = 꺾임점 · 마크 지움",
"Delete = 고른 선 지움",
"Delete = 고른 선 · 마크 지움",
"가운데 끌기 = 옮기기",
"휠 = 확대 · 축소",
"Ctrl+휠 = 돌리기",
@@ -223,6 +230,8 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
paint();
ctx.restore();
},
rerotate: rotation.rerotate,
unrotate: rotation.unrotate,
};
};
@@ -309,14 +318,15 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
if (!meta || event.button !== 0) return;
const [px, py] = at(event);
const map = screenMap();
if (layers.some((layer) => layer.press?.(px, py, map, 0))) {
if (layers.some((layer) => layer.press?.(px, py, map, 0, event.shiftKey))) {
canvas.setPointerCapture(event.pointerId); // 끌기가 캔버스 밖으로 나가도 놓음을 받음
drawTop();
}
});
canvas.addEventListener("pointerup", (event) => {
if (event.button !== 0) return;
for (const layer of layers) layer.release?.();
if (event.button !== 0 || !meta) return;
const map = screenMap();
for (const layer of layers) layer.release?.(map);
drawTop();
});
canvas.addEventListener("pointermove", (event) => {
@@ -335,7 +345,7 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
if (!meta) return;
const [px, py] = at(event);
const map = screenMap();
if (layers.some((layer) => layer.press?.(px, py, map, 2))) drawTop();
if (layers.some((layer) => layer.press?.(px, py, map, 2, event.shiftKey))) drawTop();
});
bindRouteEditNavigation({
canvas,