Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/lib/litegraph/src/LGraphCanvas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { toString } from 'es-toolkit/compat'

import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants'
import { MovingInputLink } from '@/lib/litegraph/src/canvas/MovingInputLink'
import { LitegraphLinkAdapter } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
import type { LinkRenderContext } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
import { getSlotPosition } from '@/renderer/core/canvas/litegraph/slotCalculations'
Expand Down Expand Up @@ -4769,6 +4770,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
}
)
}
if (renderLink instanceof MovingInputLink) this.setDirty(false, true)

ctx.fillStyle = colour
ctx.beginPath()
Expand Down Expand Up @@ -5699,6 +5701,12 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
// Never draw slots when the pointer is down
if (!this.pointer.isDown) reroute.drawSlots(ctx)
}

const highlightPos = this.#getHighlightPosition()
this.linkConnector.renderLinks
.filter((rl) => rl instanceof MovingInputLink)
.forEach((rl) => rl.drawConnectionCircle(ctx, highlightPos))

ctx.globalAlpha = 1
}

Expand Down
24 changes: 21 additions & 3 deletions src/lib/litegraph/src/canvas/LinkConnector.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { remove } from 'es-toolkit'

import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import { LLink } from '@/lib/litegraph/src/LLink'
import type { Reroute } from '@/lib/litegraph/src/Reroute'
Expand All @@ -13,7 +15,8 @@ import type {
INodeOutputSlot,
ItemLocator,
LinkNetwork,
LinkSegment
LinkSegment,
Point
} from '@/lib/litegraph/src/interfaces'
import { EmptySubgraphInput } from '@/lib/litegraph/src/subgraph/EmptySubgraphInput'
import { EmptySubgraphOutput } from '@/lib/litegraph/src/subgraph/EmptySubgraphOutput'
Expand Down Expand Up @@ -130,7 +133,11 @@ export class LinkConnector {
}

