mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 22:37:32 +00:00
Improve link drag & drop (#380)
- Resolves https://github.com/Comfy-Org/litegraph.js/issues/309#issuecomment-2508726168 - Output issue still pending - Splits connecting links `pointerup` handler to separate function, which can now be called from `CanvasPointer` callbacks - Minor refactor; no functional changes ### Behaviour change When moving existing links from an input slot, the link will not be disconnected until the drop event occurs. ### Current Shift + drag https://github.com/user-attachments/assets/0b98f9bf-3d5f-467e-9a9b-e5695e5a0d0b ### Proposed Shift + drag https://github.com/user-attachments/assets/0bc36215-0247-41da-8050-e8df09addf23
This commit is contained in:
@@ -2242,16 +2242,16 @@ export class LGraphCanvas implements ConnectionColorContext {
|
||||
if (!link) continue
|
||||
|
||||
const slot = link.target_slot
|
||||
const linked_node = graph._nodes_by_id[link.target_id]
|
||||
const input = linked_node.inputs[slot]
|
||||
const pos = linked_node.getConnectionPos(true, slot)
|
||||
const otherNode = graph._nodes_by_id[link.target_id]
|
||||
const input = otherNode.inputs[slot]
|
||||
const pos = otherNode.getConnectionPos(true, slot)
|
||||
|
||||
this.connecting_links.push({
|
||||
node: linked_node,
|
||||
slot: slot,
|
||||
input: input,
|
||||
node: otherNode,
|
||||
slot,
|
||||
input,
|
||||
output: null,
|
||||
pos: pos,
|
||||
pos,
|
||||
direction: LinkDirection.RIGHT,
|
||||
})
|
||||
}
|
||||
@@ -2320,14 +2320,21 @@ export class LGraphCanvas implements ConnectionColorContext {
|
||||
slot,
|
||||
output: linked_node.outputs[slot],
|
||||
pos: linked_node.getConnectionPos(false, slot),
|
||||
afterRerouteId: link_info.parentId,
|
||||
}
|
||||
this.connecting_links = [connecting]
|
||||
|
||||
pointer.onDragStart = () => {
|
||||
if (this.allow_reconnect_links && !LiteGraph.click_do_break_link_to)
|
||||
node.disconnectInput(i)
|
||||
connecting.output = linked_node.outputs[slot]
|
||||
}
|
||||
pointer.onDragEnd = (upEvent) => {
|
||||
if (this.allow_reconnect_links && !LiteGraph.click_do_break_link_to) {
|
||||
node.disconnectInput(i)
|
||||
}
|
||||
this.#processConnectingLinks(upEvent)
|
||||
connecting.output = linked_node.outputs[slot]
|
||||
this.connecting_links = null
|
||||
}
|
||||
|
||||
this.dirty_bgcanvas = true
|
||||
}
|
||||
@@ -2902,92 +2909,10 @@ export class LGraphCanvas implements ConnectionColorContext {
|
||||
|
||||
const x = e.canvasX
|
||||
const y = e.canvasY
|
||||
const node = graph.getNodeOnPos(x, y, this.visible_nodes)
|
||||
|
||||
if (this.connecting_links?.length) {
|
||||
// node below mouse
|
||||
const firstLink = this.connecting_links[0]
|
||||
if (node) {
|
||||
for (const link of this.connecting_links) {
|
||||
// dragging a connection
|
||||
this.#dirty()
|
||||
|
||||
// slot below mouse? connect
|
||||
if (link.output) {
|
||||
const slot = this.isOverNodeInput(node, x, y)
|
||||
if (slot != -1) {
|
||||
link.node.connect(link.slot, node, slot, link.afterRerouteId)
|
||||
} else if (this.link_over_widget) {
|
||||
this.emitEvent({
|
||||
subType: "connectingWidgetLink",
|
||||
link,
|
||||
node,
|
||||
widget: this.link_over_widget,
|
||||
})
|
||||
this.link_over_widget = null
|
||||
} else {
|
||||
// not on top of an input
|
||||
// look for a good slot
|
||||
link.node.connectByType(link.slot, node, link.output.type, {
|
||||
afterRerouteId: link.afterRerouteId,
|
||||
})
|
||||
}
|
||||
} else if (link.input) {
|
||||
const slot = this.isOverNodeOutput(node, x, y)
|
||||
|
||||
if (slot != -1) {
|
||||
// this is inverted has output-input nature like
|
||||
node.connect(slot, link.node, link.slot, link.afterRerouteId)
|
||||
} else {
|
||||
// not on top of an input
|
||||
// look for a good slot
|
||||
link.node.connectByTypeOutput(
|
||||
link.slot,
|
||||
node,
|
||||
link.input.type,
|
||||
{ afterRerouteId: link.afterRerouteId },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (firstLink.input || firstLink.output) {
|
||||
// For external event only.
|
||||
const linkReleaseContextExtended: LinkReleaseContextExtended = {
|
||||
links: this.connecting_links,
|
||||
}
|
||||
this.emitEvent({
|
||||
subType: "empty-release",
|
||||
originalEvent: e,
|
||||
linkReleaseContext: linkReleaseContextExtended,
|
||||
})
|
||||
// No longer in use
|
||||
// add menu when releasing link in empty space
|
||||
if (LiteGraph.release_link_on_empty_shows_menu) {
|
||||
const linkReleaseContext = firstLink.output
|
||||
? {
|
||||
node_from: firstLink.node,
|
||||
slot_from: firstLink.output,
|
||||
type_filter_in: firstLink.output.type,
|
||||
}
|
||||
: {
|
||||
node_to: firstLink.node,
|
||||
slot_from: firstLink.input,
|
||||
type_filter_out: firstLink.input?.type,
|
||||
}
|
||||
|
||||
if (e.shiftKey) {
|
||||
if (this.allow_searchbox) {
|
||||
this.showSearchBox(e, linkReleaseContext)
|
||||
}
|
||||
} else {
|
||||
if (firstLink.output) {
|
||||
this.showConnectionMenu({ nodeFrom: firstLink.node, slotFrom: firstLink.output, e: e })
|
||||
} else if (firstLink.input) {
|
||||
this.showConnectionMenu({ nodeTo: firstLink.node, slotTo: firstLink.input, e: e })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.#processConnectingLinks(e)
|
||||
} else {
|
||||
this.dirty_canvas = true
|
||||
|
||||
@@ -3019,6 +2944,98 @@ export class LGraphCanvas implements ConnectionColorContext {
|
||||
return
|
||||
}
|
||||
|
||||
#processConnectingLinks(e: CanvasPointerEvent) {
|
||||
const { graph, connecting_links } = this
|
||||
if (!graph) throw new NullGraphError()
|
||||
if (!connecting_links) return
|
||||
|
||||
const { canvasX: x, canvasY: y } = e
|
||||
const node = graph.getNodeOnPos(x, y, this.visible_nodes)
|
||||
const firstLink = connecting_links[0]
|
||||
|
||||
if (node) {
|
||||
for (const link of connecting_links) {
|
||||
// dragging a connection
|
||||
this.#dirty()
|
||||
|
||||
// slot below mouse? connect
|
||||
if (link.output) {
|
||||
const slot = this.isOverNodeInput(node, x, y)
|
||||
if (slot != -1) {
|
||||
link.node.connect(link.slot, node, slot, link.afterRerouteId)
|
||||
} else if (this.link_over_widget) {
|
||||
this.emitEvent({
|
||||
subType: "connectingWidgetLink",
|
||||
link,
|
||||
node,
|
||||
widget: this.link_over_widget,
|
||||
})
|
||||
this.link_over_widget = null
|
||||
} else {
|
||||
// not on top of an input
|
||||
// look for a good slot
|
||||
link.node.connectByType(link.slot, node, link.output.type, {
|
||||
afterRerouteId: link.afterRerouteId,
|
||||
})
|
||||
}
|
||||
} else if (link.input) {
|
||||
const slot = this.isOverNodeOutput(node, x, y)
|
||||
|
||||
if (slot != -1) {
|
||||
// this is inverted has output-input nature like
|
||||
node.connect(slot, link.node, link.slot, link.afterRerouteId)
|
||||
} else {
|
||||
// not on top of an input
|
||||
// look for a good slot
|
||||
link.node.connectByTypeOutput(
|
||||
link.slot,
|
||||
node,
|
||||
link.input.type,
|
||||
{ afterRerouteId: link.afterRerouteId },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (firstLink.input || firstLink.output) {
|
||||
// For external event only.
|
||||
const linkReleaseContextExtended: LinkReleaseContextExtended = {
|
||||
links: connecting_links,
|
||||
}
|
||||
this.emitEvent({
|
||||
subType: "empty-release",
|
||||
originalEvent: e,
|
||||
linkReleaseContext: linkReleaseContextExtended,
|
||||
})
|
||||
// No longer in use
|
||||
// add menu when releasing link in empty space
|
||||
if (LiteGraph.release_link_on_empty_shows_menu) {
|
||||
const linkReleaseContext = firstLink.output
|
||||
? {
|
||||
node_from: firstLink.node,
|
||||
slot_from: firstLink.output,
|
||||
type_filter_in: firstLink.output.type,
|
||||
}
|
||||
: {
|
||||
node_to: firstLink.node,
|
||||
slot_from: firstLink.input,
|
||||
type_filter_out: firstLink.input?.type,
|
||||
}
|
||||
|
||||
if (e.shiftKey) {
|
||||
if (this.allow_searchbox) {
|
||||
this.showSearchBox(e, linkReleaseContext)
|
||||
}
|
||||
} else {
|
||||
if (firstLink.output) {
|
||||
this.showConnectionMenu({ nodeFrom: firstLink.node, slotFrom: firstLink.output, e: e })
|
||||
} else if (firstLink.input) {
|
||||
this.showConnectionMenu({ nodeTo: firstLink.node, slotTo: firstLink.input, e: e })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse moves off the canvas. Clears all node hover states.
|
||||
* @param e
|
||||
|
||||
Reference in New Issue
Block a user