Compare commits

...

6 Commits

Author SHA1 Message Date
bymyself
c8e1f494fa Fix Rectangle/Rect type compatibility and unify type system
This commit addresses PR review comments by fixing the fundamental
type incompatibility between Rectangle (Array<number>) and strict
tuple types (Rect = [x, y, width, height]).

Key changes:
- Updated Rectangle class methods to accept both Rect and Rectangle unions
- Fixed all measure functions (containsRect, overlapBounding, etc.) to accept Rectangle
- Updated boundingRect interfaces to consistently use Rectangle instead of Rect
- Fixed all tests and Vue components to use Rectangle instead of array literals
- Resolved Point type conflicts between litegraph and pathRenderer modules
- Removed ReadOnlyPoint types and unified with Point arrays

The type system is now consistent: boundingRect properties return Rectangle
objects with full functionality, while Rect remains as a simple tuple type
for data interchange.
2025-09-19 22:25:01 -07:00
bymyself
c82c3c24f7 [refactor] convert Rectangle from Float64Array to Array inheritance - addresses review feedback
Replace Float64Array inheritance with Array<number> to remove legacy typed array usage.
Maintains compatibility with array indexing (rect[0], rect[1], etc.) and property access
(rect.x, rect.y, etc.) while adding .set() and .subarray() methods for compatibility.

Co-authored-by: webfiltered <webfiltered@users.noreply.github.com>
2025-09-19 20:33:54 -07:00
Christian Byrne
fffa81c9b5 Update src/lib/litegraph/src/LGraphCanvas.ts
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
2025-09-19 20:33:54 -07:00
bymyself
db35e0b7d2 Update test snapshots to reflect Float32Array removal
- Updated snapshots to show regular arrays instead of Float32Array
- Regenerated snapshots for LGraph, ConfigureGraph tests
- All actively running tests now have correct array expectations

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-19 20:33:53 -07:00
bymyself
0c6eeb0632 [perf] Fix Float32Array test assertions and link adapter
Fix the remaining Float32Array usage that was causing test failures:
- Update test assertions to expect regular arrays instead of Float32Array
- Convert link adapter Float32Array creation to regular arrays

Resolves: AssertionError: expected [ 50, 60 ] to deeply equal Float32Array[ 50, 60 ]
2025-09-19 16:38:09 -07:00
bymyself
fca95ad07e [perf] Remove legacy Float32Array usage from LiteGraph
Removes Float32Array usage throughout LiteGraph codebase, eliminating
type conversion overhead for Canvas 2D rendering operations.

## Changes Made

### Core Data Structures
- **LGraphNode**: Convert position/size arrays from Float32Array to regular arrays
- **LLink**: Convert coordinate storage from Float32Array to regular arrays
- **Reroute**: Replace malloc pattern with standard properties
- **LGraphGroup**: Convert boundary arrays from Float32Array to regular arrays

### Rendering Optimizations
- **LGraphCanvas**: Convert static rendering buffers to regular arrays
- **Drawing Functions**: Replace .set() method calls with direct array assignment
- **Measurement**: Update bounds calculation to use regular arrays

### Type System Updates
- **Interfaces**: Update Point/Size/Rect types to support both regular arrays and typed arrays
- **API Compatibility**: Maintain all existing property names and method signatures

## Performance Benefits

- Eliminates Float32Array conversion overhead in Canvas 2D operations
- Reduces memory allocation complexity (removed malloc patterns)
- Improves TypeScript integration with native array support
- Maintains full API compatibility with zero breaking changes

## Background

Float32Array usage was originally designed for WebGL integration, but the
current Canvas 2D rendering pipeline doesn't use WebGL, making the typed
arrays an unnecessary performance overhead. Every Canvas 2D operation that
reads coordinates from Float32Array incurs type conversion costs.
2025-09-19 16:38:09 -07:00
32 changed files with 482 additions and 431 deletions

View File

