mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
Support dragging from output to output
This commit is contained in:
@@ -39,8 +39,8 @@ export function evaluateCompatibility(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (source.type === 'output') {
|
if (source.type === 'output') {
|
||||||
if (candidate.layout.type !== 'input') {
|
if (candidate.layout.type === 'output') {
|
||||||
return { allowable: false }
|
return { allowable: Boolean(source.multiOutputDrag), targetNode }
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputSlot = sourceNode.outputs?.[source.slotIndex]
|
const outputSlot = sourceNode.outputs?.[source.slotIndex]
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { reactive, readonly } from 'vue'
|
import { reactive, readonly } from 'vue'
|
||||||
|
|
||||||
|
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||||
|
import type { LinkId } from '@/lib/litegraph/src/LLink'
|
||||||
|
import type { RerouteId } from '@/lib/litegraph/src/Reroute'
|
||||||
import type { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
|
import type { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
|
||||||
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
|
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
|
||||||
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||||
@@ -14,6 +17,7 @@ export interface SlotDragSource {
|
|||||||
direction: LinkDirection
|
direction: LinkDirection
|
||||||
position: Readonly<Point>
|
position: Readonly<Point>
|
||||||
linkId?: number
|
linkId?: number
|
||||||
|
multiOutputDrag?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SlotDropCandidate {
|
export interface SlotDropCandidate {
|
||||||
@@ -21,6 +25,14 @@ export interface SlotDropCandidate {
|
|||||||
compatible: boolean
|
compatible: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Types shared by multi-output drag logic
|
||||||
|
export interface MovedOutputNormalLink {
|
||||||
|
linkId: LinkId
|
||||||
|
inputNodeId: NodeId
|
||||||
|
inputSlotIndex: number
|
||||||
|
parentRerouteId?: RerouteId
|
||||||
|
}
|
||||||
|
|
||||||
interface PointerPosition {
|
interface PointerPosition {
|
||||||
client: Point
|
client: Point
|
||||||
canvas: Point
|
canvas: Point
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import { onBeforeUnmount } from 'vue'
|
|||||||
import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'
|
import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'
|
||||||
import type { LGraph } from '@/lib/litegraph/src/LGraph'
|
import type { LGraph } from '@/lib/litegraph/src/LGraph'
|
||||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||||
import { LLink } from '@/lib/litegraph/src/LLink'
|
import { LLink, type LinkId } from '@/lib/litegraph/src/LLink'
|
||||||
|
import type { RerouteId } from '@/lib/litegraph/src/Reroute'
|
||||||
import type { INodeInputSlot } from '@/lib/litegraph/src/interfaces'
|
import type { INodeInputSlot } from '@/lib/litegraph/src/interfaces'
|
||||||
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
|
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
|
||||||
import { evaluateCompatibility } from '@/renderer/core/canvas/links/slotLinkCompatibility'
|
import { evaluateCompatibility } from '@/renderer/core/canvas/links/slotLinkCompatibility'
|
||||||
|
import type { MovedOutputNormalLink } from '@/renderer/core/canvas/links/slotLinkDragState'
|
||||||
import {
|
import {
|
||||||
type SlotDropCandidate,
|
type SlotDropCandidate,
|
||||||
useSlotLinkDragState
|
useSlotLinkDragState
|
||||||
@@ -113,6 +115,12 @@ export function useSlotLinkInteraction({
|
|||||||
|
|
||||||
const pointerSession = createPointerSession()
|
const pointerSession = createPointerSession()
|
||||||
|
|
||||||
|
const draggingLinkIds = new Set<LinkId>()
|
||||||
|
const draggingRerouteIds = new Set<RerouteId>()
|
||||||
|
|
||||||
|
const movedOutputNormalLinks: MovedOutputNormalLink[] = []
|
||||||
|
const movedOutputFloatingLinks: LLink[] = []
|
||||||
|
|
||||||
const resolveLinkOrigin = (
|
const resolveLinkOrigin = (
|
||||||
graph: LGraph,
|
graph: LGraph,
|
||||||
link: LLink | undefined
|
link: LLink | undefined
|
||||||
@@ -258,20 +266,32 @@ export function useSlotLinkInteraction({
|
|||||||
const canvas = app.canvas
|
const canvas = app.canvas
|
||||||
const graph = canvas?.graph
|
const graph = canvas?.graph
|
||||||
const source = state.source
|
const source = state.source
|
||||||
if (!canvas || !graph || !source) return
|
if (!canvas || !graph) return
|
||||||
|
|
||||||
if (source.linkId != null) {
|
if (source?.linkId != null) {
|
||||||
const activeLink = graph.getLink(source.linkId)
|
const activeLink = graph.getLink(source.linkId)
|
||||||
if (activeLink) {
|
if (activeLink) delete activeLink._dragging
|
||||||
delete activeLink._dragging
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const id of draggingLinkIds) {
|
||||||
|
const link = graph.getLink(id)
|
||||||
|
if (link) delete link._dragging
|
||||||
|
}
|
||||||
|
for (const id of draggingRerouteIds) {
|
||||||
|
const reroute = graph.getReroute(id)
|
||||||
|
if (reroute) reroute._dragging = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
draggingLinkIds.clear()
|
||||||
|
draggingRerouteIds.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanupInteraction = () => {
|
const cleanupInteraction = () => {
|
||||||
clearDraggingFlags()
|
clearDraggingFlags()
|
||||||
pointerSession.clear()
|
pointerSession.clear()
|
||||||
endDrag()
|
endDrag()
|
||||||
|
movedOutputNormalLinks.length = 0
|
||||||
|
movedOutputFloatingLinks.length = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const disconnectSourceLink = (): boolean => {
|
const disconnectSourceLink = (): boolean => {
|
||||||
@@ -318,6 +338,49 @@ export function useSlotLinkInteraction({
|
|||||||
const targetNode = graph.getNodeById(Number(slotLayout.nodeId))
|
const targetNode = graph.getNodeById(Number(slotLayout.nodeId))
|
||||||
if (!sourceNode || !targetNode) return false
|
if (!sourceNode || !targetNode) return false
|
||||||
|
|
||||||
|
// Output ➝ Output (shift‑drag move all links)
|
||||||
|
if (source.type === 'output' && slotLayout.type === 'output') {
|
||||||
|
if (!source.multiOutputDrag) return false
|
||||||
|
|
||||||
|
const targetOutput = targetNode.outputs?.[slotLayout.index]
|
||||||
|
if (!targetOutput) return false
|
||||||
|
|
||||||
|
// Reconnect all normal links captured at drag start
|
||||||
|
for (const {
|
||||||
|
inputNodeId,
|
||||||
|
inputSlotIndex,
|
||||||
|
parentRerouteId
|
||||||
|
} of movedOutputNormalLinks) {
|
||||||
|
const inputNode = graph.getNodeById(inputNodeId)
|
||||||
|
const inputSlot = inputNode?.inputs?.[inputSlotIndex]
|
||||||
|
if (!inputNode || !inputSlot) continue
|
||||||
|
|
||||||
|
targetNode.connectSlots(
|
||||||
|
targetOutput,
|
||||||
|
inputNode,
|
||||||
|
inputSlot,
|
||||||
|
parentRerouteId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move any floating links across to the new output
|
||||||
|
const sourceNodeAtStart = graph.getNodeById(Number(source.nodeId))
|
||||||
|
const sourceOutputAtStart = sourceNodeAtStart?.outputs?.[source.slotIndex]
|
||||||
|
if (sourceOutputAtStart?._floatingLinks?.size) {
|
||||||
|
for (const floatingLink of movedOutputFloatingLinks) {
|
||||||
|
sourceOutputAtStart._floatingLinks?.delete(floatingLink)
|
||||||
|
|
||||||
|
floatingLink.origin_id = targetNode.id
|
||||||
|
floatingLink.origin_slot = slotLayout.index
|
||||||
|
|
||||||
|
targetOutput._floatingLinks ??= new Set()
|
||||||
|
targetOutput._floatingLinks.add(floatingLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if (source.type === 'output' && slotLayout.type === 'input') {
|
if (source.type === 'output' && slotLayout.type === 'input') {
|
||||||
const outputSlot = sourceNode.outputs?.[source.slotIndex]
|
const outputSlot = sourceNode.outputs?.[source.slotIndex]
|
||||||
const inputSlot = targetNode.inputs?.[slotLayout.index]
|
const inputSlot = targetNode.inputs?.[slotLayout.index]
|
||||||
@@ -459,6 +522,50 @@ export function useSlotLinkInteraction({
|
|||||||
existingLink._dragging = true
|
existingLink._dragging = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const outputSlot =
|
||||||
|
type === 'output' ? resolvedNode?.outputs?.[index] : undefined
|
||||||
|
const isMultiOutputDrag =
|
||||||
|
type === 'output' &&
|
||||||
|
Boolean(
|
||||||
|
outputSlot &&
|
||||||
|
(outputSlot.links?.length || outputSlot._floatingLinks?.size)
|
||||||
|
) &&
|
||||||
|
event.shiftKey
|
||||||
|
|
||||||
|
if (isMultiOutputDrag && outputSlot) {
|
||||||
|
movedOutputNormalLinks.length = 0
|
||||||
|
movedOutputFloatingLinks.length = 0
|
||||||
|
|
||||||
|
if (outputSlot.links?.length) {
|
||||||
|
for (const linkId of outputSlot.links) {
|
||||||
|
const link = graph.getLink(linkId)
|
||||||
|
if (!link) continue
|
||||||
|
|
||||||
|
const firstReroute = LLink.getFirstReroute(graph, link)
|
||||||
|
if (firstReroute) {
|
||||||
|
firstReroute._dragging = true
|
||||||
|
draggingRerouteIds.add(firstReroute.id)
|
||||||
|
} else {
|
||||||
|
link._dragging = true
|
||||||
|
draggingLinkIds.add(link.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
movedOutputNormalLinks.push({
|
||||||
|
linkId: link.id,
|
||||||
|
inputNodeId: link.target_id,
|
||||||
|
inputSlotIndex: link.target_slot,
|
||||||
|
parentRerouteId: link.parentId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputSlot._floatingLinks?.size) {
|
||||||
|
for (const floatingLink of outputSlot._floatingLinks) {
|
||||||
|
movedOutputFloatingLinks.push(floatingLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const direction = existingAnchor?.direction ?? baseDirection
|
const direction = existingAnchor?.direction ?? baseDirection
|
||||||
const startPosition = existingAnchor?.position ?? {
|
const startPosition = existingAnchor?.position ?? {
|
||||||
x: layout.position.x,
|
x: layout.position.x,
|
||||||
@@ -472,7 +579,8 @@ export function useSlotLinkInteraction({
|
|||||||
type,
|
type,
|
||||||
direction,
|
direction,
|
||||||
position: startPosition,
|
position: startPosition,
|
||||||
linkId: !shouldBreakExistingLink ? existingLink?.id : undefined
|
linkId: !shouldBreakExistingLink ? existingLink?.id : undefined,
|
||||||
|
multiOutputDrag: isMultiOutputDrag
|
||||||
},
|
},
|
||||||
event.pointerId
|
event.pointerId
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user