diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 5f9cef1400..d832eb613c 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -1,4 +1,4 @@ -import type { CanvasColour, Dictionary, Direction, IBoundaryNodes, IContextMenuOptions, INodeSlot, INodeInputSlot, INodeOutputSlot, IOptionalSlotData, Point, Rect, Rect32, Size, IContextMenuValue, ISlotType, ConnectingLink, NullableProperties, Positionable, ReadOnlyPoint } from "./interfaces" +import type { CanvasColour, Dictionary, Direction, IBoundaryNodes, IContextMenuOptions, INodeSlot, INodeInputSlot, INodeOutputSlot, IOptionalSlotData, Point, Rect, Rect32, Size, IContextMenuValue, ISlotType, ConnectingLink, NullableProperties, Positionable, ReadOnlyPoint, ReadOnlyRect } from "./interfaces" import type { IWidget, TWidgetValue } from "./types/widgets" import { LGraphNode, type NodeId } from "./LGraphNode" import type { CanvasDragEvent, CanvasMouseEvent, CanvasWheelEvent, CanvasEventDetail, CanvasPointerEvent } from "./types/events" @@ -7838,17 +7838,20 @@ export class LGraphCanvas { } /** - * Centers the camera on a given node (animated version) - * @method animateToNode - **/ - animateToNode( - node: LGraphNode, + * Starts an animation to fit the view around the specified selection of nodes. + * @param bounds The bounds to animate the view to, defined by a rectangle. + * @param animationParameters Various parameters for the camera movement animation. + */ + animateToBounds( + bounds: ReadOnlyRect, { duration = 350, zoom = 0.75, easing = EaseFunction.EASE_IN_OUT_QUAD }: { + /** Duration of the animation in milliseconds. */ duration?: number, + /** Relative target zoom level. 1 means the view is fit exactly on the bounding box. */ zoom?: number, easing?: EaseFunction } = {} @@ -7871,19 +7874,18 @@ export class LGraphCanvas { 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) + const targetScaleX = (zoom * cw) / Math.max(bounds[2], 300) + const targetScaleY = (zoom * ch) / Math.max(bounds[3], 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 } + targetX = -bounds[0] - bounds[2] * 0.5 + (cw * 0.5) / targetScale + targetY = -bounds[1] - bounds[3] * 0.5 + (ch * 0.5) / targetScale + const animate = (timestamp: number) => { const elapsed = timestamp - startTimestamp const progress = Math.min(elapsed / duration, 1) diff --git a/src/LGraphGroup.ts b/src/LGraphGroup.ts index 9d7f6c9f38..1406508d98 100644 --- a/src/LGraphGroup.ts +++ b/src/LGraphGroup.ts @@ -1,4 +1,4 @@ -import type { IContextMenuValue, IPinnable, Point, Positionable, Size } from "./interfaces" +import type { IContextMenuValue, IPinnable, Point, Positionable, ReadOnlyRect, Size } from "./interfaces" import type { LGraph } from "./LGraph" import type { ISerialisedGroup } from "./types/serialisation" import { LiteGraph } from "./litegraph" @@ -229,6 +229,17 @@ export class LGraphGroup implements Positionable, IPinnable { * @param padding Value in graph units to add to all sides of the group. Default: 10 */ resizeTo(objects: Iterable, padding: number = 10): void { + const boundingBox = LGraphGroup.createBounds(objects, padding); + if(boundingBox === null) return + + this.pos[0] = boundingBox[0] + this.pos[1] = boundingBox[1] - this.titleHeight + this.size[0] = boundingBox[2] + this.size[1] = boundingBox[3] + this.titleHeight + } + + static createBounds(objects: Iterable, padding: number = 10): ReadOnlyRect | null + { const bounds = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]) for (const obj of objects) { @@ -238,12 +249,14 @@ export class LGraphGroup implements Positionable, IPinnable { bounds[2] = Math.max(bounds[2], rect[0] + rect[2]) bounds[3] = Math.max(bounds[3], rect[1] + rect[3]) } - if (!bounds.every(x => isFinite(x))) return + if (!bounds.every(x => isFinite(x))) return null - this.pos[0] = bounds[0] - padding - this.pos[1] = bounds[1] - padding - this.titleHeight - this.size[0] = bounds[2] - bounds[0] + (2 * padding) - this.size[1] = bounds[3] - bounds[1] + (2 * padding) + this.titleHeight + return [ + bounds[0] - padding, + bounds[1] - padding, + bounds[2] - bounds[0] + (2 * padding), + bounds[3] - bounds[1] + (2 * padding) + ] } /**