260719_10

This commit is contained in:
2026-07-19 19:30:47 +09:00
parent 6ec8fca60e
commit dd13c4e253
5 changed files with 118 additions and 63 deletions
@@ -1,16 +1,10 @@
import { Point, type Vector } from '@flatten-js/core';
import { CANVAS_BACKGROUND_COLOR, MOUSE_ZOOM_MULTIPLIER } from '../App.consts';
import { containRectangle } from '../helpers/contain-rect.ts';
import { getAngleWithXAxis } from '../helpers/get-angle-with-x-axis.ts';
import { getBoundingBoxOfMultipleEntities } from '../helpers/get-bounding-box-of-multiple-entities.ts';
import { mapNumberRange } from '../helpers/map-number-range.ts';
import { StateVariable } from '../helpers/undo-stack.ts';
import {
getEntities,
getGridEnabled,
getScreenCanvasDrawController,
triggerReactUpdate,
} from '../state.ts';
import { getEntities, getGridEnabled, triggerReactUpdate } from '../state.ts';
import { DEFAULT_TEXT_OPTIONS, type DrawController } from './DrawController';
/**
@@ -122,23 +116,40 @@ export class ScreenCanvasDrawController implements DrawController {
);
}
/**
* 전체 도면(모든 엔티티)을 화면 중심에 여백을 두고 배치한다.
* 가로/세로 중 더 제약이 큰 축에 맞춰 배율을 정하고, 도면 중심이 화면 중심에
* 오도록 screenOffset(월드 좌표)을 역산한다. (기존 구현은 화면 픽셀 여백값을
* 월드 좌표 offset에 그대로 대입해 중심 배치가 어긋나는 문제가 있었다.)
*/
public zoomToFitScreen() {
const boundingBox = getBoundingBoxOfMultipleEntities(getEntities());
const entities = getEntities();
if (!entities.length) return;
const boundingBox = getBoundingBoxOfMultipleEntities(entities);
const boundingWidth = boundingBox.maxX - boundingBox.minX;
const fittedRect = containRectangle(
boundingBox.minX,
boundingBox.minY,
boundingBox.maxX,
boundingBox.maxY,
0,
0,
getScreenCanvasDrawController().getCanvasSize().x,
getScreenCanvasDrawController().getCanvasSize().y
const boundingHeight = boundingBox.maxY - boundingBox.minY;
const canvasSize = this.getCanvasSize();
// 10% 여백을 남기고 두 축 중 더 빡빡한 쪽에 맞춘다 (종횡비 유지)
const FIT_MARGIN = 0.9;
const scaleX =
boundingWidth > 0 ? (canvasSize.x * FIT_MARGIN) / boundingWidth : Number.POSITIVE_INFINITY;
const scaleY =
boundingHeight > 0 ? (canvasSize.y * FIT_MARGIN) / boundingHeight : Number.POSITIVE_INFINITY;
let zoomLevel = Math.min(scaleX, scaleY);
if (!Number.isFinite(zoomLevel) || zoomLevel <= 0) zoomLevel = 1;
this.setScreenScale(zoomLevel);
// screen = (world - offset) * zoom 이므로, 도면 중심을 화면 중심에 맞추려면
// offset = worldCenter - (화면 절반 픽셀) / zoom
const worldCenterX = (boundingBox.minX + boundingBox.maxX) / 2;
const worldCenterY = (boundingBox.minY + boundingBox.maxY) / 2;
this.setScreenOffset(
new Point(
worldCenterX - canvasSize.x / 2 / zoomLevel,
worldCenterY - canvasSize.y / 2 / zoomLevel
)
);
const fittedWidth = fittedRect.maxX - fittedRect.minX;
const zoomLevel = fittedWidth / boundingWidth;
getScreenCanvasDrawController().setScreenScale(zoomLevel);
getScreenCanvasDrawController().setScreenOffset(new Point(fittedRect.minX, fittedRect.minY));
}
/**