Files
Aislo/A00_Common/input_prefs.ts
T
eomsangdonandClaude Opus 5.5 efa382ae06 feat(설정 마우스): 화면 옮기기 단추 · 바꾼 키 칸 읽기 — panButton · isPanButton · buttonMask (PLAN 588-4 이음 · 요구 445 C3)
- input-prefs 에 panButton(왼 · 가운데 · 오른 · 기본 가운데) · keys(바꾼 키만) 칸
- isPanButton = 고른 단추 또는 가운데 · 585-1 카카오 부품이 부름
- test_input_prefs 기본값 · 단추 판정 · 깨진 값 거름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:36:16 +09:00

104 lines
4.8 KiB
TypeScript

/* =============================================================================
* input_prefs.ts
* 마우스 · 단축키 설정 읽기 층(PLAN 332) — ☰ [설정] 창이 [저장] 때 계정 pref 로 씀.
*
* - 값 = `aislo:pref:input-prefs`(등록표 `input-prefs`) JSON 한 벌 · 부를 때마다 읽음(새로고침 없이 바로 따름)
* - 없음 · 깨짐 · Node(저장소 없음) = 기본값(= 지금 동작) · 의존 없음 · 불러올 때 window 안 만짐(CAD 도 import)
* - 휠 확대 방향 하나로 모든 화면 일체 — 기본 밂 = 휠을 앞으로(deltaY < 0) 굴리면 확대(오토캐드) · 당김 = 몸 쪽으로
* - 핀치(트랙패드 · 손가락)는 설정과 상관없이 늘 벌리면 확대(deltaY < 0)
* - 화면 옮기기 단추 하나(PLAN 588-4) — 고른 단추로 끌면 옮김 · 안 끌고 떼면 그 단추 제 몫 · 가운데는 늘 옮김
* - 바꾼 키(`keys`)는 기본과 다른 것만 — 판정 · 등록부는 `key_registry.ts`
* ========================================================================== */
export type WheelZoom = "pull" | "push";
export type PanButton = "left" | "middle" | "right";
export interface InputPrefs {
/** 휠 확대 방향 — pull = 당기면 확대 · push = 밀면 확대 */
wheelZoom: WheelZoom;
/** 화면 옮기기 단추 */
panButton: PanButton;
/** 바꾼 단축키 `{동작 id: [키]}` — 기본과 다른 것만(`key_registry.ts`) */
keys: Record<string, string[]>;
/** CAD 내 별칭 `{명령: [별칭]}`(대문자 · acad.pgp 식 — 명령 이름 > 내 별칭 > 기본 별칭) */
cadAliases: Record<string, string[]>;
}
/** `stateKey("input-prefs")` 와 같은 키(시험이 맞춰 봄) — 등록표를 불러오면 window 가 딸려 와서 글자로 둠 */
export const INPUT_PREFS_KEY = "aislo:pref:input-prefs";
export const DEFAULT_INPUT_PREFS: InputPrefs = {
wheelZoom: "push",
panButton: "middle",
keys: {},
cadAliases: {},
};
const PAN_BUTTONS: PanButton[] = ["left", "middle", "right"];
const defaults = (): InputPrefs => ({ ...DEFAULT_INPUT_PREFS, keys: {}, cadAliases: {} });
const cleanKeys = (raw: unknown): Record<string, string[]> => {
const out: Record<string, string[]> = {};
if (!raw || typeof raw !== "object") return out;
for (const [id, list] of Object.entries(raw as Record<string, unknown>)) {
if (Array.isArray(list)) out[id] = list.filter((k): k is string => typeof k === "string");
}
return out;
};
const cleanAliases = (raw: unknown): Record<string, string[]> => {
const out: Record<string, string[]> = {};
if (!raw || typeof raw !== "object") return out;
for (const [id, list] of Object.entries(raw as Record<string, unknown>)) {
if (!Array.isArray(list)) continue;
const aliases = list
.filter((a): a is string => typeof a === "string")
.map((a) => a.toUpperCase());
if (aliases.length) out[id.toUpperCase()] = aliases;
}
return out;
};
/** 지금 설정 — 부를 때마다 저장소에서 읽음 */
export function inputPrefs(): InputPrefs {
try {
const raw = globalThis.localStorage?.getItem(INPUT_PREFS_KEY);
if (!raw) return defaults();
const value = JSON.parse(raw) as Partial<InputPrefs> | null;
return {
wheelZoom: value?.wheelZoom === "pull" ? "pull" : "push",
panButton: PAN_BUTTONS.includes(value?.panButton as PanButton)
? (value!.panButton as PanButton)
: "middle",
keys: cleanKeys(value?.keys),
cadAliases: cleanAliases(value?.cadAliases),
};
} catch {
return defaults();
}
}
/** 고른 화면 옮기기 단추 */
export const panButton = (): PanButton => inputPrefs().panButton;
/** 이 단추(`MouseEvent.button` 0 왼 · 1 가운데 · 2 오른)로 끌면 화면 옮김 — 고른 단추 · 가운데는 늘 */
export const isPanButton = (button: number): boolean =>
button === 1 || button === PAN_BUTTONS.indexOf(panButton());
/** 단추 번호 → `buttons` 비트(왼 1 · 오른 2 · 가운데 4) — 끄는 중 놓침 알아채기 */
export const buttonMask = (button: number): number => [1, 4, 2][button] ?? 0;
/** 휠 한 번이 확대인가 — 설정 방향을 따름 */
export const wheelIn = (deltaY: number): boolean =>
inputPrefs().wheelZoom === "pull" ? deltaY > 0 : deltaY < 0;
/** 트랙패드 핀치 — 브라우저가 Ctrl+휠 · 잘게 쪼갠 값으로 냄
* ponytail: |deltaY| < 50 로 마우스 Ctrl+휠(한 칸 ≥ 약 100)과 가름 · 잘게 도는 마우스는 핀치로 봄 */
export const isTrackpadPinch = (e: Pick<WheelEvent, "ctrlKey" | "deltaY">): boolean =>
e.ctrlKey && Math.abs(e.deltaY) < 50;
/** 휠 사건이 확대인가 — 핀치(`pinch` 손가락 벌림 · 트랙패드)는 늘 벌리면 확대 · 나머지는 설정 방향 */
export const wheelEventIn = (e: Pick<WheelEvent, "ctrlKey" | "deltaY">, pinch = false): boolean =>
pinch || isTrackpadPinch(e) ? e.deltaY < 0 : wheelIn(e.deltaY);