From 9b0f572ca1bdfe74f15046f3d784df0abd096ae1 Mon Sep 17 00:00:00 2001 From: dmx Date: Sun, 3 Nov 2024 17:18:34 +0400 Subject: [PATCH] (feature) animateToNode method --- src/LGraphCanvas.ts | 73 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index f2d1357b0..3aab9fcdb 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -115,6 +115,14 @@ export interface LGraphCanvasState { readOnly: boolean } +/** Easing functions for canvas animations */ +const easeFunctions = { + linear: (t: number) => t, + easeInQuad: (t: number) => t * t, + easeOutQuad: (t: number) => t * (2 - t), + easeInOutQuad: (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t) +} + /** * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked @@ -3291,6 +3299,71 @@ export class LGraphCanvas { }) } } + + /** + * Centers the camera on a given node (animated version) + * @method animateToNode + **/ + animateToNode(node: LGraphNode, duration: number = 350, zoom: number = 0.75, easing: string = 'easeInOutQuad') { + let animationId = null + const startTimestamp = performance.now() + const startX = this.ds.offset[0] + const startY = this.ds.offset[1] + const startScale = this.ds.scale + const cw = this.canvas.width / window.devicePixelRatio + const ch = this.canvas.height / window.devicePixelRatio + + let targetScale = startScale + let targetX = startX + let targetY = startY + + if (zoom > 0) { + const targetScaleX = (zoom * cw) / Math.max(node.size[0], 300) + const targetScaleY = (zoom * ch) / Math.max(node.size[1], 300) + + // Choose the smaller scale to ensure the node fits into the viewport + // Ensure we don't go over the max scale + targetScale = Math.min(targetScaleX, targetScaleY, this.ds.max_scale) + targetX = -node.pos[0] - node.size[0] * 0.5 + (cw * 0.5) / targetScale + targetY = -node.pos[1] - node.size[1] * 0.5 + (ch * 0.5) / targetScale + } else { + targetX = -node.pos[0] - node.size[0] * 0.5 + (cw * 0.5) / targetScale + targetY = -node.pos[1] - node.size[1] * 0.5 + (ch * 0.5) / targetScale + } + + const easeFunction = easeFunctions[easing] || easeFunctions.linear + + const animate = (timestamp: number) => { + const elapsed = timestamp - startTimestamp + const progress = Math.min(elapsed / duration, 1) + const easedProgress = easeFunction(progress) + + this.ds.offset[0] = startX + (targetX - startX) * easedProgress + this.ds.offset[1] = startY + (targetY - startY) * easedProgress + + if (zoom > 0) { + this.ds.scale = startScale + (targetScale - startScale) * easedProgress + } + + this.setDirty(true, true) + + if (this.onPositionChanged) { + this.onPositionChanged({ + x: this.ds.offset[0], + y: this.ds.offset[1] + }) + } + + if (progress < 1) { + animationId = requestAnimationFrame(animate) + } else { + cancelAnimationFrame(animationId) + } + } + + animationId = requestAnimationFrame(animate) + } + /** * adds some useful properties to a mouse event, like the position in graph coordinates **/