Files
Aislo/B07_DesignDetail/openwebcad/src/inputController/input-field-draw.ts
T
eomsangdonandClaude Opus 5.5 309be22ce0 feat(CAD): 331-4 동적 입력 — 커서 옆 길이 · 각도 두 칸 · Tab 잠금
- 기준점 있는 점 단계에서만 Tab 가로챔(그 밖은 브라우저 초점 이동) · 숫자 넣고 Tab = 그 칸 잠금 + 다음 칸 · 빈 Tab = 칸만 바꿈
- 각도 칸 잠금 = 각도 고정 · 길이 잠근 채 Enter = 그 길이 · `100` Tab `30` Enter → (86.603, 50)
- 커서 옆 줄 `▸[길이 100 🔒] [각도 30_]` — 잠금 🔒 · 지금 칸 ▸ · 나머지는 기준점→커서 실측
- 한 점 · Esc · 새 명령이면 각도 고정 · 길이 잠금 모두 풂(releaseOnePoint)

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

111 lines
4.1 KiB
TypeScript

/**
* 커서 따라다니는 입력창 — 캔버스에 직접 그림(`input-controller.ts` draw 가 부름).
* 칸 · 글은 테마 토큰 색을 그대로 칠함(자동색 뒤집기 건너뜀) — 명령줄 후보 상자(`App.css`
* `.cad-command-suggestions`)와 같은 색.
*/
import { Point } from '@flatten-js/core';
import {
CANVAS_INPUT_FIELD_BACKGROUND_COLOR,
CANVAS_INPUT_FIELD_BORDER_COLOR,
CANVAS_INPUT_FIELD_FONT_SIZE,
CANVAS_INPUT_FIELD_HEIGHT,
CANVAS_INPUT_FIELD_INSTRUCTION_TEXT_COLOR,
CANVAS_INPUT_FIELD_MOUSE_OFFSET,
CANVAS_INPUT_FIELD_TEXT_COLOR,
CANVAS_INPUT_FIELD_WIDTH,
} from '../App.consts.ts';
import type { ScreenCanvasDrawController } from '../drawControllers/screenCanvas.drawController.ts';
import { themeColor } from '../theme.ts';
/** 입력창 색 — 밝음 = 흰 칸 + 테두리 · 어두움 = 짙은 보라 칸 · 글 = CAD 글 색 */
export function inputFieldColors(read: (name: string, fallback: string) => string = themeColor) {
return {
background: read('--cad-chrome-raised', CANVAS_INPUT_FIELD_BACKGROUND_COLOR),
border: read('--cad-line', CANVAS_INPUT_FIELD_BORDER_COLOR),
text: read('--cad-text', CANVAS_INPUT_FIELD_TEXT_COLOR),
dimText: read('--cad-text-dim', CANVAS_INPUT_FIELD_INSTRUCTION_TEXT_COLOR),
};
}
/** 글 좌우 여백(px) */
const INPUT_FIELD_PADDING = 4;
/**
* 입력 글 칸 + 그 아래 안내 · 좌표 · 명령 후보 줄 — 바탕 한 장(명령줄 후보 상자 꼴).
* 줄마다 높이 CANVAS_INPUT_FIELD_HEIGHT · 글 중심 = 줄 세로 가운데 · 폭 = max(최소 폭, 가장 긴 줄 + 여백).
*/
export function drawInputField(
drawController: ScreenCanvasDrawController,
input: string,
lines: string[]
): void {
const mouse = drawController.getScreenMouseLocation();
const colors = inputFieldColors();
const rowHeight = CANVAS_INPUT_FIELD_HEIGHT;
const fontSize = CANVAS_INPUT_FIELD_FONT_SIZE;
const left = mouse.x + CANVAS_INPUT_FIELD_MOUSE_OFFSET;
// 화면 좌표는 위가 + — 칸 위 변이 커서 아래 OFFSET · fillRectScreen 의 y 는 아래 변
const top = mouse.y - CANVAS_INPUT_FIELD_MOUSE_OFFSET;
const height = rowHeight * (1 + lines.length);
const textWidth = Math.max(
0,
...[input, ...lines].map((text) => drawController.measureTextScreen(text, fontSize))
);
const width = Math.max(CANVAS_INPUT_FIELD_WIDTH, Math.ceil(textWidth) + INPUT_FIELD_PADDING * 2);
drawController.fillRectScreen(
left - 1,
top - height - 1,
width + 2,
height + 2,
colors.border,
true
);
drawController.fillRectScreen(left, top - height, width, height, colors.background, true);
if (lines.length) {
// 입력 줄과 목록 사이 가는 줄
drawController.fillRectScreen(left, top - rowHeight, width, 1, colors.border, true);
}
const textOptions = { textAlign: 'left' as const, fontSize, rawColor: true };
const rowCenter = (row: number) => top - rowHeight * row - rowHeight / 2;
if (input) {
drawController.drawTextScreen(input, new Point(left + INPUT_FIELD_PADDING, rowCenter(0)), {
...textOptions,
textColor: colors.text,
});
}
lines.forEach((line, index) => {
drawController.drawTextScreen(
line,
new Point(left + INPUT_FIELD_PADDING, rowCenter(index + 1)),
{
...textOptions,
textColor: colors.dimText,
}
);
});
}
/**
* 동적 입력 두 칸 — `[길이 100 🔒] ▸[각도 30_]`. 잠근 칸은 🔒 · 지금 칸은 ▸ + 친 글 ·
* 나머지는 기준점→커서 실측(각도 = 동쪽 0° 반시계 +).
*/
export function dynamicInputLine(
base: Point,
mouse: Point,
field: 'length' | 'angle',
typing: string,
lockedLength: number | null,
lockedDegrees: number | null
): string {
const live = {
length: Math.hypot(mouse.x - base.x, mouse.y - base.y),
angle: ((Math.atan2(mouse.y - base.y, mouse.x - base.x) * 180) / Math.PI + 360) % 360,
};
const cell = (name: 'length' | 'angle', label: string, locked: number | null) => {
const active = field === name;
const value = active && typing ? `${typing}_` : (locked ?? Number(live[name].toFixed(2)));
return `${active ? '▸' : ' '}[${label} ${value}${locked !== null ? ' 🔒' : ''}]`;
};
return `${cell('length', '길이', lockedLength)} ${cell('angle', '각도', lockedDegrees)}`;
}