/** Drag an existing link to a different input. */
moveInputLink(network: LinkNetwork, input: INodeInputSlot): void {
moveInputLink(
network: LinkNetwork,
input: INodeInputSlot,
opts?: { startPoint?: Point }
): void {
if (this.isConnecting) throw new Error('Already dragging links.')

const { state, inputLinks, renderLinks } = this
Expand Down Expand Up @@ -221,7 +228,13 @@ export class LinkConnector {
// Regular node links
try {
const reroute = network.getReroute(link.parentId)
const renderLink = new MovingInputLink(network, link, reroute)
const renderLink = new MovingInputLink(
network,
link,
reroute,
undefined,
opts?.startPoint
)

const mayContinue = this.events.dispatch(
'before-move-input',
Expand Down Expand Up @@ -860,6 +873,11 @@ export class LinkConnector {
}

dropOnNothing(event: CanvasPointerEvent): void {
remove(
this.renderLinks,
(link) => link instanceof MovingInputLink && link.disconnectOnDrop
).forEach((link) => (link as MovingLinkBase).disconnect())
if (this.renderLinks.length === 0) return
// For external event only.
const mayContinue = this.events.dispatch('dropped-on-canvas', event)
if (mayContinue === false) return
Expand Down
27 changes: 26 additions & 1 deletion src/lib/litegraph/src/canvas/MovingInputLink.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import type { LLink } from '@/lib/litegraph/src/LLink'
import type { Reroute } from '@/lib/litegraph/src/Reroute'
import type { CustomEventTarget } from '@/lib/litegraph/src/infrastructure/CustomEventTarget'
Expand All @@ -24,12 +25,15 @@ export class MovingInputLink extends MovingLinkBase {
readonly fromPos: Point
readonly fromDirection: LinkDirection
readonly fromSlotIndex: number
disconnectOnDrop: boolean
readonly disconnectOrigin: Point

constructor(
network: LinkNetwork,
link: LLink,
fromReroute?: Reroute,
dragDirection: LinkDirection = LinkDirection.CENTER
dragDirection: LinkDirection = LinkDirection.CENTER,
startPoint?: Point
) {
super(network, link, 'input', fromReroute, dragDirection)

Expand All @@ -38,6 +42,8 @@ export class MovingInputLink extends MovingLinkBase {
this.fromPos = fromReroute?.pos ?? this.outputPos
this.fromDirection = LinkDirection.NONE
this.fromSlotIndex = this.outputIndex
this.disconnectOnDrop = true
this.disconnectOrigin = startPoint ?? this.inputPos
}

canConnectToInput(
Expand Down Expand Up @@ -130,4 +136,23 @@ export class MovingInputLink extends MovingLinkBase {
disconnect(): boolean {
return this.inputNode.disconnectInput(this.inputIndex, true)
}

drawConnectionCircle(ctx: CanvasRenderingContext2D, to: Readonly<Point>) {
if (!this.disconnectOnDrop) return

const [originX, originY] = this.disconnectOrigin
const radius = 35
const distSquared = (originX - to[0]) ** 2 + (originY - to[1]) ** 2

ctx.save()
ctx.strokeStyle = LiteGraph.WIDGET_OUTLINE_COLOR
ctx.lineWidth = 2
ctx.beginPath()
ctx.moveTo(originX + radius, originY)
ctx.arc(originX, originY, radius, 0, Math.PI * 2)
ctx.stroke()
ctx.restore()

this.disconnectOnDrop = distSquared < radius ** 2
}
}
13 changes: 11 additions & 2 deletions src/renderer/core/canvas/links/linkConnectorAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { SlotLayout } from '@/renderer/core/layout/types'
import type { Point } from '@/lib/litegraph/src/interfaces'
import type { LGraph } from '@/lib/litegraph/src/LGraph'
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
import type { RerouteId } from '@/lib/litegraph/src/Reroute'
Expand Down Expand Up @@ -72,7 +74,11 @@ export class LinkConnectorAdapter {
beginFromInput(
nodeId: NodeId,
inputIndex: number,
opts?: { moveExisting?: boolean; fromRerouteId?: RerouteId }
opts?: {
fromRerouteId?: RerouteId
layout?: SlotLayout
moveExisting?: boolean
}
Comment on lines 77 to 81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Type inconsistency: layout should be optional.

The type declares layout: SlotLayout (required when opts is present), but line 85 checks opts?.layout, treating it as optional. This mismatch weakens type safety.

Apply this diff to correct the type:

   opts?: {
     moveExisting?: boolean
     fromRerouteId?: RerouteId
-    layout: SlotLayout
+    layout?: SlotLayout
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
opts?: {
moveExisting?: boolean
fromRerouteId?: RerouteId
layout: SlotLayout
}
opts?: {
moveExisting?: boolean
fromRerouteId?: RerouteId
layout?: SlotLayout
}
🤖 Prompt for AI Agents
In src/renderer/core/canvas/links/linkConnectorAdapter.ts around lines 76 to 80,
the opts type currently declares layout as required (layout: SlotLayout) but the
code treats opts?.layout as optional; change the opts type so layout is optional
(layout?: SlotLayout) to match usage and restore type safety. Update the type
definition only (making layout optional) and run TypeScript checks to ensure no
other call-sites relied on layout being mandatory; if any call-sites assumed
layout exists, add safe guards (e.g., optional chaining or defaults) where
necessary.

Comment on lines 77 to 81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Required before Optional

Suggested change
opts?: {
moveExisting?: boolean
fromRerouteId?: RerouteId
layout: SlotLayout
}
opts?: {
layout: SlotLayout
moveExisting?: boolean
fromRerouteId?: RerouteId
}

): void {
const node = this.network.getNodeById(nodeId)
const input = node?.inputs?.[inputIndex]
Expand All @@ -81,7 +87,10 @@ export class LinkConnectorAdapter {
const fromReroute = this.network.getReroute(opts?.fromRerouteId)

if (opts?.moveExisting) {
this.linkConnector.moveInputLink(this.network, input)
const startPoint: Point | undefined = opts.layout
? [opts.layout.position.x, opts.layout.position.y]
: undefined
this.linkConnector.moveInputLink(this.network, input, { startPoint })
} else {
this.linkConnector.dragNewFromInput(
this.network,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,8 @@ export function useSlotLinkInteraction({
})
} else {
activeAdapter.beginFromInput(localNodeId, index, {
moveExisting: shouldMoveExistingInput
moveExisting: shouldMoveExistingInput,
layout
})
}

Expand Down
Loading