hover outline
hover outline은 작은 기능처럼 보이지만, 에디터의 조작감을 크게 좌우합니다. 사용자는 클릭하기 전에 어떤 도형이 잡힐지 미리 알고 싶어합니다.
hover state를 갱신하는 코드
hover는 문서 상태가 아니라 tool state입니다. pointer가 움직일 때 마지막 world 좌표로 hit test를 하고 overlay만 다시 그립니다.
const toolState = {
hoverNodeId: null,
lastPointerWorld: null
};
canvas.addEventListener("pointermove", (event) => {
const snapshot = editorCore.getRendererSnapshot();
const screen = clientToCanvas(event, canvas);
const world = screenToWorld(screen, snapshot.camera);
const hit = hitTest(snapshot.drawList, world);
toolState.lastPointerWorld = world;
toolState.hoverNodeId = hit ? hit.id : null;
requestRender();
});
function drawHoverOverlay(ctx, scene, camera, hoverNodeId) {
if (!hoverNodeId) return;
const node = scene.nodesById.get(hoverNodeId);
const bounds = worldBoundsToScreen(nodeWorldBounds(node), camera);
drawOutline(ctx, bounds, { color: "#0d9488", width: 1 });
}
scene.nodesById는 바뀌지 않고 toolState.hoverNodeId만 바뀝니다. 이 구분이 undo/redo와 hover preview를 섞이지 않게 합니다.
hover는 hit test의 preview다
pointer move마다 world 좌표를 구하고 hit test를 수행합니다.
pointermove
-> screenToWorld
-> hitTest
-> hoverNodeId
선택과 달리 hover는 문서 편집이 아닙니다. command history에 들어가지 않습니다. transient UI state입니다.
hover outline은 overlay로 그린다
hover된 node의 bounds를 screen 좌표로 변환해 outline을 그립니다.
hover node world bounds
-> screen bounds
-> overlay outline
선택 outline과 hover outline의 색상이나 우선순위는 다르게 둘 수 있습니다.
pointer move는 과하게 일어날 수 있다
pointer move는 자주 발생합니다. 처음에는 단순히 매번 hit test해도 됩니다. 나중에는 rAF 안에서 마지막 pointer 위치만 처리하거나 spatial index를 붙일 수 있습니다.
하지만 최적화보다 중요한 것은 hover 상태가 scene model의 영구 상태와 섞이지 않는 것입니다.
오늘의 핵심
hover outline은 hit testing, overlay renderer, transient tool state가 만나는 첫 기능입니다.
model은 변하지 않는다.
tool state만 바뀐다.
overlay가 피드백을 그린다.