diff --git a/components/CanvasEditor.vue b/components/CanvasEditor.vue index d29d2a3..6c1b3a5 100644 --- a/components/CanvasEditor.vue +++ b/components/CanvasEditor.vue @@ -8,6 +8,7 @@ const rotation: Record = { left: "90", right: "270" }; +type Element = { type: 'node' | 'edge', id: string }; interface ActionMap { move: Position; @@ -73,14 +74,16 @@ const canvas = defineModel({ required: true, }); const dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5); const focusing = ref(), editing = ref(); -const canvasRef = useTemplateRef('canvasRef'); +const canvasRef = useTemplateRef('canvasRef'), transformRef = useTemplateRef('transformRef'); const nodes = useTemplateRef[]>('nodes'); +const xTween = useTween(dispX, linear, updateTransform), yTween = useTween(dispY, linear, updateTransform), zoomTween = useTween(zoom, linear, updateTransform); + const focusedNode = computed(() => nodes.value?.find(e => !!e && e.id === focusing.value)), editedNode = computed(() => nodes.value?.find(e => !!e && e.id === editing.value)); const edges = computed(() => { - return canvas.value.edges.map(e => { - const from = canvas.value.nodes.find(f => f.id === e.fromNode), to = canvas.value.nodes.find(f => f.id === e.toNode); + return canvas.value.edges?.map(e => { + const from = canvas.value.nodes!.find(f => f.id === e.fromNode), to = canvas.value.nodes!.find(f => f.id === e.toNode); const path = getPath(from!, e.fromSide, to!, e.toSide)!; return { ...e, from, to, path }; }); @@ -91,10 +94,15 @@ const historyPos = ref(-1); const historyCursor = computed(() => history.value.length > 0 && historyPos.value > -1 ? history.value[historyPos.value] : undefined); const reset = (_: MouseEvent) => { - zoom.value = minZoom.value; + zoom.value = minZoom.value; + zoomTween.refresh(); - dispX.value = 0; + dispX.value = 0; + xTween.refresh(); dispY.value = 0; + yTween.refresh(); + + updateTransform(); } function addAction(event: T, actions: HistoryAction[]) @@ -111,6 +119,11 @@ onMounted(() => { dispY.value -= (lastY - e.clientY) / zoom.value; lastX = e.clientX; lastY = e.clientY; + + xTween.refresh(); + yTween.refresh(); + + updateTransform(); }; const dragEnd = (e: MouseEvent) => { window.removeEventListener('mouseup', dragEnd); @@ -146,10 +159,12 @@ onMounted(() => { const centerX = (box.x + box.width / 2), centerY = (box.y + box.height / 2); const mousex = centerX - e.clientX, mousey = centerY - e.clientY; - dispX.value -= mousex / (diff * zoom.value) - mousex / zoom.value; - dispY.value -= mousey / (diff * zoom.value) - mousey / zoom.value; + xTween.update(dispX.value - (mousex / (diff * zoom.value) - mousex / zoom.value), 250); + yTween.update(dispY.value - (mousey / (diff * zoom.value) - mousey / zoom.value), 250); - zoom.value = clamp(zoom.value * diff, minZoom.value, 3); + zoomTween.update(clamp(zoom.value * diff, minZoom.value, 3), 250); + + updateTransform(); }, { passive: true }); canvasRef.value?.addEventListener('touchstart', (e) => { ({ x: lastX, y: lastY } = center(e.touches)); @@ -193,19 +208,35 @@ onMounted(() => { if(e.touches.length === 2) { const dist = distance(e.touches); - const diff = lastDistance / dist; + const diff = dist / lastDistance; zoom.value = clamp(zoom.value * diff, minZoom.value, 3); //@TODO } + + zoomTween.refresh(); + xTween.refresh(); + yTween.refresh(); + + updateTransform(); }; + + updateTransform(); }); +function updateTransform() +{ + if(transformRef.value) + { + transformRef.value.style.transform = `scale3d(${zoomTween.current()}, ${zoomTween.current()}, 1) translate3d(${xTween.current()}px, ${yTween.current()}px, 0)`; + transformRef.value.style.setProperty('--tw-scale', zoomTween.current().toString()); + } +} function moveNode(ids: string[], deltax: number, deltay: number) { const actions: HistoryAction<'move'>[] = []; for(const id of ids) { - const node = canvas.value.nodes.find(e => e.id === id)!; + const node = canvas.value.nodes!.find(e => e.id === id)!; actions.push({ element: id, from: { x: node.x - deltax, y: node.y - deltay }, to: { x: node.x, y: node.y } }); } @@ -217,7 +248,7 @@ function resizeNode(ids: string[], deltax: number, deltay: number, deltaw: numbe const actions: HistoryAction<'resize'>[] = []; for(const id of ids) { - const node = canvas.value.nodes.find(e => e.id === id)!; + const node = canvas.value.nodes!.find(e => e.id === id)!; actions.push({ element: id, from: { x: node.x - deltax, y: node.y - deltay, w: node.width - deltaw, h: node.height - deltah }, to: { x: node.x, y: node.y, w: node.width, h: node.height } }); } @@ -248,7 +279,11 @@ function createNode(e: MouseEvent) { let box = canvasRef.value?.getBoundingClientRect()!; const node: CanvasNode = { id: getID(16), x: (e.layerX / zoom.value) - box.width / 2 - 50, y: (e.layerY / zoom.value) - box.height / 2 - 25, width: 100, height: 50, type: 'text' }; - canvas.value.nodes.push(node); + + if(!canvas.value.nodes) + canvas.value.nodes = [node]; + else + canvas.value.nodes.push(node); addAction('create', [{ element: node.id, from: undefined, to: node }]); } @@ -259,10 +294,8 @@ function removeNode(ids: string[]) for(const id of ids) { - const index = canvas.value.nodes.findIndex(e => e.id === id); - actions.push({ element: id, from: canvas.value.nodes.splice(index, 1)[0], to: undefined }); - - console.log("Removing %s", id); + const index = canvas.value.nodes!.findIndex(e => e.id === id); + actions.push({ element: id, from: canvas.value.nodes!.splice(index, 1)[0], to: undefined }); } addAction('remove', actions); @@ -273,9 +306,9 @@ function editNodeProperty(ids: string[], property: T for(const id of ids) { - const copy = JSON.parse(JSON.stringify(canvas.value.nodes.find(e => e.id === id)!)) as CanvasNode; - canvas.value.nodes.find(e => e.id === id)![property] = value; - actions.push({ element: id, from: copy, to: canvas.value.nodes.find(e => e.id === id)! }); + const copy = JSON.parse(JSON.stringify(canvas.value.nodes!.find(e => e.id === id)!)) as CanvasNode; + canvas.value.nodes!.find(e => e.id === id)![property] = value; + actions.push({ element: id, from: copy, to: canvas.value.nodes!.find(e => e.id === id)! }); } addAction('property', actions); @@ -304,7 +337,7 @@ const undo = () => { for(const action of historyCursor.value.actions) { - const node = canvas.value.nodes.find(e => e.id === action.element)!; + const node = canvas.value.nodes!.find(e => e.id === action.element)!; switch(historyCursor.value.event) { case 'move': @@ -332,21 +365,21 @@ const undo = () => { case 'create': { const a = action as HistoryAction<'create'>; - const index = canvas.value.nodes.findIndex(e => e.id === action.element); - canvas.value.nodes.splice(index, 1); + const index = canvas.value.nodes!.findIndex(e => e.id === action.element); + canvas.value.nodes!.splice(index, 1); break; } case 'remove': { const a = action as HistoryAction<'remove'>; - canvas.value.nodes.push(a.from!); + canvas.value.nodes!.push(a.from!); break; } case 'property': { const a = action as HistoryAction<'property'>; - const index = canvas.value.nodes.findIndex(e => e.id === action.element); - canvas.value.nodes[index] = a.from; + const index = canvas.value.nodes!.findIndex(e => e.id === action.element); + canvas.value.nodes![index] = a.from; break; } } @@ -370,7 +403,7 @@ const redo = () => { for(const action of historyCursor.value.actions) { - const node = canvas.value.nodes.find(e => e.id === action.element)!; + const node = canvas.value.nodes!.find(e => e.id === action.element)!; switch(historyCursor.value.event) { case 'move': @@ -398,21 +431,21 @@ const redo = () => { case 'create': { const a = action as HistoryAction<'remove'>; - canvas.value.nodes.push(a.to!); + canvas.value.nodes!.push(a.to!); break; } case 'remove': { const a = action as HistoryAction<'remove'>; - const index = canvas.value.nodes.findIndex(e => e.id === action.element); - canvas.value.nodes.splice(index, 1); + const index = canvas.value.nodes!.findIndex(e => e.id === action.element); + canvas.value.nodes!.splice(index, 1); break; } case 'property': { const a = action as HistoryAction<'property'>; - const index = canvas.value.nodes.findIndex(e => e.id === action.element); - canvas.value.nodes[index] = a.to; + const index = canvas.value.nodes!.findIndex(e => e.id === action.element); + canvas.value.nodes![index] = a.to; break; } } @@ -429,16 +462,16 @@ useShortcuts({ \ No newline at end of file diff --git a/composables/useTween.ts b/composables/useTween.ts new file mode 100644 index 0000000..fe9b7df --- /dev/null +++ b/composables/useTween.ts @@ -0,0 +1,54 @@ +import { clamp, lerp } from "~/shared/general.utils"; + +export const linear = (progress: number): number => progress; +export const ease = (progress: number): number => -(Math.cos(Math.PI * progress) - 1) / 2; + +export function useTween(ref: Ref, animation: (progress: number) => number, then: () => void) +{ + let initial = ref.value, current = ref.value, end = ref.value, progress = 0, time = 0, animationFrame: number, stop = true, last = 0; + + function loop(t: DOMHighResTimeStamp) + { + const elapsed = t - last; + progress = clamp(progress + elapsed, 0, time); + last = t; + + const step = animation(clamp(progress / time, 0, 1)); + current = lerp(initial, end, step); + then(); + + if(progress < time && !stop) + { + animationFrame = requestAnimationFrame(loop); + } + else + { + progress = 0; + stop = true; + } + } + + return { + stop: () => { + cancelAnimationFrame(animationFrame); + stop = true; + }, + update: (target: number, duration: number) => { + initial = current; + time = duration + progress; + end = target; + + ref.value = target; + + if(stop) + { + stop = false; + last = performance.now(); + + loop(performance.now()); + } + }, + refresh: () => { current = ref.value; }, + current: () => { return current; }, + }; +} \ No newline at end of file diff --git a/db.sqlite b/db.sqlite index 4bb3e5f..eee28c5 100644 Binary files a/db.sqlite and b/db.sqlite differ diff --git a/db.sqlite-shm b/db.sqlite-shm index da89b52..fe9ac28 100644 Binary files a/db.sqlite-shm and b/db.sqlite-shm differ diff --git a/db.sqlite-wal b/db.sqlite-wal index 3089977..e69de29 100644 Binary files a/db.sqlite-wal and b/db.sqlite-wal differ diff --git a/pages/explore/edit/index.vue b/pages/explore/edit/index.vue index 9d51850..27f9352 100644 --- a/pages/explore/edit/index.vue +++ b/pages/explore/edit/index.vue @@ -206,7 +206,7 @@