fix(B07-CAD): 커서 입력창 칸 · 글 색이 테마를 따름 — 자동색 뒤집기 건너뜀(97-1)

- 칸 = --cad-chrome-raised(밝음 흰 칸 + --cad-line 테두리 · 어두움 #251f38) · 글 = --cad-text · 안내 = --cad-text-dim
- fillRectScreen · drawTextScreen 에 paintColor 건너뛰는 rawColor 선택 인자
- 입력창 그리기를 inputController/input-field-draw.ts 로 · 전체 맞춤 계산을 drawControllers/fit-view.ts 로(700줄)
- App.consts 입력창 색 상수 = 토큰 폴백 · 틀린 주석 고침
- 시험 theme-auto-color: 칸 · 글 색이 두 테마 모두 다름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
This commit is contained in:
2026-10-08 13:50:07 +09:00
co-authored by Claude Opus 5.5
parent 418c16170f
commit 9f0ab53c53
6 changed files with 205 additions and 116 deletions
@@ -2,13 +2,6 @@ import { Point } from '@flatten-js/core';
import { compact, round } from 'es-toolkit';
import { Actor } from 'xstate';
import {
CANVAS_INPUT_FIELD_BACKGROUND_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,
PINCH_ZOOM_EXPONENT,
SNAP_POINT_DISTANCE,
WHEEL_LINE_PX,
@@ -66,6 +59,7 @@ import {
type TextInputEvent,
} from '../tools/tool.types.ts';
import { activeToolAcceptsText, ImeSink, isImeKey } from './ime-sink.ts';
import { drawInputField } from './input-field-draw.ts';
import {
ABSOLUTE_POINT_REGEXP,
ABSOLUTE_POLAR_REGEXP,
@@ -127,36 +121,7 @@ export class InputController {
}
public draw(drawController: ScreenCanvasDrawController) {
const screenMouseLocation = drawController.getScreenMouseLocation();
// draw input field
drawController.fillRectScreen(
screenMouseLocation.x + CANVAS_INPUT_FIELD_MOUSE_OFFSET,
// fillRectScreen이 아래 변 기준으로 바로잡혔으므로, 칸이 있던 자리를 지키려고
// 높이만큼 내려 잡는다.
screenMouseLocation.y - CANVAS_INPUT_FIELD_MOUSE_OFFSET - CANVAS_INPUT_FIELD_HEIGHT,
CANVAS_INPUT_FIELD_WIDTH,
CANVAS_INPUT_FIELD_HEIGHT,
CANVAS_INPUT_FIELD_BACKGROUND_COLOR
);
this.ime.sync(activeToolAcceptsText());
// Draw text in input field (조합 중인 한글도 함께)
if (this.text || this.ime.composing) {
drawController.drawTextScreen(
this.text + this.ime.composing,
new Point(
screenMouseLocation.x + CANVAS_INPUT_FIELD_MOUSE_OFFSET + 2,
screenMouseLocation.y - CANVAS_INPUT_FIELD_MOUSE_OFFSET - CANVAS_INPUT_FIELD_HEIGHT - 2
),
{
textAlign: 'left',
textColor: CANVAS_INPUT_FIELD_TEXT_COLOR,
fontSize: CANVAS_INPUT_FIELD_FONT_SIZE,
}
);
}
const matchingCommands = this.getCommandSuggestions();
const toolInstruction = getLastStateInstructions();
const texts: string[] = [];
if (toolInstruction) {
@@ -166,11 +131,10 @@ export class InputController {
const roundedY = round(drawController.getWorldMouseLocation().y, 2);
texts.push(`${roundedX},${roundedY}`);
}
if (matchingCommands.length) {
// 입력 중인 문자로 시작하는 명령 후보. 예: C => CIRCLE (C), COPY (CO)
texts.push(...matchingCommands);
}
this.drawListBelowInputField(drawController, texts);
// 입력 중인 문자로 시작하는 명령 후보. 예: C => CIRCLE (C), COPY (CO)
texts.push(...this.getCommandSuggestions());
// 입력 글(조합 중인 한글도 함께)
drawInputField(drawController, this.text + this.ime.composing, texts);
}
public submitText(value: string) {
@@ -661,32 +625,6 @@ export class InputController {
type: ActorEvent.ESC,
});
}
private drawListBelowInputField(
drawController: ScreenCanvasDrawController,
texts: string[]
): void {
const screenMouseLocation = drawController.worldToTarget(
drawController.getWorldMouseLocation()
);
const startY =
screenMouseLocation.y - CANVAS_INPUT_FIELD_MOUSE_OFFSET - CANVAS_INPUT_FIELD_HEIGHT * 2 - 2;
const offsetY = CANVAS_INPUT_FIELD_HEIGHT;
texts.forEach((text, index) => {
drawController.drawTextScreen(
text,
new Point(
screenMouseLocation.x + CANVAS_INPUT_FIELD_MOUSE_OFFSET + 2,
startY - index * offsetY
),
{
textAlign: 'left',
textColor: CANVAS_INPUT_FIELD_INSTRUCTION_TEXT_COLOR,
fontSize: CANVAS_INPUT_FIELD_FONT_SIZE,
}
);
});
}
}
/**
@@ -0,0 +1,79 @@
/**
* 커서 따라다니는 입력창 — 캔버스에 직접 그림(`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),
};
}
/** 입력 글 칸 + 그 아래 안내 · 좌표 · 명령 후보 줄 */
export function drawInputField(
drawController: ScreenCanvasDrawController,
input: string,
lines: string[]
): void {
const mouse = drawController.getScreenMouseLocation();
const colors = inputFieldColors();
const left = mouse.x + CANVAS_INPUT_FIELD_MOUSE_OFFSET;
// 화면 좌표는 위가 + — fillRectScreen 의 y 는 칸 아래 변
const bottom = mouse.y - CANVAS_INPUT_FIELD_MOUSE_OFFSET - CANVAS_INPUT_FIELD_HEIGHT;
const width = CANVAS_INPUT_FIELD_WIDTH;
drawController.fillRectScreen(
left - 1,
bottom - 1,
width + 2,
CANVAS_INPUT_FIELD_HEIGHT + 2,
colors.border,
true
);
drawController.fillRectScreen(
left,
bottom,
width,
CANVAS_INPUT_FIELD_HEIGHT,
colors.background,
true
);
const textOptions = {
textAlign: 'left' as const,
fontSize: CANVAS_INPUT_FIELD_FONT_SIZE,
rawColor: true,
};
if (input) {
drawController.drawTextScreen(input, new Point(left + 2, bottom - 2), {
...textOptions,
textColor: colors.text,
});
}
lines.forEach((line, index) => {
drawController.drawTextScreen(
line,
new Point(
left + 2,
bottom - CANVAS_INPUT_FIELD_HEIGHT - 2 - index * CANVAS_INPUT_FIELD_HEIGHT
),
{ ...textOptions, textColor: colors.dimText }
);
});
}