From c4322f620c88260cde4231dcc9ac2475d3108784 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 22 Aug 2026 14:42:06 +0900 Subject: [PATCH] =?UTF-8?q?feat(B06):=20=EC=A1=B0=EC=A0=95=EC=B0=BD=20?= =?UTF-8?q?=ED=95=AD=EB=AA=A9=202=ED=96=89=20=EA=B5=AC=EC=A1=B0=20+=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EB=B2=84=ED=8A=BC=20=EC=8B=AD=EC=9E=90(D-?= =?UTF-8?q?pad)=20=EB=B0=B0=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 항목별 1행 = 이름 라벨, 2행 = 값 조작(구조물 형식·재질·높이·단 수·이동) - 이동 버튼 십자 배치: ▲ 위 / ◀ ↺ ▶ 가운데 / ▼ 아래 — 중앙이 초기화 - __controls(flex)와의 우선순위 충돌은 복합 선택자로 해결 Co-Authored-By: Claude Opus 5 (1M context) --- .../B06_Section_UI_Cross_Structure_Panel.ts | 103 +++++++++++------- B06_Section/B06_Section_UI_Style_Cross.css | 49 ++++++++- ui_template/ui_template_locale_b2.ts | 1 + 3 files changed, 109 insertions(+), 44 deletions(-) diff --git a/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts b/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts index ff59df22..cb9b2d4b 100644 --- a/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts +++ b/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts @@ -77,6 +77,19 @@ function makeButton(label: string, title: string, onClick: () => void): HTMLButt return button; } +/** 항목 행 — 1행 이름 라벨 + 2행 값 조작(2026-08-22 사용자 확정 2행 구조). */ +function makeRow(labelText: string): { row: HTMLElement; controls: HTMLElement } { + const row = document.createElement("div"); + row.className = "b06-structure-panel__struct"; + const label = document.createElement("span"); + label.className = "b06-structure-panel__label"; + label.textContent = labelText; + const controls = document.createElement("div"); + controls.className = "b06-structure-panel__controls"; + row.append(label, controls); + return { row, controls }; +} + /** 구조물 조정 오버레이 창을 만든다. 처음에는 숨어 있다. */ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHandle { const root = document.createElement("div"); @@ -96,41 +109,57 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan if (current) run(current); }; - const buttons = document.createElement("div"); - buttons.className = "b06-structure-panel__buttons"; + const moveRow = makeRow(L("B06_Cross_Move_Label")); + // 버튼 묶음 클래스는 컨트롤 행에 — **십자(D-pad) 배치**(2026-08-22 사용자): + // ▲ 위 / ◀ ↺ ▶ 가운데 / ▼ 아래. 초기화가 십자 중앙이다. + moveRow.controls.classList.add("b06-structure-panel__buttons"); + const buttons = moveRow.controls; + const dpad = ( + label: string, + slot: "up" | "down" | "left" | "right" | "reset", + title: string, + onClick: () => void, + ): HTMLButtonElement => { + const button = makeButton(label, title, onClick); + button.classList.add(`b06-structure-panel__btn--${slot}`); + return button; + }; buttons.append( - makeButton( - "◀", - L("B06_Cross_Revet_Left"), - act((key) => deps.nudge(key, STEP_M)), - ), - makeButton( - "▶", - L("B06_Cross_Revet_Right"), - act((key) => deps.nudge(key, -STEP_M)), - ), - makeButton( + dpad( "▲", + "up", L("B06_Cross_Revet_Up"), act((key) => deps.nudgeSlope(key, -STEP_M)), ), - makeButton( - "▼", - L("B06_Cross_Revet_Down"), - act((key) => deps.nudgeSlope(key, STEP_M)), + dpad( + "◀", + "left", + L("B06_Cross_Revet_Left"), + act((key) => deps.nudge(key, STEP_M)), ), - makeButton( + dpad( "↺", + "reset", L("B06_Cross_Revet_Reset"), act((key) => deps.reset(key)), ), + dpad( + "▶", + "right", + L("B06_Cross_Revet_Right"), + act((key) => deps.nudge(key, -STEP_M)), + ), + dpad( + "▼", + "down", + L("B06_Cross_Revet_Down"), + act((key) => deps.nudgeSlope(key, STEP_M)), + ), ); // 재질 행(2026-08-22 사용자 ④ — 높이 행 **위**, 폭 축소: 한계는 툴팁으로). - const materialRow = document.createElement("label"); - materialRow.className = "b06-structure-panel__struct"; - const materialLabelEl = document.createElement("span"); - materialLabelEl.textContent = L("B06_Cross_Mat_Label"); + const materialParts = makeRow(L("B06_Cross_Mat_Label")); + const materialRow = materialParts.row; const materialSelect = document.createElement("select"); materialSelect.className = "b06-structure-panel__select"; const MATERIAL_LABEL: Record = { @@ -148,13 +177,11 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan materialSelect.addEventListener("change", () => { if (current) deps.setMaterial(current, materialSelect.value as RevetMaterial); }); - materialRow.append(materialLabelEl, materialSelect); + materialParts.controls.append(materialSelect); // 높이 행 — ±0.1m, 한계는 재질이 정한다. - const heightRow = document.createElement("div"); - heightRow.className = "b06-structure-panel__struct"; - const heightLabel = document.createElement("span"); - heightLabel.textContent = L("B06_Cross_Height_Label"); + const heightParts = makeRow(L("B06_Cross_Height_Label")); + const heightRow = heightParts.row; const heightValue = document.createElement("span"); heightValue.className = "b06-structure-panel__hval"; const heightMinus = makeButton( @@ -167,13 +194,11 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan L("B06_Cross_Height_Plus"), act((key) => deps.nudgeHeight(key, HEIGHT_STEP_M)), ); - heightRow.append(heightLabel, heightMinus, heightValue, heightPlus); + heightParts.controls.append(heightMinus, heightValue, heightPlus); // 유입측 구조물 형식 드롭다운(2026-08-22 사용자) — 자동/기슭막이/집수정 I·ㄴ·ㄷ. - const structureRow = document.createElement("label"); - structureRow.className = "b06-structure-panel__struct"; - const structureLabel = document.createElement("span"); - structureLabel.textContent = L("B06_Cross_Struct_Label"); + const structureParts = makeRow(L("B06_Cross_Struct_Label")); + const structureRow = structureParts.row; const select = document.createElement("select"); select.className = "b06-structure-panel__select"; const OPTIONS: Array<[InletStructureChoice, string]> = [ @@ -192,13 +217,11 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan select.addEventListener("change", () => { deps.setStructure(select.value as InletStructureChoice); }); - structureRow.append(structureLabel, select); + structureParts.controls.append(select); // 다단 기슭막이 단 수(2026-08-22 사용자) — 숫자 입력 + +/- 병행(스피너는 CSS 제거). - const extraRow = document.createElement("label"); - extraRow.className = "b06-structure-panel__struct"; - const extraLabel = document.createElement("span"); - extraLabel.textContent = L("B06_Cross_Extra_Count"); + const extraParts = makeRow(L("B06_Cross_Extra_Count")); + const extraRow = extraParts.row; const countInput = document.createElement("input"); countInput.type = "number"; countInput.min = "0"; @@ -218,7 +241,7 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan const countPlus = makeButton("+", L("B06_Cross_Extra_Add"), () => deps.setExtraCount(clampCount(Number(countInput.value) + 1)), ); - extraRow.append(extraLabel, countMinus, countInput, countPlus); + extraParts.controls.append(countMinus, countInput, countPlus); const closeButton = makeButton("✕", L("B06_Cross_Revet_Close"), () => deps.close()); closeButton.classList.add("b06-structure-panel__close"); @@ -226,7 +249,7 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan const head = document.createElement("div"); head.className = "b06-structure-panel__head"; head.append(title, closeButton); - root.append(head, value, structureRow, materialRow, heightRow, extraRow, buttons); + root.append(head, value, structureRow, materialRow, heightRow, extraRow, moveRow.row); return { root, @@ -259,7 +282,7 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan } select.value = structure; } - buttons.classList.toggle("is-hidden", !movable); + moveRow.row.classList.toggle("is-hidden", !movable); materialRow.classList.toggle("is-hidden", !movable); heightRow.classList.toggle("is-hidden", !movable); if (movable) { diff --git a/B06_Section/B06_Section_UI_Style_Cross.css b/B06_Section/B06_Section_UI_Style_Cross.css index 18f76007..253ee9f8 100644 --- a/B06_Section/B06_Section_UI_Style_Cross.css +++ b/B06_Section/B06_Section_UI_Style_Cross.css @@ -601,11 +601,38 @@ font-size: var(--text-caption); } -.b06-structure-panel__buttons { - display: flex; +/* 이동 조작 = 십자(D-pad) 배치(2026-08-22 사용자) — 중앙이 초기화. + __controls(flex)보다 뒤에 선언된 것과 무관하게 이기도록 복합 선택자. */ +.b06-structure-panel__controls.b06-structure-panel__buttons { + display: grid; + grid-template-areas: + ". up ." + "left reset right" + ". down ."; + justify-content: start; gap: 2px; } +.b06-structure-panel__btn--up { + grid-area: up; +} + +.b06-structure-panel__btn--down { + grid-area: down; +} + +.b06-structure-panel__btn--left { + grid-area: left; +} + +.b06-structure-panel__btn--right { + grid-area: right; +} + +.b06-structure-panel__btn--reset { + grid-area: reset; +} + .b06-structure-panel__btn.is-hidden { display: none; } @@ -643,14 +670,28 @@ } /* 유입 구조물 형식 드롭다운(2026-08-22 사용자) — 라벨 + select 한 줄. */ +/* 항목 행 = 2행 구조(2026-08-22 사용자): 1행 이름 라벨, 2행 값 조작. */ .b06-structure-panel__struct { display: flex; - align-items: center; - gap: var(--spacing-8); + flex-direction: column; + align-items: stretch; + gap: 2px; color: var(--color-text-secondary); font-size: var(--text-caption); } +.b06-structure-panel__label { + color: var(--color-text-muted); + font-size: var(--text-caption); + line-height: 1.2; +} + +.b06-structure-panel__controls { + display: flex; + align-items: center; + gap: 2px; +} + .b06-structure-panel__struct.is-hidden { display: none; } diff --git a/ui_template/ui_template_locale_b2.ts b/ui_template/ui_template_locale_b2.ts index f5d95d92..978d7a90 100644 --- a/ui_template/ui_template_locale_b2.ts +++ b/ui_template/ui_template_locale_b2.ts @@ -265,6 +265,7 @@ export const ui_locales_b2 = { "Cannot move further down the slope", ], B06_Cross_Height_Label: ["높이", "Height"], + B06_Cross_Move_Label: ["이동(좌우·사면 상하)", "Move (lateral / along slope)"], B06_Cross_Height_Minus: ["높이 −0.1m", "Height −0.1m"], B06_Cross_Height_Plus: ["높이 +0.1m", "Height +0.1m"], B06_Cross_Height_Limit: [