mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-24 08:44:06 +00:00
Add functionality to quickly disconnect moved input links (#7459)
Disconnections are frequently performed by dragging a link from an input slot and dropping it on the canvas, but needing to wait for the searchbox to pop up, and then needing to manually close out of this can make it feel slow. Sometimes, this will even result in users disabling the link release action for more responsive graph building. Instead, this PR introduces new functionality where a link which is moved only a short distance from a node input and dropped will be immediately disconnected instead of performing the default link release action.  ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7459-Add-functionality-to-quickly-disconnect-moved-input-links-2c86d73d365081919052f3856db8e672) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -2,6 +2,7 @@ import { toString } from 'es-toolkit/compat'
|
||||
import { toValue } from 'vue'
|
||||
|
||||
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'
|
||||
@@ -5014,6 +5015,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
}
|
||||
)
|
||||
}
|
||||
if (renderLink instanceof MovingInputLink) this.setDirty(false, true)
|
||||
|
||||
ctx.fillStyle = colour
|
||||
ctx.beginPath()
|
||||
@@ -5908,6 +5910,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
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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',
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
@@ -72,7 +74,11 @@ export class LinkConnectorAdapter {
|
||||
beginFromInput(
|
||||
nodeId: NodeId,
|
||||
inputIndex: number,
|
||||
opts?: { moveExisting?: boolean; fromRerouteId?: RerouteId }
|
||||
opts?: {
|
||||
fromRerouteId?: RerouteId
|
||||
layout?: SlotLayout
|
||||
moveExisting?: boolean
|
||||
}
|
||||
): void {
|
||||
const node = this.network.getNodeById(nodeId)
|
||||
const input = node?.inputs?.[inputIndex]
|
||||
@@ -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,
|
||||
|
||||
@@ -671,7 +671,8 @@ export function useSlotLinkInteraction({
|
||||
})
|
||||
} else {
|
||||
activeAdapter.beginFromInput(localNodeId, index, {
|
||||
moveExisting: shouldMoveExistingInput
|
||||
moveExisting: shouldMoveExistingInput,
|
||||
layout
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user