provisional/phy-editor-feedback.md の合意事項と phy-editor-keybindings.md の割り当て案に沿って、PHY 画面を一から書き直した。 App.vue は共通のシェル(入出力・履歴・タブ)だけにし、PHY は src/ui/phy/ に、コンポーネント・composable・純粋モジュール(logic.ts, viewport.ts)を分けて置いた。 - 画面確認で見つかった不具合(読み込み時の全体表示、Shift+クリック、削除の確認の大きさ、Alt+↑、フォーカス中の二重描画、長押し中の選択など)を直し、キャンバス上の実際の操作で確かめる e2e を足した - 階層パネルでクラスターの子を余分な折りたたみで包まず、件数を配下のキー・部品の数にそろえた - 実装は Codex(Luna)。検証: vue-tsc 0件、Vitest 64件、Playwright 30件、prettier --check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VbfgHT848s3JbuFw8MKFF8
47 lines
1.3 KiB
TypeScript
47 lines
1.3 KiB
TypeScript
export type Viewport = { zoom: number; pan: { x: number; y: number } };
|
|
export type ViewBounds = { x: number; y: number; width: number; height: number };
|
|
|
|
/** 境界を余白付きで中央へ収める。座標・倍率だけを扱う純粋関数。 */
|
|
export function fitViewport(
|
|
bounds: ViewBounds,
|
|
width: number,
|
|
height: number,
|
|
padding = 40,
|
|
minZoom = 8,
|
|
maxZoom = 160,
|
|
): Viewport {
|
|
const zoom = Math.max(
|
|
minZoom,
|
|
Math.min(
|
|
maxZoom,
|
|
Math.min(
|
|
(width - 2 * padding) / Math.max(bounds.width, 1),
|
|
(height - 2 * padding) / Math.max(bounds.height, 1),
|
|
),
|
|
),
|
|
);
|
|
return {
|
|
zoom,
|
|
pan: {
|
|
x: (width - (2 * bounds.x + bounds.width) * zoom) / 2,
|
|
y: (height - (2 * bounds.y + bounds.height) * zoom) / 2,
|
|
},
|
|
};
|
|
}
|
|
|
|
/** 指定した画面座標の下にある点を保ったまま倍率を変える。 */
|
|
export function zoomAround(
|
|
point: { x: number; y: number },
|
|
viewport: Viewport,
|
|
factor: number,
|
|
minZoom = 5,
|
|
maxZoom = 200,
|
|
): Viewport {
|
|
const zoom = Math.max(minZoom, Math.min(maxZoom, viewport.zoom * factor));
|
|
const world = {
|
|
x: (point.x - viewport.pan.x) / viewport.zoom,
|
|
y: (point.y - viewport.pan.y) / viewport.zoom,
|
|
};
|
|
return { zoom, pan: { x: point.x - world.x * zoom, y: point.y - world.y * zoom } };
|
|
}
|