- ZOOM(Z): 「윈도우 구석 지정, 축척 비율 (nX 또는 nXP) 입력 또는 [전체(A)/범위(E)/이전(P)/윈도우(W)] <실시간>:」 덧물음 · E · A = 범위 · P = 앞 화면 · W · 누름 = 두 구석 윈도우 · 2X = 배율 곱 · 빈 Enter = 실시간 안내 · 중심 · 동적 · 축척 · 객체 = 없음 안내 · 'Z 투명 · ⛶ 단추 = 바로 범위 - 약속 더함: SubPrompt.onText(꼴이 정해지지 않은 글 · nX) — routeTyped 가 덧물음에 먼저 물음 · undefined = 사다리 그대로(브레인 알림) - Shift+우클릭: 점 물음에서만 한 번 스냅 메뉴(시작점에서 · 두 점 사이의 중간 · 끝점 · 중간점 · 교차점 · 중심 · 사분점 · 수직 · 노드 · 근처점 · 없음) → 고른 이름을 친 것과 같은 길 · Esc · 바깥 누름 = 메뉴만 닫음(ic/snap-menu.ts) - 고르기 숨은 키워드 ALL · L · P(선택 도구 첫 점 대기 · 쉼 · 명령 안 자식) — 앞 고름에 더함 · 「n개를 찾음, 총 m개」 - 그립 물음 새 꼴 「** 신축 ** 신축점 지정 또는 [종료(X)]:」 · X = 놓음 · 기준점(B) · 복사(C) · 명령 취소(U) 없음 안내 - ↶ ↷ 단추 = undoCommand · redoCommand(끊고 되돌림) · 우클릭 = 명령 중 Enter · 쉴 때 반복(가-1 길 · 시험) - 시험: mouse-acad 마-2 더함(21) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
159 lines
5.5 KiB
TypeScript
159 lines
5.5 KiB
TypeScript
/**
|
|
* 보기 바꿈(337 마) — 범위 줌 · 앞 화면(ZOOM 이전(P)) · 휠 두 번 = 범위 · ZOOM 덧물음.
|
|
* 앞 화면 기억 = ZOOM · 휠 두 번 · ⛶ 로 바꾼 것만(휠 굴림 · 팬은 안 쌓음) · 오토캐드처럼 열 개.
|
|
* ZOOM 은 덧물음 — 도구를 안 바꿔 명령 중 `'Z` 도 같은 길(투명).
|
|
*/
|
|
import { Point } from '@flatten-js/core';
|
|
import type { OptionSpec, PromptSpec } from '../commands/prompt.ts';
|
|
import { getScreenCanvasDrawController } from '../state.ts';
|
|
import { notice, pushSubPrompt, type SubPrompt, type SubResult } from './prompt-input.ts';
|
|
|
|
type View = { scale: number; offset: Point };
|
|
|
|
const MAX_VIEWS = 10;
|
|
/** 휠 두 번 — 이 안에 같은 자리(화면 픽셀)를 다시 누르면 */
|
|
const DOUBLE_MS = 400;
|
|
const DOUBLE_PX = 5;
|
|
|
|
const views: View[] = [];
|
|
let lastMiddle: { time: number; x: number; y: number } | null = null;
|
|
|
|
/** 지금 화면을 앞 화면으로 적고 바꿈 */
|
|
export function changeView(change: () => void): void {
|
|
const controller = getScreenCanvasDrawController();
|
|
views.push({ scale: controller.getScreenScale(), offset: controller.getScreenOffset() });
|
|
if (views.length > MAX_VIEWS) views.shift();
|
|
change();
|
|
}
|
|
|
|
/** 범위 줌 — 도면 전체를 화면에 */
|
|
export const zoomExtents = (): void =>
|
|
changeView(() => getScreenCanvasDrawController().zoomToFitScreen());
|
|
|
|
/** 앞 화면으로 — 없으면 false */
|
|
export function zoomPrevious(): boolean {
|
|
const view = views.pop();
|
|
if (!view) return false;
|
|
const controller = getScreenCanvasDrawController();
|
|
controller.setScreenScale(view.scale);
|
|
controller.setScreenOffset(view.offset);
|
|
return true;
|
|
}
|
|
|
|
/** 가운데 단추 누름 — 두 번째 누름이면 true(그 뒤 다시 처음부터) */
|
|
export function isMiddleDouble(point: { x: number; y: number }, time = Date.now()): boolean {
|
|
const double =
|
|
!!lastMiddle &&
|
|
time - lastMiddle.time <= DOUBLE_MS &&
|
|
Math.hypot(point.x - lastMiddle.x, point.y - lastMiddle.y) <= DOUBLE_PX;
|
|
lastMiddle = double ? null : { time, x: point.x, y: point.y };
|
|
return double;
|
|
}
|
|
|
|
/** 배율만 바꿈 — 화면 가운데 자리를 붙잡음 */
|
|
function setScaleAboutCenter(scale: number): void {
|
|
const controller = getScreenCanvasDrawController();
|
|
const size = controller.getCanvasSize();
|
|
const offset = controller.getScreenOffset();
|
|
const old = controller.getScreenScale();
|
|
const cx = offset.x + size.x / 2 / old;
|
|
const cy = offset.y + size.y / 2 / old;
|
|
controller.setScreenScale(scale);
|
|
controller.setScreenOffset(new Point(cx - size.x / 2 / scale, cy - size.y / 2 / scale));
|
|
}
|
|
|
|
/** 윈도우 줌 — 두 구석 상자를 화면에 꽉(종횡비 유지) */
|
|
export function zoomWindow(a: Point, b: Point): void {
|
|
const controller = getScreenCanvasDrawController();
|
|
const size = controller.getCanvasSize();
|
|
const width = Math.abs(a.x - b.x);
|
|
const height = Math.abs(a.y - b.y);
|
|
const scale = Math.min(
|
|
width > 0 ? size.x / width : Number.POSITIVE_INFINITY,
|
|
height > 0 ? size.y / height : Number.POSITIVE_INFINITY
|
|
);
|
|
if (!Number.isFinite(scale)) return; // 같은 점 두 번 — 안 바꿈
|
|
const cx = (a.x + b.x) / 2;
|
|
const cy = (a.y + b.y) / 2;
|
|
changeView(() => {
|
|
controller.setScreenScale(scale);
|
|
controller.setScreenOffset(new Point(cx - size.x / 2 / scale, cy - size.y / 2 / scale));
|
|
});
|
|
}
|
|
|
|
const zoomOption = (key: string, kw: string, label: string): OptionSpec => ({
|
|
key,
|
|
kw,
|
|
en: key,
|
|
label,
|
|
});
|
|
|
|
/** 오토캐드 ZOOM 첫 줄 — 넣은 넷 · 뺀 넷 · <실시간> */
|
|
const ZOOM_PROMPT: PromptSpec = {
|
|
text: '윈도우 구석 지정, 축척 비율 (nX 또는 nXP) 입력',
|
|
options: [
|
|
zoomOption('ALL', 'A', '전체'),
|
|
zoomOption('EXTENTS', 'E', '범위'),
|
|
zoomOption('PREVIOUS', 'P', '이전'),
|
|
zoomOption('WINDOW', 'W', '윈도우'),
|
|
],
|
|
absent: [
|
|
zoomOption('CENTER', 'C', '중심'),
|
|
zoomOption('DYNAMIC', 'D', '동적'),
|
|
zoomOption('SCALE', 'S', '축척'),
|
|
zoomOption('OBJECT', 'O', '객체'),
|
|
],
|
|
default: { kind: 'enter', label: '실시간' },
|
|
};
|
|
const FIRST_CORNER: PromptSpec = { text: '첫 번째 구석 지정' };
|
|
const OTHER_CORNER: PromptSpec = { text: '반대 구석 지정' };
|
|
const SCALE_REGEXP = /^(\d*\.?\d+)\s*XP?$/i;
|
|
|
|
/** ZOOM — 덧물음 하나가 단계(첫 줄 → 첫 구석 → 반대 구석)를 품음 */
|
|
export function startZoomPrompt(): void {
|
|
let stage: 'main' | 'first' | 'other' = 'main';
|
|
let corner: Point | null = null;
|
|
const sub: SubPrompt = {
|
|
kind: 'point',
|
|
get prompt() {
|
|
return stage === 'main' ? ZOOM_PROMPT : stage === 'first' ? FIRST_CORNER : OTHER_CORNER;
|
|
},
|
|
onPoint(point): SubResult {
|
|
if (stage === 'other' && corner) {
|
|
zoomWindow(corner, point);
|
|
return 'done';
|
|
}
|
|
corner = point; // 첫 줄에서 누름 = 윈도우 첫 구석(오토캐드)
|
|
stage = 'other';
|
|
return 'stay';
|
|
},
|
|
onOption(key): SubResult {
|
|
if (key === 'WINDOW') {
|
|
stage = 'first';
|
|
return 'stay';
|
|
}
|
|
if (key === 'PREVIOUS') {
|
|
if (!zoomPrevious()) notice('앞 화면이 없음.');
|
|
return 'done';
|
|
}
|
|
zoomExtents(); // 전체(A) = 범위(이 CAD 엔 도면 한계 없음)
|
|
return 'done';
|
|
},
|
|
onEnter(): SubResult {
|
|
if (stage !== 'main') return 'stay';
|
|
notice('실시간 줌은 휠로.');
|
|
return 'done';
|
|
},
|
|
onText(text) {
|
|
const match = stage === 'main' ? SCALE_REGEXP.exec(text) : null;
|
|
if (!match) return undefined;
|
|
const factor = Number.parseFloat(match[1]);
|
|
if (factor <= 0) return undefined;
|
|
const scale = getScreenCanvasDrawController().getScreenScale() * factor;
|
|
changeView(() => setScaleAboutCenter(scale));
|
|
return 'done';
|
|
},
|
|
};
|
|
pushSubPrompt(sub);
|
|
}
|