Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { Point } from '@flatten-js/core';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { createActor } from 'xstate';
|
||||
import { LineEntity } from '../entities/LineEntity.ts';
|
||||
import {
|
||||
getEntities,
|
||||
resetUndoBaseline,
|
||||
setEntities,
|
||||
setSelectedEntityIds,
|
||||
undo,
|
||||
} from '../state.ts';
|
||||
import { ScaleState, scaleToolStateMachine } from './scale-tool.ts';
|
||||
import { ActorEvent } from './tool.types.ts';
|
||||
|
||||
const ends = (index: number) => {
|
||||
const line = getEntities()[index] as LineEntity;
|
||||
const a = line.getStartPoint();
|
||||
const b = line.getEndPoint();
|
||||
return [a.x, a.y, b.x, b.y];
|
||||
};
|
||||
|
||||
/** 기준점 (2,0) 클릭까지 — 선 A(0,0)-(10,0) 고름 · 선 B(0,5)-(4,5) 는 그대로 둘 것 */
|
||||
function startAtBasePoint() {
|
||||
const a = new LineEntity('layer1', new Point(0, 0), new Point(10, 0));
|
||||
const b = new LineEntity('layer1', new Point(0, 5), new Point(4, 5));
|
||||
setEntities([a, b]);
|
||||
resetUndoBaseline();
|
||||
setSelectedEntityIds([a.id]);
|
||||
const actor = createActor(scaleToolStateMachine).start();
|
||||
expect(actor.getSnapshot().value).toBe(ScaleState.WAITING_FOR_BASE_VECTOR_START_POINT);
|
||||
actor.send({
|
||||
type: ActorEvent.MOUSE_CLICK,
|
||||
worldMouseLocation: new Point(2, 0),
|
||||
screenMouseLocation: new Point(0, 0),
|
||||
holdingCtrl: false,
|
||||
holdingShift: false,
|
||||
});
|
||||
expect(actor.getSnapshot().value).toBe(ScaleState.WAITING_FOR_BASE_VECTOR_END_POINT);
|
||||
return { actor, a, b };
|
||||
}
|
||||
|
||||
const typeNumber = (actor: ReturnType<typeof startAtBasePoint>['actor'], value: number) =>
|
||||
actor.send({ type: ActorEvent.NUMBER_INPUT, value, worldMouseLocation: new Point(99, 99) });
|
||||
|
||||
describe('축척 — 기준점 + 숫자 배율 (226-1)', () => {
|
||||
beforeEach(() => setSelectedEntityIds([]));
|
||||
|
||||
it('배율 2 = 기준점 고정 두 배 · 다른 객체 · 순서 그대로', () => {
|
||||
const { actor, a, b } = startAtBasePoint();
|
||||
typeNumber(actor, 2);
|
||||
// (0,0)→(-2,0) · (10,0)→(18,0) — 기준점 (2,0) 은 그대로
|
||||
expect(ends(0)).toEqual([-2, 0, 18, 0]);
|
||||
expect(getEntities()[0].id).toBe(a.id);
|
||||
expect(getEntities()[1]).toBe(b);
|
||||
expect(ends(1)).toEqual([0, 5, 4, 5]);
|
||||
// 끝나면 선택 풀고 다음 고르기로
|
||||
expect(actor.getSnapshot().value).toBe(ScaleState.WAITING_FOR_SELECTION);
|
||||
});
|
||||
|
||||
it('배율 0.5 = 절반', () => {
|
||||
const { actor } = startAtBasePoint();
|
||||
typeNumber(actor, 0.5);
|
||||
expect(ends(0)).toEqual([1, 0, 6, 0]);
|
||||
});
|
||||
|
||||
it('되돌리기 한 번에 원래 자리', () => {
|
||||
const { actor } = startAtBasePoint();
|
||||
typeNumber(actor, 2);
|
||||
undo();
|
||||
expect(ends(0)).toEqual([0, 0, 10, 0]);
|
||||
expect(getEntities()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('Esc = 취소 — 객체 그대로', () => {
|
||||
const { actor, a, b } = startAtBasePoint();
|
||||
actor.send({ type: ActorEvent.ESC });
|
||||
expect(getEntities()).toEqual([a, b]);
|
||||
expect(ends(0)).toEqual([0, 0, 10, 0]);
|
||||
typeNumber(actor, 2);
|
||||
expect(ends(0)).toEqual([0, 0, 10, 0]);
|
||||
});
|
||||
|
||||
it('배율 0 은 받지 않음', () => {
|
||||
const { actor } = startAtBasePoint();
|
||||
typeNumber(actor, 0);
|
||||
expect(ends(0)).toEqual([0, 0, 10, 0]);
|
||||
expect(actor.getSnapshot().value).toBe(ScaleState.WAITING_FOR_BASE_VECTOR_END_POINT);
|
||||
});
|
||||
});
|
||||
@@ -2,15 +2,23 @@ import type { Point } from '@flatten-js/core';
|
||||
import {
|
||||
addEntities,
|
||||
deleteEntities,
|
||||
getEntities,
|
||||
getSelectedEntities,
|
||||
getSelectedEntityIds,
|
||||
setAngleGuideOriginPoint,
|
||||
setEntities,
|
||||
setGhostHelperEntities,
|
||||
setSelectedEntityIds,
|
||||
setShouldDrawHelpers,
|
||||
} from '../state';
|
||||
import { Tool } from '../tools';
|
||||
import type { DrawEvent, MouseClickEvent, StateEvent, ToolContext } from './tool.types';
|
||||
import type {
|
||||
DrawEvent,
|
||||
MouseClickEvent,
|
||||
NumberInputEvent,
|
||||
StateEvent,
|
||||
ToolContext,
|
||||
} from './tool.types';
|
||||
import { assign, createMachine, sendTo } from 'xstate';
|
||||
import { selectToolStateMachine } from './select-tool';
|
||||
import type { Entity } from '../entities/Entity';
|
||||
@@ -42,6 +50,7 @@ export enum ScaleAction {
|
||||
COPY_SELECTION_BEFORE_SCALE = 'COPY_SELECTION_BEFORE_SCALE',
|
||||
DRAW_TEMP_SCALE_ENTITIES = 'DRAW_TEMP_SCALE_ENTITIES',
|
||||
SCALE_SELECTION = 'SCALE_SELECTION',
|
||||
SCALE_SELECTION_BY_FACTOR = 'SCALE_SELECTION_BY_FACTOR',
|
||||
DESELECT_ENTITIES = 'DESELECT_ENTITIES',
|
||||
RESTORE_ORIGINAL_ENTITIES = 'RESTORE_ORIGINAL_ENTITIES',
|
||||
}
|
||||
@@ -98,7 +107,7 @@ export const scaleToolStateMachine = createMachine(
|
||||
[ScaleState.WAITING_FOR_SELECTION]: {
|
||||
description: 'Select what you want to scale',
|
||||
meta: {
|
||||
instructions: 'Select what you want to scale, then ENTER',
|
||||
instructions: '축척할 객체를 선택한 뒤 ENTER.',
|
||||
},
|
||||
invoke: {
|
||||
id: 'selectToolInsideTheScaleTool',
|
||||
@@ -142,7 +151,7 @@ export const scaleToolStateMachine = createMachine(
|
||||
[ScaleState.WAITING_FOR_BASE_VECTOR_START_POINT]: {
|
||||
description: 'Select the origin of the scale operation',
|
||||
meta: {
|
||||
instructions: 'Select the origin of the scale operation',
|
||||
instructions: '기준점을 지정하십시오. (Esc 취소)',
|
||||
},
|
||||
always: {
|
||||
actions: ScaleAction.ENABLE_HELPERS,
|
||||
@@ -161,9 +170,17 @@ export const scaleToolStateMachine = createMachine(
|
||||
[ScaleState.WAITING_FOR_BASE_VECTOR_END_POINT]: {
|
||||
description: 'Select the end of the base scale line',
|
||||
meta: {
|
||||
instructions: 'Select the end of the base scale line',
|
||||
instructions:
|
||||
'배율을 입력하고 ENTER — 또는 끌어서 맞출 기준선 끝을 클릭하십시오. (Esc 취소)',
|
||||
},
|
||||
on: {
|
||||
// 오토캐드 SCALE — 기준점 뒤 숫자 배율(2 = 두 배 · 0.5 = 절반)
|
||||
NUMBER_INPUT: {
|
||||
guard: ({ event }) =>
|
||||
(event as NumberInputEvent).value > 0 && getSelectedEntityIds().length > 0,
|
||||
actions: [ScaleAction.SCALE_SELECTION_BY_FACTOR, ScaleAction.DESELECT_ENTITIES],
|
||||
target: ScaleState.INIT,
|
||||
},
|
||||
MOUSE_CLICK: {
|
||||
actions: [
|
||||
ScaleAction.RECORD_BASE_VECTOR_END_POINT,
|
||||
@@ -180,7 +197,7 @@ export const scaleToolStateMachine = createMachine(
|
||||
[ScaleState.WAITING_FOR_SCALE_VECTOR_END_POINT]: {
|
||||
description: 'Select the end of the scale line',
|
||||
meta: {
|
||||
instructions: 'Select the end of the scale line',
|
||||
instructions: '새 길이의 끝을 클릭하십시오. (Esc 취소)',
|
||||
},
|
||||
on: {
|
||||
DRAW: {
|
||||
@@ -235,7 +252,9 @@ export const scaleToolStateMachine = createMachine(
|
||||
return {
|
||||
...context,
|
||||
// Make a copy of the selected entities before scaling them, so we can restore them when the user cancels the scale action
|
||||
originalSelectedEntities: compact(selectedEntities.map((entity) => cloneKeepingLook(entity, true))),
|
||||
originalSelectedEntities: compact(
|
||||
selectedEntities.map((entity) => cloneKeepingLook(entity, true))
|
||||
),
|
||||
};
|
||||
}),
|
||||
[ScaleAction.DRAW_TEMP_SCALE_ENTITIES]: ({ context, event }) => {
|
||||
@@ -282,6 +301,24 @@ export const scaleToolStateMachine = createMachine(
|
||||
setGhostHelperEntities([]);
|
||||
setSelectedEntityIds([]);
|
||||
},
|
||||
[ScaleAction.SCALE_SELECTION_BY_FACTOR]: ({ context, event }) => {
|
||||
const origin = context.baseVectorStartPoint;
|
||||
if (!origin) {
|
||||
throw new Error('[SCALE] Calling scale by factor without a base point');
|
||||
}
|
||||
const factor = (event as NumberInputEvent).value;
|
||||
const selectedIds = new Set(getSelectedEntityIds());
|
||||
// 제자리 교체 — 복사본을 키워 같은 순서에 넣고 되돌리기 한 칸으로 남긴다
|
||||
setEntities(
|
||||
getEntities().map((entity) => {
|
||||
if (!selectedIds.has(entity.id)) return entity;
|
||||
const scaled = cloneKeepingLook(entity, true);
|
||||
scaled.scale(origin, factor);
|
||||
return scaled;
|
||||
}),
|
||||
true
|
||||
);
|
||||
},
|
||||
[ScaleAction.DESELECT_ENTITIES]: assign(() => {
|
||||
setGhostHelperEntities([]);
|
||||
setSelectedEntityIds([]);
|
||||
|
||||
Reference in New Issue
Block a user