tool state machine과 pointer capture
move, resize, rotate 수학이 맞아도 pointer 이벤트 흐름이 흔들리면 에디터는 쉽게 깨집니다. tool은 상태 기계로 다루는 편이 안전합니다.
tool state를 명시한다
const initialToolState = {
mode: "idle",
pointerId: null,
drag: null,
previewPatch: null
};
function pointerDownOnSelection(state, event, hit) {
event.currentTarget.setPointerCapture(event.pointerId);
return {
mode: "dragging",
pointerId: event.pointerId,
drag: {
kind: "move",
startWorld: event.world,
initialSelection: snapshotSelection(state.document, hit.selection)
},
previewPatch: null
};
}
dragging 중에는 document를 바로 바꾸지 않고 preview patch를 만들 수 있습니다.
commit과 cancel을 분리한다
function updateMoveDrag(tool, currentWorld) {
const delta = subtract(currentWorld, tool.drag.startWorld);
return {
...tool,
previewPatch: moveSelectionPreview(tool.drag.initialSelection, delta)
};
}
function commitTool(state) {
if (!state.tool.previewPatch) return state;
return applyCommand(state, createCommandFromPreview(state.tool.previewPatch));
}
function cancelTool(state) {
return { ...state, tool: initialToolState };
}
Escape, pointer cancel, window blur는 preview를 버려야 합니다. pointer up은 command를 하나만 만들어 undo stack에 넣습니다.
pointer capture를 상태 전환과 함께 쓴다
function bindCanvasPointerEvents(canvas, toolController) {
canvas.addEventListener("pointerdown", (event) => {
canvas.setPointerCapture(event.pointerId);
toolController.send({
type: "pointerDown",
pointerId: event.pointerId,
point: { x: event.offsetX, y: event.offsetY },
modifiers: readModifiers(event)
});
});
canvas.addEventListener("pointermove", (event) => {
toolController.send({
type: "pointerMove",
pointerId: event.pointerId,
point: { x: event.offsetX, y: event.offsetY },
modifiers: readModifiers(event)
});
});
canvas.addEventListener("lostpointercapture", (event) => {
toolController.send({ type: "cancel", pointerId: event.pointerId });
});
}
pointer capture는 이벤트를 놓치지 않기 위한 브라우저 API이고, state machine은 놓쳤을 때도 정리 가능한 편집 모델입니다. 둘을 함께 써야 drag 중 pointer가 canvas 밖으로 나가도 상태가 꼬이지 않습니다.
오늘의 핵심
tool state machine은 수학을 실제 편집 동작으로 묶는 구조입니다. preview와 commit을 분리하면 undo/redo, cancel, pointer capture가 안정됩니다.