@@ -4,7 +4,7 @@ import type { Ref } from 'vue'
import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync' import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync'
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems' import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags' import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces' import type { Rect } from '@/lib/litegraph/src/interfaces'
import { LGraphNode } from '@/lib/litegraph/src/litegraph' import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
@@ -71,7 +71,7 @@ export function useSelectionToolboxPosition(
visible.value = true visible.value = true
// Get bounds for all selected items // Get bounds for all selected items
const allBounds: ReadOnlyRect[] = [] const allBounds: Rect[] = []
for (const item of selectableItems) { for (const item of selectableItems) {
// Skip items without valid IDs // Skip items without valid IDs
if (item.id == null) continue if (item.id == null) continue

View File

@@ -1,4 +1,4 @@
import type { Point, ReadOnlyRect, Rect } from './interfaces' import type { Point, Rect } from './interfaces'
import { EaseFunction, Rectangle } from './litegraph' import { EaseFunction, Rectangle } from './litegraph'
export interface DragAndScaleState { export interface DragAndScaleState {
@@ -188,10 +188,7 @@ export class DragAndScale {
* Fits the view to the specified bounds. * Fits the view to the specified bounds.
* @param bounds The bounds to fit the view to, defined by a rectangle. * @param bounds The bounds to fit the view to, defined by a rectangle.
*/ */
fitToBounds( fitToBounds(bounds: Rect, { zoom = 0.75 }: { zoom?: number } = {}): void {
bounds: ReadOnlyRect,
{ zoom = 0.75 }: { zoom?: number } = {}
): void {
const cw = this.element.width / window.devicePixelRatio const cw = this.element.width / window.devicePixelRatio
const ch = this.element.height / window.devicePixelRatio const ch = this.element.height / window.devicePixelRatio
let targetScale = this.scale let targetScale = this.scale
@@ -223,7 +220,7 @@ export class DragAndScale {
* @param bounds The bounds to animate the view to, defined by a rectangle. * @param bounds The bounds to animate the view to, defined by a rectangle.
*/ */
animateToBounds( animateToBounds(
bounds: ReadOnlyRect, bounds: Rect | Rectangle,
setDirty: () => void, setDirty: () => void,
{ {
duration = 350, duration = 350,

View File

@@ -4,6 +4,7 @@ import {
SUBGRAPH_INPUT_ID, SUBGRAPH_INPUT_ID,
SUBGRAPH_OUTPUT_ID SUBGRAPH_OUTPUT_ID
} from '@/lib/litegraph/src/constants' } from '@/lib/litegraph/src/constants'
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import type { UUID } from '@/lib/litegraph/src/utils/uuid' import type { UUID } from '@/lib/litegraph/src/utils/uuid'
import { createUuidv4, zeroUuid } from '@/lib/litegraph/src/utils/uuid' import { createUuidv4, zeroUuid } from '@/lib/litegraph/src/utils/uuid'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
@@ -1707,7 +1708,12 @@ export class LGraph
...subgraphNode.subgraph.groups ...subgraphNode.subgraph.groups
].map((p: { pos: Point; size?: Size }): HasBoundingRect => { ].map((p: { pos: Point; size?: Size }): HasBoundingRect => {
return { return {
boundingRect: [p.pos[0], p.pos[1], p.size?.[0] ?? 0, p.size?.[1] ?? 0] boundingRect: new Rectangle(
p.pos[0],
p.pos[1],
p.size?.[0] ?? 0,
p.size?.[1] ?? 0
)
} }
}) })
const bounds = createBounds(positionables) ?? [0, 0, 0, 0] const bounds = createBounds(positionables) ?? [0, 0, 0, 0]

View File

@@ -47,8 +47,6 @@ import type {
NullableProperties, NullableProperties,
Point, Point,
Positionable, Positionable,
ReadOnlyPoint,
ReadOnlyRect,
Rect, Rect,
Size Size
} from './interfaces' } from './interfaces'
@@ -236,11 +234,11 @@ export class LGraphCanvas
implements CustomEventDispatcher<LGraphCanvasEventMap> implements CustomEventDispatcher<LGraphCanvasEventMap>
{ {
// Optimised buffers used during rendering // Optimised buffers used during rendering
static #temp = new Float32Array(4) static #temp = [0, 0, 0, 0] satisfies Rect
static #temp_vec2 = new Float32Array(2) static #temp_vec2 = [0, 0] satisfies Point
static #tmp_area = new Float32Array(4) static #tmp_area = [0, 0, 0, 0] satisfies Rect
static #margin_area = new Float32Array(4) static #margin_area = [0, 0, 0, 0] satisfies Rect
static #link_bounding = new Float32Array(4) static #link_bounding = [0, 0, 0, 0] satisfies Rect
static DEFAULT_BACKGROUND_IMAGE = static DEFAULT_BACKGROUND_IMAGE =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=' 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII='
@@ -628,7 +626,7 @@ export class LGraphCanvas
dirty_area?: Rect | null dirty_area?: Rect | null
/** @deprecated Unused */ /** @deprecated Unused */
node_in_panel?: LGraphNode | null node_in_panel?: LGraphNode | null
last_mouse: ReadOnlyPoint = [0, 0] last_mouse: Point = [0, 0]
last_mouseclick: number = 0 last_mouseclick: number = 0
graph: LGraph | Subgraph | null graph: LGraph | Subgraph | null
get _graph(): LGraph | Subgraph { get _graph(): LGraph | Subgraph {
@@ -2633,7 +2631,7 @@ export class LGraphCanvas
pointer: CanvasPointer, pointer: CanvasPointer,
node?: LGraphNode | undefined node?: LGraphNode | undefined
): void { ): void {
const dragRect = new Float32Array(4) const dragRect: [number, number, number, number] = [0, 0, 0, 0]
dragRect[0] = e.canvasX dragRect[0] = e.canvasX
dragRect[1] = e.canvasY dragRect[1] = e.canvasY
@@ -3166,7 +3164,7 @@ export class LGraphCanvas
LGraphCanvas.active_canvas = this LGraphCanvas.active_canvas = this
this.adjustMouseEvent(e) this.adjustMouseEvent(e)
const mouse: ReadOnlyPoint = [e.clientX, e.clientY] const mouse: Point = [e.clientX, e.clientY]
this.mouse[0] = mouse[0] this.mouse[0] = mouse[0]
this.mouse[1] = mouse[1] this.mouse[1] = mouse[1]
const delta = [mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1]] const delta = [mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1]]
@@ -4055,7 +4053,10 @@ export class LGraphCanvas
this.setDirty(true) this.setDirty(true)
} }
#handleMultiSelect(e: CanvasPointerEvent, dragRect: Float32Array) { #handleMultiSelect(
e: CanvasPointerEvent,
dragRect: [number, number, number, number]
) {
// Process drag // Process drag
// Convert Point pair (pos, offset) to Rect // Convert Point pair (pos, offset) to Rect
const { graph, selectedItems, subgraph } = this const { graph, selectedItems, subgraph } = this
@@ -4826,7 +4827,7 @@ export class LGraphCanvas
} }
/** Get the target snap / highlight point in graph space */ /** Get the target snap / highlight point in graph space */
#getHighlightPosition(): ReadOnlyPoint { #getHighlightPosition(): Point {
return LiteGraph.snaps_for_comfy return LiteGraph.snaps_for_comfy
? this.linkConnector.state.snapLinksPos ?? ? this.linkConnector.state.snapLinksPos ??
this._highlight_pos ?? this._highlight_pos ??
@@ -4841,7 +4842,7 @@ export class LGraphCanvas
*/ */
#renderSnapHighlight( #renderSnapHighlight(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
highlightPos: ReadOnlyPoint highlightPos: Point
): void { ): void {
const linkConnectorSnap = !!this.linkConnector.state.snapLinksPos const linkConnectorSnap = !!this.linkConnector.state.snapLinksPos
if (!this._highlight_pos && !linkConnectorSnap) return if (!this._highlight_pos && !linkConnectorSnap) return
@@ -5183,7 +5184,8 @@ export class LGraphCanvas
// clip if required (mask) // clip if required (mask)
const shape = node._shape || RenderShape.BOX const shape = node._shape || RenderShape.BOX
const size = LGraphCanvas.#temp_vec2 const size = LGraphCanvas.#temp_vec2
size.set(node.renderingSize) size[0] = node.renderingSize[0]
size[1] = node.renderingSize[1]
if (node.collapsed) { if (node.collapsed) {
ctx.font = this.inner_text_font ctx.font = this.inner_text_font
@@ -5378,7 +5380,10 @@ export class LGraphCanvas
// Normalised node dimensions // Normalised node dimensions
const area = LGraphCanvas.#tmp_area const area = LGraphCanvas.#tmp_area
area.set(node.boundingRect) area[0] = node.boundingRect[0]
area[1] = node.boundingRect[1]
area[2] = node.boundingRect[2]
area[3] = node.boundingRect[3]
area[0] -= node.pos[0] area[0] -= node.pos[0]
area[1] -= node.pos[1] area[1] -= node.pos[1]
@@ -5480,7 +5485,10 @@ export class LGraphCanvas
shape = RenderShape.ROUND shape = RenderShape.ROUND
) { ) {
const snapGuide = LGraphCanvas.#temp const snapGuide = LGraphCanvas.#temp
snapGuide.set(item.boundingRect) snapGuide[0] = item.boundingRect[0]
snapGuide[1] = item.boundingRect[1]
snapGuide[2] = item.boundingRect[2]
snapGuide[3] = item.boundingRect[3]
// Not all items have pos equal to top-left of bounds // Not all items have pos equal to top-left of bounds
const { pos } = item const { pos } = item
@@ -5920,8 +5928,8 @@ export class LGraphCanvas
*/ */
renderLink( renderLink(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
a: ReadOnlyPoint, a: Point,
b: ReadOnlyPoint, b: Point,
link: LLink | null, link: LLink | null,
skip_border: boolean, skip_border: boolean,
flow: number | null, flow: number | null,
@@ -5938,9 +5946,9 @@ export class LGraphCanvas
/** When defined, render data will be saved to this reroute instead of the {@link link}. */ /** When defined, render data will be saved to this reroute instead of the {@link link}. */
reroute?: Reroute reroute?: Reroute
/** Offset of the bezier curve control point from {@link a point a} (output side) */ /** Offset of the bezier curve control point from {@link a point a} (output side) */
startControl?: ReadOnlyPoint startControl?: Point
/** Offset of the bezier curve control point from {@link b point b} (input side) */ /** Offset of the bezier curve control point from {@link b point b} (input side) */
endControl?: ReadOnlyPoint endControl?: Point
/** Number of sublines (useful to represent vec3 or rgb) @todo If implemented, refactor calculations out of the loop */ /** Number of sublines (useful to represent vec3 or rgb) @todo If implemented, refactor calculations out of the loop */
num_sublines?: number num_sublines?: number
/** Whether this is a floating link segment */ /** Whether this is a floating link segment */
@@ -8411,7 +8419,7 @@ export class LGraphCanvas
* Starts an animation to fit the view around the specified selection of nodes. * 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 bounds The bounds to animate the view to, defined by a rectangle.
*/ */
animateToBounds(bounds: ReadOnlyRect, options: AnimationOptions = {}) { animateToBounds(bounds: Rect | Rectangle, options: AnimationOptions = {}) {
const setDirty = () => this.setDirty(true, true) const setDirty = () => this.setDirty(true, true)
this.ds.animateToBounds(bounds, setDirty, options) this.ds.animateToBounds(bounds, setDirty, options)
} }

View File

@@ -1,4 +1,5 @@
import { NullGraphError } from '@/lib/litegraph/src/infrastructure/NullGraphError' import { NullGraphError } from '@/lib/litegraph/src/infrastructure/NullGraphError'
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import type { LGraph } from './LGraph' import type { LGraph } from './LGraph'
import { LGraphCanvas } from './LGraphCanvas' import { LGraphCanvas } from './LGraphCanvas'
@@ -40,15 +41,15 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
title: string title: string
font?: string font?: string
font_size: number = LiteGraph.DEFAULT_GROUP_FONT || 24 font_size: number = LiteGraph.DEFAULT_GROUP_FONT || 24
_bounding: Float32Array = new Float32Array([ _bounding: [number, number, number, number] = [
10, 10,
10, 10,
LGraphGroup.minWidth, LGraphGroup.minWidth,
LGraphGroup.minHeight LGraphGroup.minHeight
]) ]
_pos: Point = this._bounding.subarray(0, 2) _pos: Point = [10, 10]
_size: Size = this._bounding.subarray(2, 4) _size: Size = [LGraphGroup.minWidth, LGraphGroup.minHeight]
/** @deprecated See {@link _children} */ /** @deprecated See {@link _children} */
_nodes: LGraphNode[] = [] _nodes: LGraphNode[] = []
_children: Set<Positionable> = new Set() _children: Set<Positionable> = new Set()
@@ -107,8 +108,13 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
this._size[1] = Math.max(LGraphGroup.minHeight, v[1]) this._size[1] = Math.max(LGraphGroup.minHeight, v[1])
} }
get boundingRect() { get boundingRect(): Rectangle {
return this._bounding return Rectangle.from([
this._pos[0],
this._pos[1],
this._size[0],
this._size[1]
])
} }
get nodes() { get nodes() {
@@ -145,14 +151,17 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
configure(o: ISerialisedGroup): void { configure(o: ISerialisedGroup): void {
this.id = o.id this.id = o.id
this.title = o.title this.title = o.title
this._bounding.set(o.bounding) this._pos[0] = o.bounding[0]
this._pos[1] = o.bounding[1]
this._size[0] = o.bounding[2]
this._size[1] = o.bounding[3]
this.color = o.color this.color = o.color
this.flags = o.flags || this.flags this.flags = o.flags || this.flags
if (o.font_size) this.font_size = o.font_size if (o.font_size) this.font_size = o.font_size
} }
serialize(): ISerialisedGroup { serialize(): ISerialisedGroup {
const b = this._bounding const b = [this._pos[0], this._pos[1], this._size[0], this._size[1]]
return { return {
id: this.id, id: this.id,
title: this.title, title: this.title,
@@ -210,7 +219,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
) )
if (LiteGraph.highlight_selected_group && this.selected) { if (LiteGraph.highlight_selected_group && this.selected) {
strokeShape(ctx, this._bounding, { strokeShape(ctx, this.boundingRect, {
title_height: this.titleHeight, title_height: this.titleHeight,
padding padding
}) })
@@ -251,7 +260,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
// Move nodes we overlap the centre point of // Move nodes we overlap the centre point of
for (const node of nodes) { for (const node of nodes) {
if (containsCentre(this._bounding, node.boundingRect)) { if (containsCentre(this.boundingRect, node.boundingRect)) {
this._nodes.push(node) this._nodes.push(node)
children.add(node) children.add(node)
} }
@@ -259,12 +268,13 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
// Move reroutes we overlap the centre point of // Move reroutes we overlap the centre point of
for (const reroute of reroutes.values()) { for (const reroute of reroutes.values()) {
if (isPointInRect(reroute.pos, this._bounding)) children.add(reroute) if (isPointInRect(reroute.pos, this.boundingRect)) children.add(reroute)
} }
// Move groups we wholly contain // Move groups we wholly contain
for (const group of groups) { for (const group of groups) {
if (containsRect(this._bounding, group._bounding)) children.add(group) if (containsRect(this.boundingRect, group.boundingRect))
children.add(group)
} }
groups.sort((a, b) => { groups.sort((a, b) => {

View File

@@ -18,7 +18,6 @@ import type { Reroute, RerouteId } from './Reroute'
import { getNodeInputOnPos, getNodeOutputOnPos } from './canvas/measureSlots' import { getNodeInputOnPos, getNodeOutputOnPos } from './canvas/measureSlots'
import type { IDrawBoundingOptions } from './draw' import type { IDrawBoundingOptions } from './draw'
import { NullGraphError } from './infrastructure/NullGraphError' import { NullGraphError } from './infrastructure/NullGraphError'
import type { ReadOnlyRectangle } from './infrastructure/Rectangle'
import { Rectangle } from './infrastructure/Rectangle' import { Rectangle } from './infrastructure/Rectangle'
import type { import type {
ColorOption, ColorOption,
@@ -37,8 +36,6 @@ import type {
ISlotType, ISlotType,
Point, Point,
Positionable, Positionable,
ReadOnlyPoint,
ReadOnlyRect,
Rect, Rect,
Size Size
} from './interfaces' } from './interfaces'
@@ -387,7 +384,7 @@ export class LGraphNode
* Called once at the start of every frame. Caller may change the values in {@link out}, which will be reflected in {@link boundingRect}. * Called once at the start of every frame. Caller may change the values in {@link out}, which will be reflected in {@link boundingRect}.
* WARNING: Making changes to boundingRect via onBounding is poorly supported, and will likely result in strange behaviour. * WARNING: Making changes to boundingRect via onBounding is poorly supported, and will likely result in strange behaviour.
*/ */
onBounding?(this: LGraphNode, out: Rect): void onBounding?(this: LGraphNode, out: Rectangle): void
console?: string[] console?: string[]
_level?: number _level?: number
_shape?: RenderShape _shape?: RenderShape
@@ -413,12 +410,12 @@ export class LGraphNode
} }
/** @inheritdoc {@link renderArea} */ /** @inheritdoc {@link renderArea} */
#renderArea: Float32Array = new Float32Array(4) #renderArea: [number, number, number, number] = [0, 0, 0, 0]
/** /**
* Rect describing the node area, including shadows and any protrusions. * Rect describing the node area, including shadows and any protrusions.
* Determines if the node is visible. Calculated once at the start of every frame. * Determines if the node is visible. Calculated once at the start of every frame.
*/ */
get renderArea(): ReadOnlyRect { get renderArea(): Rect {
return this.#renderArea return this.#renderArea
} }
@@ -429,12 +426,12 @@ export class LGraphNode
* *
* Determines the node hitbox and other rendering effects. Calculated once at the start of every frame. * Determines the node hitbox and other rendering effects. Calculated once at the start of every frame.
*/ */
get boundingRect(): ReadOnlyRectangle { get boundingRect(): Rectangle {
return this.#boundingRect return this.#boundingRect
} }
/** The offset from {@link pos} to the top-left of {@link boundingRect}. */ /** The offset from {@link pos} to the top-left of {@link boundingRect}. */
get boundingOffset(): ReadOnlyPoint { get boundingOffset(): Point {
const { const {
pos: [posX, posY], pos: [posX, posY],
boundingRect: [bX, bY] boundingRect: [bX, bY]
@@ -443,9 +440,9 @@ export class LGraphNode
} }
/** {@link pos} and {@link size} values are backed by this {@link Rect}. */ /** {@link pos} and {@link size} values are backed by this {@link Rect}. */
_posSize: Float32Array = new Float32Array(4) _posSize: [number, number, number, number] = [0, 0, 0, 0]
_pos: Point = this._posSize.subarray(0, 2) _pos: Point = [0, 0]
_size: Size = this._posSize.subarray(2, 4) _size: Size = [0, 0]
public get pos() { public get pos() {
return this._pos return this._pos
@@ -1653,7 +1650,7 @@ export class LGraphNode
inputs ? inputs.filter((input) => !isWidgetInputSlot(input)).length : 1, inputs ? inputs.filter((input) => !isWidgetInputSlot(input)).length : 1,
outputs ? outputs.length : 1 outputs ? outputs.length : 1
) )
const size = out || new Float32Array([0, 0]) const size = out || [0, 0]
rows = Math.max(rows, 1) rows = Math.max(rows, 1)
// although it should be graphcanvas.inner_text_font size // although it should be graphcanvas.inner_text_font size
const font_size = LiteGraph.NODE_TEXT_SIZE const font_size = LiteGraph.NODE_TEXT_SIZE
@@ -1978,7 +1975,7 @@ export class LGraphNode
* @param out `x, y, width, height` are written to this array. * @param out `x, y, width, height` are written to this array.
* @param ctx The canvas context to use for measuring text. * @param ctx The canvas context to use for measuring text.
*/ */
measure(out: Rect, ctx: CanvasRenderingContext2D): void { measure(out: Rectangle, ctx: CanvasRenderingContext2D): void {
const titleMode = this.title_mode const titleMode = this.title_mode
const renderTitle = const renderTitle =
titleMode != TitleMode.TRANSPARENT_TITLE && titleMode != TitleMode.TRANSPARENT_TITLE &&
@@ -2004,13 +2001,13 @@ export class LGraphNode
/** /**
* returns the bounding of the object, used for rendering purposes * returns the bounding of the object, used for rendering purposes
* @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage * @param out {Rect?} [optional] a place to store the output, to free garbage
* @param includeExternal {boolean?} [optional] set to true to * @param includeExternal {boolean?} [optional] set to true to
* include the shadow and connection points in the bounding calculation * include the shadow and connection points in the bounding calculation
* @returns the bounding box in format of [topleft_cornerx, topleft_cornery, width, height] * @returns the bounding box in format of [topleft_cornerx, topleft_cornery, width, height]
*/ */
getBounding(out?: Rect, includeExternal?: boolean): Rect { getBounding(out?: Rect, includeExternal?: boolean): Rect {
out ||= new Float32Array(4) out ||= [0, 0, 0, 0]
const rect = includeExternal ? this.renderArea : this.boundingRect const rect = includeExternal ? this.renderArea : this.boundingRect
out[0] = rect[0] out[0] = rect[0]
@@ -2031,7 +2028,10 @@ export class LGraphNode
this.onBounding?.(bounds) this.onBounding?.(bounds)
const renderArea = this.#renderArea const renderArea = this.#renderArea
renderArea.set(bounds) renderArea[0] = bounds[0]
renderArea[1] = bounds[1]
renderArea[2] = bounds[2]
renderArea[3] = bounds[3]
// 4 offset for collapsed node connection points // 4 offset for collapsed node connection points
renderArea[0] -= 4 renderArea[0] -= 4
renderArea[1] -= 4 renderArea[1] -= 4
@@ -3174,7 +3174,7 @@ export class LGraphNode
* @returns the position * @returns the position
*/ */
getConnectionPos(is_input: boolean, slot_number: number, out?: Point): Point { getConnectionPos(is_input: boolean, slot_number: number, out?: Point): Point {
out ||= new Float32Array(2) out ||= [0, 0]
const { const {
pos: [nodeX, nodeY], pos: [nodeX, nodeY],
@@ -3841,7 +3841,7 @@ export class LGraphNode
slot.boundingRect[3] = LiteGraph.NODE_SLOT_HEIGHT slot.boundingRect[3] = LiteGraph.NODE_SLOT_HEIGHT
} }
#measureSlots(): ReadOnlyRect | null { #measureSlots(): Rect | null {
const slots: (NodeInputSlot | NodeOutputSlot)[] = [] const slots: (NodeInputSlot | NodeOutputSlot)[] = []
for (const [slotIndex, slot] of this.#concreteInputs.entries()) { for (const [slotIndex, slot] of this.#concreteInputs.entries()) {

View File

@@ -109,7 +109,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
data?: number | string | boolean | { toToolTip?(): string } data?: number | string | boolean | { toToolTip?(): string }
_data?: unknown _data?: unknown
/** Centre point of the link, calculated during render only - can be inaccurate */ /** Centre point of the link, calculated during render only - can be inaccurate */
_pos: Float32Array _pos: [number, number]
/** @todo Clean up - never implemented in comfy. */ /** @todo Clean up - never implemented in comfy. */
_last_time?: number _last_time?: number
/** The last canvas 2D path that was used to render this link */ /** The last canvas 2D path that was used to render this link */
@@ -171,7 +171,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
this._data = null this._data = null
// center // center
this._pos = new Float32Array(2) this._pos = [0, 0]
} }
/** @deprecated Use {@link LLink.create} */ /** @deprecated Use {@link LLink.create} */

View File

@@ -1,3 +1,4 @@
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
import { LayoutSource } from '@/renderer/core/layout/types' import { LayoutSource } from '@/renderer/core/layout/types'
@@ -12,8 +13,8 @@ import type {
LinkSegment, LinkSegment,
Point, Point,
Positionable, Positionable,
ReadOnlyRect, ReadonlyLinkNetwork,
ReadonlyLinkNetwork Rect
} from './interfaces' } from './interfaces'
import { distance, isPointInRect } from './measure' import { distance, isPointInRect } from './measure'
import type { Serialisable, SerialisableReroute } from './types/serialisation' import type { Serialisable, SerialisableReroute } from './types/serialisation'
@@ -49,8 +50,6 @@ export class Reroute
return Reroute.radius + gap + Reroute.slotRadius return Reroute.radius + gap + Reroute.slotRadius
} }
#malloc = new Float32Array(8)
/** The network this reroute belongs to. Contains all valid links and reroutes. */ /** The network this reroute belongs to. Contains all valid links and reroutes. */
#network: WeakRef<LinkNetwork> #network: WeakRef<LinkNetwork>
@@ -73,7 +72,7 @@ export class Reroute
/** This property is only defined on the last reroute of a floating reroute chain (closest to input end). */ /** This property is only defined on the last reroute of a floating reroute chain (closest to input end). */
floating?: FloatingRerouteSlot floating?: FloatingRerouteSlot
#pos = this.#malloc.subarray(0, 2) #pos: [number, number] = [0, 0]
/** @inheritdoc */ /** @inheritdoc */
get pos(): Point { get pos(): Point {
return this.#pos return this.#pos
@@ -89,17 +88,17 @@ export class Reroute
} }
/** @inheritdoc */ /** @inheritdoc */
get boundingRect(): ReadOnlyRect { get boundingRect(): Rectangle {
const { radius } = Reroute const { radius } = Reroute
const [x, y] = this.#pos const [x, y] = this.#pos
return [x - radius, y - radius, 2 * radius, 2 * radius] return Rectangle.from([x - radius, y - radius, 2 * radius, 2 * radius])
} }
/** /**
* Slightly over-sized rectangle, guaranteed to contain the entire surface area for hover detection. * Slightly over-sized rectangle, guaranteed to contain the entire surface area for hover detection.
* Eliminates most hover positions using an extremely cheap check. * Eliminates most hover positions using an extremely cheap check.
*/ */
get #hoverArea(): ReadOnlyRect { get #hoverArea(): Rect {
const xOffset = 2 * Reroute.slotOffset const xOffset = 2 * Reroute.slotOffset
const yOffset = 2 * Math.max(Reroute.radius, Reroute.slotRadius) const yOffset = 2 * Math.max(Reroute.radius, Reroute.slotRadius)
@@ -126,14 +125,14 @@ export class Reroute
sin: number = 0 sin: number = 0
/** Bezier curve control point for the "target" (input) side of the link */ /** Bezier curve control point for the "target" (input) side of the link */
controlPoint: Point = this.#malloc.subarray(4, 6) controlPoint: [number, number] = [0, 0]
/** @inheritdoc */ /** @inheritdoc */
path?: Path2D path?: Path2D
/** @inheritdoc */ /** @inheritdoc */
_centreAngle?: number _centreAngle?: number
/** @inheritdoc */ /** @inheritdoc */
_pos: Float32Array = this.#malloc.subarray(6, 8) _pos: [number, number] = [0, 0]
/** @inheritdoc */ /** @inheritdoc */
_dragging?: boolean _dragging?: boolean

View File

@@ -67,7 +67,7 @@ interface IDrawTextInAreaOptions {
*/ */
export function strokeShape( export function strokeShape(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
area: Rect, area: Rect | Rectangle,
{ {
shape = RenderShape.BOX, shape = RenderShape.BOX,
round_radius, round_radius,

View File

@@ -1,10 +1,6 @@
import { clamp } from 'es-toolkit/compat' import { clamp } from 'es-toolkit/compat'
import type { import type { Rect, Size } from '@/lib/litegraph/src/interfaces'
ReadOnlyRect,
ReadOnlySize,
Size
} from '@/lib/litegraph/src/interfaces'
/** /**
* Basic width and height, with min/max constraints. * Basic width and height, with min/max constraints.
@@ -55,15 +51,15 @@ export class ConstrainedSize {
this.desiredHeight = height this.desiredHeight = height
} }
static fromSize(size: ReadOnlySize): ConstrainedSize { static fromSize(size: Size): ConstrainedSize {
return new ConstrainedSize(size[0], size[1]) return new ConstrainedSize(size[0], size[1])
} }
static fromRect(rect: ReadOnlyRect): ConstrainedSize { static fromRect(rect: Rect): ConstrainedSize {
return new ConstrainedSize(rect[2], rect[3]) return new ConstrainedSize(rect[2], rect[3])
} }
setSize(size: ReadOnlySize): void { setSize(size: Size): void {
this.desiredWidth = size[0] this.desiredWidth = size[0]
this.desiredHeight = size[1] this.desiredHeight = size[1]
} }

View File

@@ -1,6 +1,6 @@
import type { LGraph } from '@/lib/litegraph/src/LGraph' import type { LGraph } from '@/lib/litegraph/src/LGraph'
import type { LLink, ResolvedConnection } from '@/lib/litegraph/src/LLink' import type { LLink, ResolvedConnection } from '@/lib/litegraph/src/LLink'
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces' import type { Rect } from '@/lib/litegraph/src/interfaces'
import type { Subgraph } from '@/lib/litegraph/src/subgraph/Subgraph' import type { Subgraph } from '@/lib/litegraph/src/subgraph/Subgraph'
import type { import type {
ExportedSubgraph, ExportedSubgraph,
@@ -29,7 +29,7 @@ export interface LGraphEventMap {
/** The type of subgraph to create. */ /** The type of subgraph to create. */
subgraph: Subgraph subgraph: Subgraph
/** The boundary around every item that was moved into the subgraph. */ /** The boundary around every item that was moved into the subgraph. */
bounds: ReadOnlyRect bounds: Rect
/** The raw data that was used to create the subgraph. */ /** The raw data that was used to create the subgraph. */
exportedSubgraph: ExportedSubgraph exportedSubgraph: ExportedSubgraph
/** The links that were used to create the subgraph. */ /** The links that were used to create the subgraph. */

View File

@@ -1,47 +1,50 @@
import type { import type {
CompassCorners, CompassCorners,
Point, Point,
ReadOnlyPoint, Rect,
ReadOnlyRect,
ReadOnlySize,
ReadOnlyTypedArray,
Size Size
} from '@/lib/litegraph/src/interfaces' } from '@/lib/litegraph/src/interfaces'
import { isInRectangle } from '@/lib/litegraph/src/measure' import { isInRectangle } from '@/lib/litegraph/src/measure'
/** /**
* A rectangle, represented as a float64 array of 4 numbers: [x, y, width, height]. * A rectangle, represented as an array of 4 numbers: [x, y, width, height].
* *
* This class is a subclass of Float64Array, and so has all the methods of that class. Notably, * This class extends Array and provides both array access (rect[0], rect[1], etc.)
* {@link Rectangle.from} can be used to convert a {@link ReadOnlyRect}. Typing of this however, * and convenient property access (rect.x, rect.y, rect.width, rect.height).
* is broken due to the base TS lib returning Float64Array rather than `this`.
*
* Sub-array properties ({@link Float64Array.subarray}):
* - {@link pos}: The position of the top-left corner of the rectangle.
* - {@link size}: The size of the rectangle.
*/ */
export class Rectangle extends Float64Array { export class Rectangle extends Array<number> {
#pos: Point | undefined
#size: Size | undefined
constructor( constructor(
x: number = 0, x: number = 0,
y: number = 0, y: number = 0,
width: number = 0, width: number = 0,
height: number = 0 height: number = 0
) { ) {
super(4) super()
this[0] = x this[0] = x
this[1] = y this[1] = y
this[2] = width this[2] = width
this[3] = height this[3] = height
this.length = 4
} }
static override from([x, y, width, height]: ReadOnlyRect): Rectangle { static override from([x, y, width, height]: Rect): Rectangle {
return new Rectangle(x, y, width, height) return new Rectangle(x, y, width, height)
} }
/** Set all values from an array (for TypedArray compatibility) */
set(values: ArrayLike<number>): void {
this[0] = values[0] ?? 0
this[1] = values[1] ?? 0
this[2] = values[2] ?? 0
this[3] = values[3] ?? 0
}
/** Create a subarray (for TypedArray compatibility) */
subarray(begin: number = 0, end?: number): number[] {
const endIndex = end ?? this.length
return this.slice(begin, endIndex)
}
/** /**
* Creates a new rectangle positioned at the given centre, with the given width/height. * Creates a new rectangle positioned at the given centre, with the given width/height.
* @param centre The centre of the rectangle, as an `[x, y]` point * @param centre The centre of the rectangle, as an `[x, y]` point
@@ -49,57 +52,38 @@ export class Rectangle extends Float64Array {
* @param height The height of the rectangle. Default: {@link width} * @param height The height of the rectangle. Default: {@link width}
* @returns A new rectangle whose centre is at {@link x} * @returns A new rectangle whose centre is at {@link x}
*/ */
static fromCentre( static fromCentre([x, y]: Point, width: number, height = width): Rectangle {
[x, y]: ReadOnlyPoint,
width: number,
height = width
): Rectangle {
const left = x - width * 0.5 const left = x - width * 0.5
const top = y - height * 0.5 const top = y - height * 0.5
return new Rectangle(left, top, width, height) return new Rectangle(left, top, width, height)
} }
static ensureRect(rect: ReadOnlyRect): Rectangle { static ensureRect(rect: Rect | Rectangle): Rectangle {
return rect instanceof Rectangle return rect instanceof Rectangle
? rect ? rect
: new Rectangle(rect[0], rect[1], rect[2], rect[3]) : new Rectangle(rect[0], rect[1], rect[2], rect[3])
} }
override subarray(
begin: number = 0,
end?: number
): Float64Array<ArrayBuffer> {
const byteOffset = begin << 3
const length = end === undefined ? end : end - begin
return new Float64Array(this.buffer, byteOffset, length)
}
/** /**
* A reference to the position of the top-left corner of this rectangle. * The position of the top-left corner of this rectangle.
*
* Updating the values of the returned object will update this rectangle.
*/ */
get pos(): Point { get pos(): Point {
this.#pos ??= this.subarray(0, 2) return [this[0], this[1]]
return this.#pos!
} }
set pos(value: ReadOnlyPoint) { set pos(value: Point) {
this[0] = value[0] this[0] = value[0]
this[1] = value[1] this[1] = value[1]
} }
/** /**
* A reference to the size of this rectangle. * The size of this rectangle.
*
* Updating the values of the returned object will update this rectangle.
*/ */
get size(): Size { get size(): Size {
this.#size ??= this.subarray(2, 4) return [this[2], this[3]]
return this.#size!
} }
set size(value: ReadOnlySize) { set size(value: Size) {
this[2] = value[0] this[2] = value[0]
this[3] = value[1] this[3] = value[1]
} }
@@ -192,7 +176,7 @@ export class Rectangle extends Float64Array {
* Updates the rectangle to the values of {@link rect}. * Updates the rectangle to the values of {@link rect}.
* @param rect The rectangle to update to. * @param rect The rectangle to update to.
*/ */
updateTo(rect: ReadOnlyRect) { updateTo(rect: Rect) {
this[0] = rect[0] this[0] = rect[0]
this[1] = rect[1] this[1] = rect[1]
this[2] = rect[2] this[2] = rect[2]
@@ -215,7 +199,7 @@ export class Rectangle extends Float64Array {
* @param point The point to check * @param point The point to check
* @returns `true` if {@link point} is inside this rectangle, otherwise `false`. * @returns `true` if {@link point} is inside this rectangle, otherwise `false`.
*/ */
containsPoint([x, y]: ReadOnlyPoint): boolean { containsPoint([x, y]: Point): boolean {
const [left, top, width, height] = this const [left, top, width, height] = this
return x >= left && x < left + width && y >= top && y < top + height return x >= left && x < left + width && y >= top && y < top + height
} }
@@ -226,7 +210,7 @@ export class Rectangle extends Float64Array {
* @param other The rectangle to check * @param other The rectangle to check
* @returns `true` if {@link other} is inside this rectangle, otherwise `false`. * @returns `true` if {@link other} is inside this rectangle, otherwise `false`.
*/ */
containsRect(other: ReadOnlyRect): boolean { containsRect(other: Rect | Rectangle): boolean {
const { right, bottom } = this const { right, bottom } = this
const otherRight = other[0] + other[2] const otherRight = other[0] + other[2]
const otherBottom = other[1] + other[3] const otherBottom = other[1] + other[3]
@@ -251,7 +235,7 @@ export class Rectangle extends Float64Array {
* @param rect The rectangle to check * @param rect The rectangle to check
* @returns `true` if {@link rect} overlaps with this rectangle, otherwise `false`. * @returns `true` if {@link rect} overlaps with this rectangle, otherwise `false`.
*/ */
overlaps(rect: ReadOnlyRect): boolean { overlaps(rect: Rect | Rectangle): boolean {
return ( return (
this.x < rect[0] + rect[2] && this.x < rect[0] + rect[2] &&
this.y < rect[1] + rect[3] && this.y < rect[1] + rect[3] &&
@@ -384,12 +368,12 @@ export class Rectangle extends Float64Array {
} }
/** @returns The offset from the top-left of this rectangle to the point [{@link x}, {@link y}], as a new {@link Point}. */ /** @returns The offset from the top-left of this rectangle to the point [{@link x}, {@link y}], as a new {@link Point}. */
getOffsetTo([x, y]: ReadOnlyPoint): Point { getOffsetTo([x, y]: Point): Point {
return [x - this[0], y - this[1]] return [x - this[0], y - this[1]]
} }
/** @returns The offset from the point [{@link x}, {@link y}] to the top-left of this rectangle, as a new {@link Point}. */ /** @returns The offset from the point [{@link x}, {@link y}] to the top-left of this rectangle, as a new {@link Point}. */
getOffsetFrom([x, y]: ReadOnlyPoint): Point { getOffsetFrom([x, y]: Point): Point {
return [this[0] - x, this[1] - y] return [this[0] - x, this[1] - y]
} }
@@ -470,14 +454,4 @@ export class Rectangle extends Float64Array {
} }
} }
export type ReadOnlyRectangle = Omit< // ReadOnlyRectangle is now just Rectangle since we unified the types
ReadOnlyTypedArray<Rectangle>,
| 'setHeightBottomAnchored'
| 'setWidthRightAnchored'
| 'resizeTopLeft'
| 'resizeBottomLeft'
| 'resizeTopRight'
| 'resizeBottomRight'
| 'resizeBottomRight'
| 'updateTo'
>

View File

@@ -1,4 +1,4 @@
import type { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle' import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events' import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
import type { ContextMenu } from './ContextMenu' import type { ContextMenu } from './ContextMenu'
@@ -60,7 +60,7 @@ export interface HasBoundingRect {
* @readonly * @readonly
* @see {@link move} * @see {@link move}
*/ */
readonly boundingRect: ReadOnlyRect readonly boundingRect: Rectangle
} }
/** An object containing a set of child objects */ /** An object containing a set of child objects */
@@ -193,7 +193,7 @@ export interface LinkSegment {
/** The last canvas 2D path that was used to render this segment */ /** The last canvas 2D path that was used to render this segment */
path?: Path2D path?: Path2D
/** Centre point of the {@link path}. Calculated during render only - can be inaccurate */ /** Centre point of the {@link path}. Calculated during render only - can be inaccurate */
readonly _pos: Float32Array readonly _pos: [number, number]
/** /**
* Y-forward along the {@link path} from its centre point, in radians. * Y-forward along the {@link path} from its centre point, in radians.
* `undefined` if using circles for link centres. * `undefined` if using circles for link centres.
@@ -225,52 +225,13 @@ export interface IFoundSlot extends IInputOrOutput {
} }
/** A point represented as `[x, y]` co-ordinates */ /** A point represented as `[x, y]` co-ordinates */
export type Point = [x: number, y: number] | Float32Array | Float64Array export type Point = [x: number, y: number]
/** A size represented as `[width, height]` */ /** A size represented as `[width, height]` */
export type Size = [width: number, height: number] | Float32Array | Float64Array export type Size = [width: number, height: number]
/** A very firm array */
type ArRect = [x: number, y: number, width: number, height: number]
/** A rectangle starting at top-left coordinates `[x, y, width, height]` */ /** A rectangle starting at top-left coordinates `[x, y, width, height]` */
export type Rect = ArRect | Float32Array | Float64Array export type Rect = [number, number, number, number]
/** A point represented as `[x, y]` co-ordinates that will not be modified */
export type ReadOnlyPoint =
| readonly [x: number, y: number]
| ReadOnlyTypedArray<Float32Array>
| ReadOnlyTypedArray<Float64Array>
/** A size represented as `[width, height]` that will not be modified */
export type ReadOnlySize =
| readonly [width: number, height: number]
| ReadOnlyTypedArray<Float32Array>
| ReadOnlyTypedArray<Float64Array>
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
export type ReadOnlyRect =
| readonly [x: number, y: number, width: number, height: number]
| ReadOnlyTypedArray<Float32Array>
| ReadOnlyTypedArray<Float64Array>
type TypedArrays =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
type TypedBigIntArrays = BigInt64Array | BigUint64Array
export type ReadOnlyTypedArray<T extends TypedArrays | TypedBigIntArrays> =
Omit<
Readonly<T>,
'fill' | 'copyWithin' | 'reverse' | 'set' | 'sort' | 'subarray'
>
/** Union of property names that are of type Match */ /** Union of property names that are of type Match */
type KeysOfType<T, Match> = Exclude< type KeysOfType<T, Match> = Exclude<
@@ -329,7 +290,7 @@ export interface INodeSlot extends HasBoundingRect {
nameLocked?: boolean nameLocked?: boolean
pos?: Point pos?: Point
/** @remarks Automatically calculated; not included in serialisation. */ /** @remarks Automatically calculated; not included in serialisation. */
boundingRect: Rect boundingRect: Rectangle
/** /**
* A list of floating link IDs that are connected to this slot. * A list of floating link IDs that are connected to this slot.
* This is calculated at runtime; it is **not** serialized. * This is calculated at runtime; it is **not** serialized.

View File

@@ -1,10 +1,5 @@
import type { import type { Rectangle } from './infrastructure/Rectangle'
HasBoundingRect, import type { HasBoundingRect, Point, Rect } from './interfaces'
Point,
ReadOnlyPoint,
ReadOnlyRect,
Rect
} from './interfaces'
import { Alignment, LinkDirection, hasFlag } from './types/globalEnums' import { Alignment, LinkDirection, hasFlag } from './types/globalEnums'
/** /**
@@ -13,7 +8,7 @@ import { Alignment, LinkDirection, hasFlag } from './types/globalEnums'
* @param b Point b as `x, y` * @param b Point b as `x, y`
* @returns Distance between point {@link a} & {@link b} * @returns Distance between point {@link a} & {@link b}
*/ */
export function distance(a: ReadOnlyPoint, b: ReadOnlyPoint): number { export function distance(a: Point, b: Point): number {
return Math.sqrt( return Math.sqrt(
(b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])
) )
@@ -61,10 +56,7 @@ export function isInRectangle(
* @param rect The rectangle, as `x, y, width, height` * @param rect The rectangle, as `x, y, width, height`
* @returns `true` if the point is inside the rect, otherwise `false` * @returns `true` if the point is inside the rect, otherwise `false`
*/ */
export function isPointInRect( export function isPointInRect(point: Point, rect: Rect | Rectangle): boolean {
point: ReadOnlyPoint,
rect: ReadOnlyRect
): boolean {
return ( return (
point[0] >= rect[0] && point[0] >= rect[0] &&
point[0] < rect[0] + rect[2] && point[0] < rect[0] + rect[2] &&
@@ -80,7 +72,11 @@ export function isPointInRect(
* @param rect The rectangle, as `x, y, width, height` * @param rect The rectangle, as `x, y, width, height`
* @returns `true` if the point is inside the rect, otherwise `false` * @returns `true` if the point is inside the rect, otherwise `false`
*/ */
export function isInRect(x: number, y: number, rect: ReadOnlyRect): boolean { export function isInRect(
x: number,
y: number,
rect: Rect | Rectangle
): boolean {
return ( return (
x >= rect[0] && x >= rect[0] &&
x < rect[0] + rect[2] && x < rect[0] + rect[2] &&
@@ -121,7 +117,10 @@ export function isInsideRectangle(
* @param b Rectangle B as `x, y, width, height` * @param b Rectangle B as `x, y, width, height`
* @returns `true` if rectangles overlap, otherwise `false` * @returns `true` if rectangles overlap, otherwise `false`
*/ */
export function overlapBounding(a: ReadOnlyRect, b: ReadOnlyRect): boolean { export function overlapBounding(
a: Rect | Rectangle,
b: Rect | Rectangle
): boolean {
const aRight = a[0] + a[2] const aRight = a[0] + a[2]
const aBottom = a[1] + a[3] const aBottom = a[1] + a[3]
const bRight = b[0] + b[2] const bRight = b[0] + b[2]
@@ -137,7 +136,7 @@ export function overlapBounding(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
* @param rect The rectangle, as `x, y, width, height` * @param rect The rectangle, as `x, y, width, height`
* @returns The centre of the rectangle, as `x, y` * @returns The centre of the rectangle, as `x, y`
*/ */
export function getCentre(rect: ReadOnlyRect): Point { export function getCentre(rect: Rect | Rectangle): Point {
return [rect[0] + rect[2] * 0.5, rect[1] + rect[3] * 0.5] return [rect[0] + rect[2] * 0.5, rect[1] + rect[3] * 0.5]
} }
@@ -147,7 +146,10 @@ export function getCentre(rect: ReadOnlyRect): Point {
* @param b Sub-rectangle B as `x, y, width, height` * @param b Sub-rectangle B as `x, y, width, height`
* @returns `true` if {@link a} contains most of {@link b}, otherwise `false` * @returns `true` if {@link a} contains most of {@link b}, otherwise `false`
*/ */
export function containsCentre(a: ReadOnlyRect, b: ReadOnlyRect): boolean { export function containsCentre(
a: Rect | Rectangle,
b: Rect | Rectangle
): boolean {
const centreX = b[0] + b[2] * 0.5 const centreX = b[0] + b[2] * 0.5
const centreY = b[1] + b[3] * 0.5 const centreY = b[1] + b[3] * 0.5
return isInRect(centreX, centreY, a) return isInRect(centreX, centreY, a)
@@ -159,7 +161,10 @@ export function containsCentre(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
* @param b Sub-rectangle B as `x, y, width, height` * @param b Sub-rectangle B as `x, y, width, height`
* @returns `true` if {@link a} wholly contains {@link b}, otherwise `false` * @returns `true` if {@link a} wholly contains {@link b}, otherwise `false`
*/ */
export function containsRect(a: ReadOnlyRect, b: ReadOnlyRect): boolean { export function containsRect(
a: Rect | Rectangle,
b: Rect | Rectangle
): boolean {
const aRight = a[0] + a[2] const aRight = a[0] + a[2]
const aBottom = a[1] + a[3] const aBottom = a[1] + a[3]
const bRight = b[0] + b[2] const bRight = b[0] + b[2]
@@ -289,8 +294,8 @@ export function rotateLink(
* the right * the right
*/ */
export function getOrientation( export function getOrientation(
lineStart: ReadOnlyPoint, lineStart: Point,
lineEnd: ReadOnlyPoint, lineEnd: Point,
x: number, x: number,
y: number y: number
): number { ): number {
@@ -310,10 +315,10 @@ export function getOrientation(
*/ */
export function findPointOnCurve( export function findPointOnCurve(
out: Point, out: Point,
a: ReadOnlyPoint, a: Point,
b: ReadOnlyPoint, b: Point,
controlA: ReadOnlyPoint, controlA: Point,
controlB: ReadOnlyPoint, controlB: Point,
t: number = 0.5 t: number = 0.5
): void { ): void {
const iT = 1 - t const iT = 1 - t
@@ -330,8 +335,13 @@ export function findPointOnCurve(
export function createBounds( export function createBounds(
objects: Iterable<HasBoundingRect>, objects: Iterable<HasBoundingRect>,
padding: number = 10 padding: number = 10
): ReadOnlyRect | null { ): Rect | null {
const bounds = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]) const bounds: [number, number, number, number] = [
Infinity,
Infinity,
-Infinity,
-Infinity
]
for (const obj of objects) { for (const obj of objects) {
const rect = obj.boundingRect const rect = obj.boundingRect
@@ -379,11 +389,11 @@ export function snapPoint(pos: Point | Rect, snapTo: number): boolean {
* @returns The original {@link rect}, modified in place. * @returns The original {@link rect}, modified in place.
*/ */
export function alignToContainer( export function alignToContainer(
rect: Rect, rect: Rect | Rectangle,
anchors: Alignment, anchors: Alignment,
[containerX, containerY, containerWidth, containerHeight]: ReadOnlyRect, [containerX, containerY, containerWidth, containerHeight]: Rect | Rectangle,
[insetX, insetY]: ReadOnlyPoint = [0, 0] [insetX, insetY]: Point = [0, 0]
): Rect { ): Rect | Rectangle {
if (hasFlag(anchors, Alignment.Left)) { if (hasFlag(anchors, Alignment.Left)) {
// Left // Left
rect[0] = containerX + insetX rect[0] = containerX + insetX
@@ -422,11 +432,11 @@ export function alignToContainer(
* @returns The original {@link rect}, modified in place. * @returns The original {@link rect}, modified in place.
*/ */
export function alignOutsideContainer( export function alignOutsideContainer(
rect: Rect, rect: Rect | Rectangle,
anchors: Alignment, anchors: Alignment,
[otherX, otherY, otherWidth, otherHeight]: ReadOnlyRect, [otherX, otherY, otherWidth, otherHeight]: Rect | Rectangle,
[outsetX, outsetY]: ReadOnlyPoint = [0, 0] [outsetX, outsetY]: Point = [0, 0]
): Rect { ): Rect | Rectangle {
if (hasFlag(anchors, Alignment.Left)) { if (hasFlag(anchors, Alignment.Left)) {
// Left // Left
rect[0] = otherX - outsetX - rect[2] rect[0] = otherX - outsetX - rect[2]

View File

@@ -5,7 +5,7 @@ import type {
INodeInputSlot, INodeInputSlot,
INodeOutputSlot, INodeOutputSlot,
OptionalProps, OptionalProps,
ReadOnlyPoint Point
} from '@/lib/litegraph/src/interfaces' } from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { type IDrawOptions, NodeSlot } from '@/lib/litegraph/src/node/NodeSlot' import { type IDrawOptions, NodeSlot } from '@/lib/litegraph/src/node/NodeSlot'
@@ -32,7 +32,7 @@ export class NodeInputSlot extends NodeSlot implements INodeInputSlot {
this.#widget = widget ? new WeakRef(widget) : undefined this.#widget = widget ? new WeakRef(widget) : undefined
} }
get collapsedPos(): ReadOnlyPoint { get collapsedPos(): Point {
return [0, LiteGraph.NODE_TITLE_HEIGHT * -0.5] return [0, LiteGraph.NODE_TITLE_HEIGHT * -0.5]
} }

View File

@@ -5,7 +5,7 @@ import type {
INodeInputSlot, INodeInputSlot,
INodeOutputSlot, INodeOutputSlot,
OptionalProps, OptionalProps,
ReadOnlyPoint Point
} from '@/lib/litegraph/src/interfaces' } from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { type IDrawOptions, NodeSlot } from '@/lib/litegraph/src/node/NodeSlot' import { type IDrawOptions, NodeSlot } from '@/lib/litegraph/src/node/NodeSlot'
@@ -24,7 +24,7 @@ export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot {
return false return false
} }
get collapsedPos(): ReadOnlyPoint { get collapsedPos(): Point {
return [ return [
this.#node._collapsed_width ?? LiteGraph.NODE_COLLAPSED_WIDTH, this.#node._collapsed_width ?? LiteGraph.NODE_COLLAPSED_WIDTH,
LiteGraph.NODE_TITLE_HEIGHT * -0.5 LiteGraph.NODE_TITLE_HEIGHT * -0.5

View File

@@ -8,8 +8,7 @@ import type {
INodeSlot, INodeSlot,
ISubgraphInput, ISubgraphInput,
OptionalProps, OptionalProps,
Point, Point
ReadOnlyPoint
} from '@/lib/litegraph/src/interfaces' } from '@/lib/litegraph/src/interfaces'
import { LiteGraph, Rectangle } from '@/lib/litegraph/src/litegraph' import { LiteGraph, Rectangle } from '@/lib/litegraph/src/litegraph'
import { getCentre } from '@/lib/litegraph/src/measure' import { getCentre } from '@/lib/litegraph/src/measure'
@@ -36,7 +35,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot {
pos?: Point pos?: Point
/** The offset from the parent node to the centre point of this slot. */ /** The offset from the parent node to the centre point of this slot. */
get #centreOffset(): ReadOnlyPoint { get #centreOffset(): Point {
const nodePos = this.node.pos const nodePos = this.node.pos
const { boundingRect } = this const { boundingRect } = this
@@ -52,7 +51,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot {
} }
/** The center point of this slot when the node is collapsed. */ /** The center point of this slot when the node is collapsed. */
abstract get collapsedPos(): ReadOnlyPoint abstract get collapsedPos(): Point
#node: LGraphNode #node: LGraphNode
get node(): LGraphNode { get node(): LGraphNode {

View File

@@ -7,7 +7,7 @@ import type {
INodeInputSlot, INodeInputSlot,
INodeOutputSlot, INodeOutputSlot,
Point, Point,
ReadOnlyRect Rect
} from '@/lib/litegraph/src/interfaces' } from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums' import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums'
@@ -213,7 +213,7 @@ export class SubgraphInput extends SubgraphSlot {
} }
/** For inputs, x is the right edge of the input node. */ /** For inputs, x is the right edge of the input node. */
override arrange(rect: ReadOnlyRect): void { override arrange(rect: Rect): void {
const [right, top, width, height] = rect const [right, top, width, height] = rect
const { boundingRect: b, pos } = this const { boundingRect: b, pos } = this

View File

@@ -7,7 +7,7 @@ import type {
INodeInputSlot, INodeInputSlot,
INodeOutputSlot, INodeOutputSlot,
Point, Point,
ReadOnlyRect Rect
} from '@/lib/litegraph/src/interfaces' } from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums' import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums'
@@ -119,7 +119,7 @@ export class SubgraphOutput extends SubgraphSlot {
return [x + height, y + height * 0.5] return [x + height, y + height * 0.5]
} }
override arrange(rect: ReadOnlyRect): void { override arrange(rect: Rect): void {
const [left, top, width, height] = rect const [left, top, width, height] = rect
const { boundingRect: b, pos } = this const { boundingRect: b, pos } = this

View File

@@ -11,8 +11,8 @@ import type {
INodeInputSlot, INodeInputSlot,
INodeOutputSlot, INodeOutputSlot,
Point, Point,
ReadOnlyRect, Rect,
ReadOnlySize Size
} from '@/lib/litegraph/src/interfaces' } from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { SlotBase } from '@/lib/litegraph/src/node/SlotBase' import { SlotBase } from '@/lib/litegraph/src/node/SlotBase'
@@ -45,7 +45,7 @@ export abstract class SubgraphSlot
return LiteGraph.NODE_SLOT_HEIGHT return LiteGraph.NODE_SLOT_HEIGHT
} }
readonly #pos: Point = new Float32Array(2) readonly #pos: Point = [0, 0]
readonly measurement: ConstrainedSize = new ConstrainedSize( readonly measurement: ConstrainedSize = new ConstrainedSize(
SubgraphSlot.defaultHeight, SubgraphSlot.defaultHeight,
@@ -133,7 +133,7 @@ export abstract class SubgraphSlot
} }
} }
measure(): ReadOnlySize { measure(): Size {
const width = LGraphCanvas._measureText?.(this.displayName) ?? 0 const width = LGraphCanvas._measureText?.(this.displayName) ?? 0
const { defaultHeight } = SubgraphSlot const { defaultHeight } = SubgraphSlot
@@ -141,7 +141,7 @@ export abstract class SubgraphSlot
return this.measurement.toSize() return this.measurement.toSize()
} }
abstract arrange(rect: ReadOnlyRect): void abstract arrange(rect: Rect): void
abstract connect( abstract connect(
slot: INodeInputSlot | INodeOutputSlot, slot: INodeInputSlot | INodeOutputSlot,

View File

@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, vi } from 'vitest' import { afterEach, beforeEach, describe, expect, vi } from 'vitest'
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import type { INodeInputSlot, Point } from '@/lib/litegraph/src/interfaces' import type { INodeInputSlot, Point } from '@/lib/litegraph/src/interfaces'
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { LGraph } from '@/lib/litegraph/src/litegraph' import { LGraph } from '@/lib/litegraph/src/litegraph'
@@ -84,8 +85,8 @@ describe('LGraphNode', () => {
})) }))
} }
node.configure(configureData) node.configure(configureData)
expect(node.pos).toEqual(new Float32Array([50, 60])) expect(node.pos).toEqual([50, 60])
expect(node.size).toEqual(new Float32Array([70, 80])) expect(node.size).toEqual([70, 80])
}) })
test('should configure inputs correctly', () => { test('should configure inputs correctly', () => {
@@ -571,7 +572,7 @@ describe('LGraphNode', () => {
name: 'test_in', name: 'test_in',
type: 'string', type: 'string',
link: null, link: null,
boundingRect: new Float32Array([0, 0, 0, 0]) boundingRect: new Rectangle(0, 0, 0, 0)
} }
}) })
test('should return position based on title height when collapsed', () => { test('should return position based on title height when collapsed', () => {
@@ -594,7 +595,7 @@ describe('LGraphNode', () => {
name: 'test_in_2', name: 'test_in_2',
type: 'number', type: 'number',
link: null, link: null,
boundingRect: new Float32Array([0, 0, 0, 0]) boundingRect: new Rectangle(0, 0, 0, 0)
} }
node.inputs = [inputSlot, inputSlot2] node.inputs = [inputSlot, inputSlot2]
const slotIndex = 0 const slotIndex = 0
@@ -629,13 +630,13 @@ describe('LGraphNode', () => {
name: 'in0', name: 'in0',
type: 'string', type: 'string',
link: null, link: null,
boundingRect: new Float32Array([0, 0, 0, 0]) boundingRect: new Rectangle(0, 0, 0, 0)
} }
const input1: INodeInputSlot = { const input1: INodeInputSlot = {
name: 'in1', name: 'in1',
type: 'number', type: 'number',
link: null, link: null,
boundingRect: new Float32Array([0, 0, 0, 0]), boundingRect: new Rectangle(0, 0, 0, 0),
pos: [5, 45] pos: [5, 45]
} }
node.inputs = [input0, input1] node.inputs = [input0, input1]

View File

@@ -4,19 +4,19 @@ exports[`LGraph configure() > LGraph matches previous snapshot (normal configure
LGraph { LGraph {
"_groups": [ "_groups": [
LGraphGroup { LGraphGroup {
"_bounding": Float32Array [ "_bounding": [
20, 10,
20, 10,
1, 140,
3, 80,
], ],
"_children": Set {}, "_children": Set {},
"_nodes": [], "_nodes": [],
"_pos": Float32Array [ "_pos": [
20, 20,
20, 20,
], ],
"_size": Float32Array [ "_size": [
1, 1,
3, 3,
], ],
@@ -39,19 +39,19 @@ LGraph {
LGraphNode { LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -98,6 +98,7 @@ LGraph {
"selected": [Function], "selected": [Function],
}, },
"title": "LGraphNode", "title": "LGraphNode",
"title_buttons": [],
"type": "mustBeSet", "type": "mustBeSet",
"widgets": undefined, "widgets": undefined,
"widgets_start_y": undefined, "widgets_start_y": undefined,
@@ -108,19 +109,19 @@ LGraph {
"1": LGraphNode { "1": LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -167,6 +168,7 @@ LGraph {
"selected": [Function], "selected": [Function],
}, },
"title": "LGraphNode", "title": "LGraphNode",
"title_buttons": [],
"type": "mustBeSet", "type": "mustBeSet",
"widgets": undefined, "widgets": undefined,
"widgets_start_y": undefined, "widgets_start_y": undefined,
@@ -178,19 +180,19 @@ LGraph {
LGraphNode { LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -237,6 +239,7 @@ LGraph {
"selected": [Function], "selected": [Function],
}, },
"title": "LGraphNode", "title": "LGraphNode",
"title_buttons": [],
"type": "mustBeSet", "type": "mustBeSet",
"widgets": undefined, "widgets": undefined,
"widgets_start_y": undefined, "widgets_start_y": undefined,
@@ -249,7 +252,16 @@ LGraph {
"config": {}, "config": {},
"elapsed_time": 0.01, "elapsed_time": 0.01,
"errors_in_execution": undefined, "errors_in_execution": undefined,
"events": CustomEventTarget {}, "events": CustomEventTarget {
Symbol(listeners): {
"bubbling": Map {},
"capturing": Map {},
},
Symbol(listenerOptions): {
"bubbling": Map {},
"capturing": Map {},
},
},
"execution_time": undefined, "execution_time": undefined,
"execution_timer_id": undefined, "execution_timer_id": undefined,
"extra": {}, "extra": {},
@@ -296,7 +308,16 @@ LGraph {
"config": {}, "config": {},
"elapsed_time": 0.01, "elapsed_time": 0.01,
"errors_in_execution": undefined, "errors_in_execution": undefined,
"events": CustomEventTarget {}, "events": CustomEventTarget {
Symbol(listeners): {
"bubbling": Map {},
"capturing": Map {},
},
Symbol(listenerOptions): {
"bubbling": Map {},
"capturing": Map {},
},
},
"execution_time": undefined, "execution_time": undefined,
"execution_timer_id": undefined, "execution_timer_id": undefined,
"extra": {}, "extra": {},

View File

@@ -4,19 +4,19 @@ exports[`LGraph > supports schema v0.4 graphs > oldSchemaGraph 1`] = `
LGraph { LGraph {
"_groups": [ "_groups": [
LGraphGroup { LGraphGroup {
"_bounding": Float32Array [ "_bounding": [
20, 10,
20, 10,
1, 140,
3, 80,
], ],
"_children": Set {}, "_children": Set {},
"_nodes": [], "_nodes": [],
"_pos": Float32Array [ "_pos": [
20, 20,
20, 20,
], ],
"_size": Float32Array [ "_size": [
1, 1,
3, 3,
], ],
@@ -39,19 +39,19 @@ LGraph {
LGraphNode { LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -111,19 +111,19 @@ LGraph {
"1": LGraphNode { "1": LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -184,19 +184,19 @@ LGraph {
LGraphNode { LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -258,7 +258,16 @@ LGraph {
"config": {}, "config": {},
"elapsed_time": 0.01, "elapsed_time": 0.01,
"errors_in_execution": undefined, "errors_in_execution": undefined,
"events": CustomEventTarget {}, "events": CustomEventTarget {
Symbol(listeners): {
"bubbling": Map {},
"capturing": Map {},
},
Symbol(listenerOptions): {
"bubbling": Map {},
"capturing": Map {},
},
},
"execution_time": undefined, "execution_time": undefined,
"execution_timer_id": undefined, "execution_timer_id": undefined,
"extra": {}, "extra": {},

View File

@@ -4,19 +4,19 @@ exports[`LGraph (constructor only) > Matches previous snapshot > basicLGraph 1`]
LGraph { LGraph {
"_groups": [ "_groups": [
LGraphGroup { LGraphGroup {
"_bounding": Float32Array [ "_bounding": [
20, 10,
20, 10,
1, 140,
3, 80,
], ],
"_children": Set {}, "_children": Set {},
"_nodes": [], "_nodes": [],
"_pos": Float32Array [ "_pos": [
20, 20,
20, 20,
], ],
"_size": Float32Array [ "_size": [
1, 1,
3, 3,
], ],
@@ -39,19 +39,19 @@ LGraph {
LGraphNode { LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -109,19 +109,19 @@ LGraph {
"1": LGraphNode { "1": LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -180,19 +180,19 @@ LGraph {
LGraphNode { LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -252,7 +252,16 @@ LGraph {
"config": {}, "config": {},
"elapsed_time": 0.01, "elapsed_time": 0.01,
"errors_in_execution": undefined, "errors_in_execution": undefined,
"events": CustomEventTarget {}, "events": CustomEventTarget {
Symbol(listeners): {
"bubbling": Map {},
"capturing": Map {},
},
Symbol(listenerOptions): {
"bubbling": Map {},
"capturing": Map {},
},
},
"execution_time": undefined, "execution_time": undefined,
"execution_timer_id": undefined, "execution_timer_id": undefined,
"extra": {}, "extra": {},
@@ -299,7 +308,16 @@ LGraph {
"config": {}, "config": {},
"elapsed_time": 0.01, "elapsed_time": 0.01,
"errors_in_execution": undefined, "errors_in_execution": undefined,
"events": CustomEventTarget {}, "events": CustomEventTarget {
Symbol(listeners): {
"bubbling": Map {},
"capturing": Map {},
},
Symbol(listenerOptions): {
"bubbling": Map {},
"capturing": Map {},
},
},
"execution_time": undefined, "execution_time": undefined,
"execution_timer_id": undefined, "execution_timer_id": undefined,
"extra": {}, "extra": {},

View File

@@ -1,5 +1,6 @@
import { test as baseTest } from 'vitest' import { test as baseTest } from 'vitest'
import { Rectangle } from '../src/infrastructure/Rectangle'
import type { Point, Rect } from '../src/interfaces' import type { Point, Rect } from '../src/interfaces'
import { import {
addDirectionalOffset, addDirectionalOffset,
@@ -131,8 +132,8 @@ test('snapPoint correctly snaps points to grid', ({ expect }) => {
test('createBounds correctly creates bounding box', ({ expect }) => { test('createBounds correctly creates bounding box', ({ expect }) => {
const objects = [ const objects = [
{ boundingRect: [0, 0, 10, 10] as Rect }, { boundingRect: new Rectangle(0, 0, 10, 10) },
{ boundingRect: [5, 5, 10, 10] as Rect } { boundingRect: new Rectangle(5, 5, 10, 10) }
] ]
const defaultBounds = createBounds(objects) const defaultBounds = createBounds(objects)

View File

@@ -7,11 +7,14 @@
* Maintains backward compatibility with existing litegraph integration. * Maintains backward compatibility with existing litegraph integration.
*/ */
import type { LGraph } from '@/lib/litegraph/src/LGraph' import type { LGraph } from '@/lib/litegraph/src/LGraph'
import type { LLink } from '@/lib/litegraph/src/LLink' import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import { LLink } from '@/lib/litegraph/src/LLink'
import type { Reroute } from '@/lib/litegraph/src/Reroute' import type { Reroute } from '@/lib/litegraph/src/Reroute'
import type { import type {
CanvasColour, CanvasColour,
ReadOnlyPoint INodeInputSlot,
INodeOutputSlot,
Point as LitegraphPoint
} from '@/lib/litegraph/src/interfaces' } from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { import {
@@ -24,6 +27,7 @@ import {
type ArrowShape, type ArrowShape,
CanvasPathRenderer, CanvasPathRenderer,
type Direction, type Direction,
type DragLinkData,
type LinkRenderData, type LinkRenderData,
type RenderContext as PathRenderContext, type RenderContext as PathRenderContext,
type Point, type Point,
@@ -205,6 +209,7 @@ export class LitegraphLinkAdapter {
case LinkDirection.DOWN: case LinkDirection.DOWN:
return 'down' return 'down'
case LinkDirection.CENTER: case LinkDirection.CENTER:
case LinkDirection.NONE:
return 'none' return 'none'
default: default:
return 'right' return 'right'
@@ -306,22 +311,22 @@ export class LitegraphLinkAdapter {
* Critically: does nothing for CENTER/NONE directions (no case for them) * Critically: does nothing for CENTER/NONE directions (no case for them)
*/ */
private applySplineOffset( private applySplineOffset(
point: Point, point: LitegraphPoint,
direction: LinkDirection, direction: LinkDirection,
distance: number distance: number
): void { ): void {
switch (direction) { switch (direction) {
case LinkDirection.LEFT: case LinkDirection.LEFT:
point.x -= distance point[0] -= distance
break break
case LinkDirection.RIGHT: case LinkDirection.RIGHT:
point.x += distance point[0] += distance
break break
case LinkDirection.UP: case LinkDirection.UP:
point.y -= distance point[1] -= distance
break break
case LinkDirection.DOWN: case LinkDirection.DOWN:
point.y += distance point[1] += distance
break break
// CENTER and NONE: no offset applied (original behavior) // CENTER and NONE: no offset applied (original behavior)
} }
@@ -333,8 +338,8 @@ export class LitegraphLinkAdapter {
*/ */
renderLinkDirect( renderLinkDirect(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
a: ReadOnlyPoint, a: LitegraphPoint,
b: ReadOnlyPoint, b: LitegraphPoint,
link: LLink | null, link: LLink | null,
skip_border: boolean, skip_border: boolean,
flow: number | boolean | null, flow: number | boolean | null,
@@ -344,8 +349,8 @@ export class LitegraphLinkAdapter {
context: LinkRenderContext, context: LinkRenderContext,
extras: { extras: {
reroute?: Reroute reroute?: Reroute
startControl?: ReadOnlyPoint startControl?: LitegraphPoint
endControl?: ReadOnlyPoint endControl?: LitegraphPoint
num_sublines?: number num_sublines?: number
disabled?: boolean disabled?: boolean
} = {} } = {}
@@ -406,13 +411,19 @@ export class LitegraphLinkAdapter {
y: a[1] + (extras.startControl![1] || 0) y: a[1] + (extras.startControl![1] || 0)
} }
const end = { x: b[0], y: b[1] } const end = { x: b[0], y: b[1] }
this.applySplineOffset(end, endDir, dist * factor) const endArray: LitegraphPoint = [end.x, end.y]
this.applySplineOffset(endArray, endDir, dist * factor)
end.x = endArray[0]
end.y = endArray[1]
cps.push(start, end) cps.push(start, end)
linkData.controlPoints = cps linkData.controlPoints = cps
} else if (!hasStartCtrl && hasEndCtrl) { } else if (!hasStartCtrl && hasEndCtrl) {
// End provided, derive start via direction offset (CENTER => no offset) // End provided, derive start via direction offset (CENTER => no offset)
const start = { x: a[0], y: a[1] } const start = { x: a[0], y: a[1] }
this.applySplineOffset(start, startDir, dist * factor) const startArray: LitegraphPoint = [start.x, start.y]
this.applySplineOffset(startArray, startDir, dist * factor)
start.x = startArray[0]
start.y = startArray[1]
const end = { const end = {
x: b[0] + (extras.endControl![0] || 0), x: b[0] + (extras.endControl![0] || 0),
y: b[1] + (extras.endControl![1] || 0) y: b[1] + (extras.endControl![1] || 0)
@@ -423,8 +434,14 @@ export class LitegraphLinkAdapter {
// Neither provided: derive both from directions (CENTER => no offset) // Neither provided: derive both from directions (CENTER => no offset)
const start = { x: a[0], y: a[1] } const start = { x: a[0], y: a[1] }
const end = { x: b[0], y: b[1] } const end = { x: b[0], y: b[1] }
this.applySplineOffset(start, startDir, dist * factor) const startArray: LitegraphPoint = [start.x, start.y]
this.applySplineOffset(end, endDir, dist * factor) const endArray: LitegraphPoint = [end.x, end.y]
this.applySplineOffset(startArray, startDir, dist * factor)
this.applySplineOffset(endArray, endDir, dist * factor)
start.x = startArray[0]
start.y = startArray[1]
end.x = endArray[0]
end.y = endArray[1]
cps.push(start, end) cps.push(start, end)
linkData.controlPoints = cps linkData.controlPoints = cps
} }
@@ -449,7 +466,7 @@ export class LitegraphLinkAdapter {
// Copy calculated center position back to litegraph object // Copy calculated center position back to litegraph object
// This is needed for hit detection and menu interaction // This is needed for hit detection and menu interaction
if (linkData.centerPos) { if (linkData.centerPos) {
linkSegment._pos = linkSegment._pos || new Float32Array(2) linkSegment._pos = linkSegment._pos || [0, 0]
linkSegment._pos[0] = linkData.centerPos.x linkSegment._pos[0] = linkData.centerPos.x
linkSegment._pos[1] = linkData.centerPos.y linkSegment._pos[1] = linkData.centerPos.y
@@ -463,8 +480,8 @@ export class LitegraphLinkAdapter {
if (this.enableLayoutStoreWrites && link && link.id !== -1) { if (this.enableLayoutStoreWrites && link && link.id !== -1) {
// Calculate bounds and center only when writing // Calculate bounds and center only when writing
const bounds = this.calculateLinkBounds( const bounds = this.calculateLinkBounds(
[linkData.startPoint.x, linkData.startPoint.y] as ReadOnlyPoint, [linkData.startPoint.x, linkData.startPoint.y] as LitegraphPoint,
[linkData.endPoint.x, linkData.endPoint.y] as ReadOnlyPoint, [linkData.endPoint.x, linkData.endPoint.y] as LitegraphPoint,
linkData linkData
) )
const centerPos = linkData.centerPos || { const centerPos = linkData.centerPos || {
@@ -497,33 +514,57 @@ export class LitegraphLinkAdapter {
} }
} }
/**
* Render a link being dragged from a slot to mouse position
* Used during link creation/reconnection
*/
renderDraggingLink( renderDraggingLink(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
from: ReadOnlyPoint, fromNode: LGraphNode | null,
to: ReadOnlyPoint, fromSlot: INodeOutputSlot | INodeInputSlot,
colour: CanvasColour, fromSlotIndex: number,
startDir: LinkDirection, toPosition: LitegraphPoint,
endDir: LinkDirection, context: LinkRenderContext,
context: LinkRenderContext options: {
fromInput?: boolean
color?: CanvasColour
disabled?: boolean
} = {}
): void { ): void {
this.renderLinkDirect( if (!fromNode) return
ctx,
from, // Get slot position using layout tree if available
to, const slotPos = getSlotPosition(
null, fromNode,
false, fromSlotIndex,
null, options.fromInput || false
colour,
startDir,
endDir,
{
...context,
linkMarkerShape: LinkMarkerShape.None
},
{
disabled: false
}
) )
if (!slotPos) return
// Get slot direction
const slotDir =
fromSlot.dir ||
(options.fromInput ? LinkDirection.LEFT : LinkDirection.RIGHT)
// Create drag data
const dragData: DragLinkData = {
fixedPoint: { x: slotPos[0], y: slotPos[1] },
fixedDirection: this.convertDirection(slotDir),
dragPoint: { x: toPosition[0], y: toPosition[1] },
color: options.color ? String(options.color) : undefined,
type: fromSlot.type !== undefined ? String(fromSlot.type) : undefined,
disabled: options.disabled || false,
fromInput: options.fromInput || false
}
// Convert context
const pathContext = this.convertToPathRenderContext(context)
// Hide center marker when dragging links
pathContext.style.showCenterMarker = false
// Render using pure renderer
this.pathRenderer.drawDraggingLink(ctx, dragData, pathContext)
} }
/** /**
@@ -531,8 +572,8 @@ export class LitegraphLinkAdapter {
* Includes padding for line width and control points * Includes padding for line width and control points
*/ */
private calculateLinkBounds( private calculateLinkBounds(
startPos: ReadOnlyPoint, startPos: LitegraphPoint,
endPos: ReadOnlyPoint, endPos: LitegraphPoint,
linkData: LinkRenderData linkData: LinkRenderData
): Bounds { ): Bounds {
let minX = Math.min(startPos[0], endPos[0]) let minX = Math.min(startPos[0], endPos[0])

View File

@@ -12,7 +12,7 @@ import type { LGraph } from '@/lib/litegraph/src/LGraph'
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas' import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
import { LLink } from '@/lib/litegraph/src/LLink' import { LLink } from '@/lib/litegraph/src/LLink'
import { Reroute } from '@/lib/litegraph/src/Reroute' import { Reroute } from '@/lib/litegraph/src/Reroute'
import type { ReadOnlyPoint } from '@/lib/litegraph/src/interfaces' import type { Point as LitegraphPoint } from '@/lib/litegraph/src/interfaces'
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums' import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
import { LitegraphLinkAdapter } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter' import { LitegraphLinkAdapter } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
import type { LinkRenderContext } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter' import type { LinkRenderContext } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
@@ -125,7 +125,7 @@ export function useLinkLayoutSync() {
// Special handling for floating input chain // Special handling for floating input chain
const isFloatingInputChain = !sourceNode && targetNode const isFloatingInputChain = !sourceNode && targetNode
const startControl: ReadOnlyPoint = isFloatingInputChain const startControl: LitegraphPoint = isFloatingInputChain
? [0, 0] ? [0, 0]
: [dist * reroute.cos, dist * reroute.sin] : [dist * reroute.cos, dist * reroute.sin]
@@ -161,7 +161,7 @@ export function useLinkLayoutSync() {
(endPos[1] - lastReroute.pos[1]) ** 2 (endPos[1] - lastReroute.pos[1]) ** 2
) )
const finalDist = Math.min(Reroute.maxSplineOffset, finalDistance * 0.25) const finalDist = Math.min(Reroute.maxSplineOffset, finalDistance * 0.25)
const finalStartControl: ReadOnlyPoint = [ const finalStartControl: LitegraphPoint = [
finalDist * lastReroute.cos, finalDist * lastReroute.cos,
finalDist * lastReroute.sin finalDist * lastReroute.sin
] ]

View File

@@ -1,4 +1,4 @@
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces' import type { Rect } from '@/lib/litegraph/src/interfaces'
import type { Bounds } from '@/renderer/core/layout/types' import type { Bounds } from '@/renderer/core/layout/types'
/** /**
@@ -33,9 +33,7 @@ export const lcm = (a: number, b: number): number => {
* @param rectangles - Array of rectangle tuples in [x, y, width, height] format * @param rectangles - Array of rectangle tuples in [x, y, width, height] format
* @returns Bounds object with union rectangle, or null if no rectangles provided * @returns Bounds object with union rectangle, or null if no rectangles provided
*/ */
export function computeUnionBounds( export function computeUnionBounds(rectangles: readonly Rect[]): Bounds | null {
rectangles: readonly ReadOnlyRect[]
): Bounds | null {
const n = rectangles.length const n = rectangles.length
if (n === 0) { if (n === 0) {
return null return null

View File

@@ -1,6 +1,7 @@
import { afterEach, beforeEach, describe, expect, vi } from 'vitest' import { afterEach, beforeEach, describe, expect, vi } from 'vitest'
import type { INodeInputSlot, Point } from '@/lib/litegraph/src/litegraph' import type { INodeInputSlot, Point } from '@/lib/litegraph/src/litegraph'
import { Rectangle } from '@/lib/litegraph/src/litegraph'
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { LGraph } from '@/lib/litegraph/src/litegraph' import { LGraph } from '@/lib/litegraph/src/litegraph'
import { NodeInputSlot } from '@/lib/litegraph/src/litegraph' import { NodeInputSlot } from '@/lib/litegraph/src/litegraph'
@@ -84,8 +85,8 @@ describe('LGraphNode', () => {
})) }))
} }
node.configure(configureData) node.configure(configureData)
expect(node.pos).toEqual(new Float32Array([50, 60])) expect(node.pos).toEqual([50, 60])
expect(node.size).toEqual(new Float32Array([70, 80])) expect(node.size).toEqual([70, 80])
}) })
test('should configure inputs correctly', () => { test('should configure inputs correctly', () => {
@@ -571,7 +572,7 @@ describe('LGraphNode', () => {
name: 'test_in', name: 'test_in',
type: 'string', type: 'string',
link: null, link: null,
boundingRect: new Float32Array([0, 0, 0, 0]) boundingRect: new Rectangle(0, 0, 0, 0)
} }
}) })
test('should return position based on title height when collapsed', () => { test('should return position based on title height when collapsed', () => {
@@ -594,7 +595,7 @@ describe('LGraphNode', () => {
name: 'test_in_2', name: 'test_in_2',
type: 'number', type: 'number',
link: null, link: null,
boundingRect: new Float32Array([0, 0, 0, 0]) boundingRect: new Rectangle(0, 0, 0, 0)
} }
node.inputs = [inputSlot, inputSlot2] node.inputs = [inputSlot, inputSlot2]
const slotIndex = 0 const slotIndex = 0

View File

@@ -4,19 +4,19 @@ exports[`LGraph > supports schema v0.4 graphs > oldSchemaGraph 1`] = `
LGraph { LGraph {
"_groups": [ "_groups": [
LGraphGroup { LGraphGroup {
"_bounding": Float32Array [ "_bounding": [
20, 10,
20, 10,
1, 140,
3, 80,
], ],
"_children": Set {}, "_children": Set {},
"_nodes": [], "_nodes": [],
"_pos": Float32Array [ "_pos": [
20, 20,
20, 20,
], ],
"_size": Float32Array [ "_size": [
1, 1,
3, 3,
], ],
@@ -39,19 +39,19 @@ LGraph {
LGraphNode { LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -111,19 +111,19 @@ LGraph {
"1": LGraphNode { "1": LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],
@@ -184,19 +184,19 @@ LGraph {
LGraphNode { LGraphNode {
"_collapsed_width": undefined, "_collapsed_width": undefined,
"_level": undefined, "_level": undefined,
"_pos": Float32Array [ "_pos": [
10, 10,
10, 10,
], ],
"_posSize": Float32Array [ "_posSize": [
10, 0,
10, 0,
140, 0,
60, 0,
], ],
"_relative_id": undefined, "_relative_id": undefined,
"_shape": undefined, "_shape": undefined,
"_size": Float32Array [ "_size": [
140, 140,
60, 60,
], ],

View File

@@ -1,6 +1,7 @@
// TODO: Fix these tests after migration // TODO: Fix these tests after migration
import { test as baseTest } from 'vitest' import { test as baseTest } from 'vitest'
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import type { Point, Rect } from '@/lib/litegraph/src/interfaces' import type { Point, Rect } from '@/lib/litegraph/src/interfaces'
import { import {
addDirectionalOffset, addDirectionalOffset,
@@ -132,8 +133,8 @@ test('snapPoint correctly snaps points to grid', ({ expect }) => {
test('createBounds correctly creates bounding box', ({ expect }) => { test('createBounds correctly creates bounding box', ({ expect }) => {
const objects = [ const objects = [
{ boundingRect: [0, 0, 10, 10] as Rect }, { boundingRect: new Rectangle(0, 0, 10, 10) },
{ boundingRect: [5, 5, 10, 10] as Rect } { boundingRect: new Rectangle(5, 5, 10, 10) }
] ]
const defaultBounds = createBounds(objects) const defaultBounds = createBounds(objects)

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces' import type { Rect } from '@/lib/litegraph/src/interfaces'
import { computeUnionBounds, gcd, lcm } from '@/utils/mathUtil' import { computeUnionBounds, gcd, lcm } from '@/utils/mathUtil'
describe('mathUtil', () => { describe('mathUtil', () => {
@@ -27,9 +27,9 @@ describe('mathUtil', () => {
expect(computeUnionBounds([])).toBe(null) expect(computeUnionBounds([])).toBe(null)
}) })
// Tests for tuple format (ReadOnlyRect) // Tests for tuple format (Rect)
it('should work with ReadOnlyRect tuple format', () => { it('should work with Rect tuple format', () => {
const tuples: ReadOnlyRect[] = [ const tuples: Rect[] = [
[10, 20, 30, 40] as const, // bounds: 10,20 to 40,60 [10, 20, 30, 40] as const, // bounds: 10,20 to 40,60
[50, 10, 20, 30] as const // bounds: 50,10 to 70,40 [50, 10, 20, 30] as const // bounds: 50,10 to 70,40
] ]
@@ -44,8 +44,8 @@ describe('mathUtil', () => {
}) })
}) })
it('should handle single ReadOnlyRect tuple', () => { it('should handle single Rect tuple', () => {
const tuple: ReadOnlyRect = [10, 20, 30, 40] as const const tuple: Rect = [10, 20, 30, 40] as const
const result = computeUnionBounds([tuple]) const result = computeUnionBounds([tuple])
expect(result).toEqual({ expect(result).toEqual({
@@ -57,7 +57,7 @@ describe('mathUtil', () => {
}) })
it('should handle tuple format with negative dimensions', () => { it('should handle tuple format with negative dimensions', () => {
const tuples: ReadOnlyRect[] = [ const tuples: Rect[] = [
[100, 50, -20, -10] as const, // x+width=80, y+height=40 [100, 50, -20, -10] as const, // x+width=80, y+height=40
[90, 45, 15, 20] as const // x+width=105, y+height=65 [90, 45, 15, 20] as const // x+width=105, y+height=65
] ]
@@ -74,7 +74,7 @@ describe('mathUtil', () => {
it('should maintain optimal performance with SoA tuples', () => { it('should maintain optimal performance with SoA tuples', () => {
// Test that array access is as expected for typical selection sizes // Test that array access is as expected for typical selection sizes
const tuples: ReadOnlyRect[] = Array.from( const tuples: Rect[] = Array.from(
{ length: 10 }, { length: 10 },
(_, i) => (_, i) =>
[ [