From 26a2f39dd6ecaa0bbc5bb47ac676862b354f92a9 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 29 Aug 2026 17:00:27 +0900 Subject: [PATCH] =?UTF-8?q?feat(B07):=20=ED=8A=B8=EB=9E=99=ED=8C=A8?= =?UTF-8?q?=EB=93=9C=20=EB=91=90=20=EC=86=90=EA=B0=80=EB=9D=BD=20=ED=8C=AC?= =?UTF-8?q?=20+=20=ED=99=95=EB=8C=80=EB=A5=BC=20=EB=8D=B8=ED=83=80=20?= =?UTF-8?q?=EB=B9=84=EB=A1=80=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 확대가 이벤트 1회당 10% 고정이라 트랙패드처럼 잔 델타를 초당 수십 번 보내는 입력에서 튀었고, 두 손가락 이동은 팬이 아니라 확대로 먹혔다. - zoomScreen을 부호 기반에서 델타 비례 지수식 exp(-delta*지수)로 변경. 마우스 휠 한 칸(deltaY 100)은 종전과 같은 10%, 트랙패드 잔 델타는 그만큼만 - 트랙패드 두 손가락 이동은 panScreen으로 — 마우스 휠은 한 칸이 100/120px로 딱 떨어지고 가로 델타가 없다는 성질로 기기를 가른다 - 핀치(ctrl+휠)는 기기 무관 확대, 전용 지수로 천천히 - deltaMode(줄·쪽 단위) 픽셀 환산 추가 Co-Authored-By: Claude Fable 5 --- B07_DesignDetail/openwebcad/src/App.consts.ts | 16 +++++++- .../screenCanvas.drawController.ts | 9 +++-- .../src/inputController/input-controller.ts | 39 +++++++++++++++++-- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/B07_DesignDetail/openwebcad/src/App.consts.ts b/B07_DesignDetail/openwebcad/src/App.consts.ts index 50d6bb9a..ecb9e1b0 100644 --- a/B07_DesignDetail/openwebcad/src/App.consts.ts +++ b/B07_DesignDetail/openwebcad/src/App.consts.ts @@ -139,9 +139,21 @@ export const GUIDE_LINE_WIDTH = 1; export const GUIDE_LINE_STYLE = [5, 5]; // Dashed line /** - * Mouse zoom multiplier. Higher zooms faster for each mouse scroll + * 확대 배율 = exp(delta * 지수). 델타에 비례하므로 마우스 휠 한 칸(deltaY 100)은 + * 종전과 같은 10%를 움직이고, 트랙패드가 잔 델타를 초당 수십 번 보내도 그 크기만큼만 + * 움직인다 (ln(1.1)/100 = 0.000953). */ -export const MOUSE_ZOOM_MULTIPLIER = 0.1; +export const WHEEL_ZOOM_EXPONENT = 0.000953; + +/** + * 트랙패드 핀치(ctrl+휠)용 지수. 핀치는 한 동작의 델타 총합이 휠 한 칸보다 작아 + * 같은 값을 쓰면 거의 안 움직인다. 확대가 너무 빠르면/느리면 이 값만 조정한다. + */ +export const PINCH_ZOOM_EXPONENT = 0.0015; + +/** deltaMode가 줄(1)·쪽(2) 단위로 올 때 픽셀로 환산하는 값 (마우스 휠 한 칸 = 100px 기준). */ +export const WHEEL_LINE_PX = 33; +export const WHEEL_PAGE_PX = 800; /** * Canvas input field offset to mouse location diff --git a/B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts b/B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts index 8b0d6a17..4d3fcd71 100644 --- a/B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts +++ b/B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts @@ -1,5 +1,5 @@ import { Point, type Vector } from '@flatten-js/core'; -import { MOUSE_ZOOM_MULTIPLIER } from '../App.consts'; +import { WHEEL_ZOOM_EXPONENT } from '../App.consts'; 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'; @@ -107,13 +107,14 @@ export class ScreenCanvasDrawController implements DrawController { * This function takes the deltaY from the mouse wheel event and zooms the screen in or out * The location of the mouse in world space is preserved * @param deltaY + * @param exponent 델타 한 단위당 확대 지수 (휠·핀치가 서로 다른 값을 쓴다) */ - public zoomScreen(deltaY: number) { + public zoomScreen(deltaY: number, exponent: number = WHEEL_ZOOM_EXPONENT) { const worldMouseLocationBeforeZoom = this.getWorldMouseLocation(); const oldScreenScale = this.getScreenScale(); - const newScreenScale = - oldScreenScale * (1 - MOUSE_ZOOM_MULTIPLIER * (deltaY / Math.abs(deltaY))); + // 부호가 아니라 크기까지 본다 — 트랙패드의 잔 델타는 그만큼만 움직인다. + const newScreenScale = oldScreenScale * Math.exp(-deltaY * exponent); this.setScreenScale(newScreenScale); // now get the location of the cursor in world space again diff --git a/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts b/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts index 4dad90a8..f70c8a9e 100644 --- a/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts +++ b/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts @@ -9,7 +9,11 @@ import { CANVAS_INPUT_FIELD_TEXT_COLOR, CANVAS_INPUT_FIELD_WIDTH, HIGHLIGHT_ENTITY_DISTANCE, + PINCH_ZOOM_EXPONENT, SNAP_POINT_DISTANCE, + WHEEL_LINE_PX, + WHEEL_PAGE_PX, + WHEEL_ZOOM_EXPONENT, } from '../App.consts.ts'; import { MouseButton } from '../App.types.ts'; import type { ScreenCanvasDrawController } from '../drawControllers/screenCanvas.drawController.ts'; @@ -228,13 +232,30 @@ export class InputController { public handleMouseWheel(evt: WheelEvent) { // 확대는 도면만 — 트랙패드 핀치(ctrl+휠)가 브라우저 전체를 키우던 것을 막는다. evt.preventDefault(); - if (Math.abs(evt.deltaY) === 0) { + const drawController = getScreenCanvasDrawController(); + // deltaY 단위를 픽셀로 맞춘다 — 브라우저에 따라 줄(1)·쪽(2) 단위로 오기도 한다. + const unitPx = evt.deltaMode === 1 ? WHEEL_LINE_PX : evt.deltaMode === 2 ? WHEEL_PAGE_PX : 1; + const deltaX = evt.deltaX * unitPx; + const deltaY = evt.deltaY * unitPx; + + // 핀치(ctrl+휠)는 기기와 무관하게 확대. + if (evt.ctrlKey) { + if (deltaY !== 0) drawController.zoomScreen(-deltaY, PINCH_ZOOM_EXPONENT); + return; + } + + // 트랙패드 두 손가락 이동은 팬 — 문서를 스크롤하듯 화면이 손가락 반대로 간다. + if (!isMouseWheel(evt)) { + drawController.panScreen(-deltaX, deltaY); + return; + } + + if (deltaY === 0) { return; // We can't zoom by zero delta } - const drawController = getScreenCanvasDrawController(); // Aislo: wheel direction is inverted on purpose - pulling the wheel zooms in, pushing // zooms out, matching the B04/B05 maps and the B06 cross sections (2026-08-02). - drawController.zoomScreen(-evt.deltaY); + drawController.zoomScreen(-deltaY, WHEEL_ZOOM_EXPONENT); } public handleMouseDown(evt: MouseEvent) { @@ -540,3 +561,15 @@ export class InputController { }); } } + +/** + * 마우스 휠인지 트랙패드인지 가른다. 마우스 휠은 한 칸이 정해진 크기(Chrome 100px, + * 일부 브라우저 120px)로 딱 떨어지고 가로 델타가 없다. 트랙패드는 손가락이 움직인 + * 만큼 잔 델타를 보내고 가로 델타도 함께 온다. 줄·쪽 단위(deltaMode≠0)는 휠뿐이다. + */ +function isMouseWheel(evt: WheelEvent): boolean { + if (evt.deltaMode !== 0) return true; + if (evt.deltaX !== 0) return false; + const step = Math.abs(evt.deltaY); + return step !== 0 && (step % 100 === 0 || step % 120 === 0); +}