mini Figma-like editor 완성
이제 강의의 capstone을 정의합니다. 목표는 Figma 전체를 복제하는 것이 아니라, GPU renderer 기반 편집기의 핵심 흐름을 끝까지 연결하는 것입니다.
mini editor의 메인 루프
capstone은 아래 흐름이 실제로 이어지는지 확인하는 통합 테스트입니다.
function createMiniEditor({ canvas, panels }) {
const core = createEditorCore(initialDocument());
const renderer = createWebGLRenderer(canvas);
function frame() {
const snapshot = core.getSnapshot();
renderer.render(snapshot);
panels.layers.render(snapshot, core.dispatch);
panels.inspector.render(snapshot, core.dispatch);
panels.status.render(renderer.getStats(), snapshot);
}
canvas.addEventListener("pointerdown", (event) => {
core.dispatch(pointerDownToAction(event, canvas, core.getSnapshot()));
frame();
});
canvas.addEventListener("pointermove", (event) => {
core.dispatch(pointerMoveToAction(event, canvas, core.getSnapshot()));
frame();
});
return {
dispatch(action) {
core.dispatch(action);
frame();
},
save() {
return JSON.stringify(core.getSnapshot().document);
},
load(json) {
core.dispatch({ type: "loadDocument", document: JSON.parse(json) });
frame();
}
};
}
처음에는 모든 action 뒤에 frame()을 호출해도 됩니다. 이후 dirty flag와 requestAnimationFrame scheduler로 바꾸면 됩니다.
core, tool, renderer를 조립한다
capstone의 핵심은 모든 입력이 core action으로 들어가고, renderer는 snapshot만 읽는 구조입니다.
function installCanvasInput(canvas, core, requestFrame) {
let drag = null;
canvas.addEventListener("pointerdown", (event) => {
const input = pointerEventToInput(event, canvas, core.getSnapshot().camera);
drag = beginSelectOrTransform(core.getSnapshot(), input);
canvas.setPointerCapture(event.pointerId);
requestFrame();
});
canvas.addEventListener("pointermove", (event) => {
const input = pointerEventToInput(event, canvas, core.getSnapshot().camera);
const action = drag
? updateActiveDrag(core.getSnapshot(), drag, input)
: updateHover(core.getSnapshot(), input);
core.dispatch(action);
requestFrame();
});
canvas.addEventListener("pointerup", (event) => {
if (!drag) return;
const input = pointerEventToInput(event, canvas, core.getSnapshot().camera);
core.dispatch(commitActiveDrag(core.getSnapshot(), drag, input));
drag = null;
requestFrame();
});
}
beginSelectOrTransform, updateActiveDrag, commitActiveDrag 안에서 Part 3의 move/resize/rotate/marquee 함수를 선택해 호출합니다.
capstone 범위
최소 capstone은 다음 기능을 포함합니다.
infinite canvas pan/zoom
rectangle/image/text node
selection, hover, move
resize and rotate handles
layer panel
property inspector
undo/redo
JSON export/import
WebGL renderer
WebGPU renderer experiment
이 정도면 “GPU canvas 위에 Figma-like editor를 어떻게 세우는가”를 설명하기에 충분합니다.
모든 기능은 core를 거친다
도형 추가, 선택 변경, 이동, 삭제, 속성 변경은 editor core action으로 들어갑니다.
UI/input
-> action
-> command/state update
-> renderer redraw
-> panels update
렌더러가 직접 문서 상태를 바꾸지 않는 원칙을 끝까지 지킵니다.
완성 기준은 흐름이다
capstone의 완성 기준은 기능 수가 아니라 흐름입니다. 사용자가 도형을 만들고, 선택하고, 움직이고, 저장하고, 다시 열 수 있어야 합니다.
create
select
edit
undo
save
load
render
이 흐름이 끊기지 않으면 이후 기능은 점진적으로 붙일 수 있습니다.
오늘의 핵심
mini editor는 강의의 모든 경계를 검증하는 통합 테스트입니다.
scene model
tool math
renderer backend
UI panels
persistence
작지만 끝까지 동작하는 에디터를 만드는 것이 목표입니다.