Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Major] Split layout and draw of LGraphNode (Part1) #510

Closed
wants to merge 1 commit into from

Conversation

huchenlei
Copy link
Member

Splits layout and draw of LGraphNode.

This PR splits the layout and draw of LGraphNode so we can perform layout operations easier. Also this would help us better handle which element should interact with the cursor.

@huchenlei
Copy link
Member Author

Seems like when doing layout of a node with multiple DOM widgets, the location of DOM widget is not correctly captured.

The root issue is that DOMWidget is performing layout during drawing. https://github.com/Comfy-Org/ComfyUI_frontend/blob/aaca5191ab4451ae9edf41a71b363cb482161b56/src/scripts/domWidget.ts#L241-L251

Copy link
Contributor

@webfiltered webfiltered left a comment

Choose a reason for hiding this comment

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

Shiny! The more we split measure / arrange / draw, the easier our lives will be. Code is looking good - haven't tested yet though. Let me know if you want a hand w/that.


slot.draw(ctx, {
pos,
slot.data.draw(ctx, {
Copy link
Contributor

Choose a reason for hiding this comment

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

Had to go hunting for the meaning of data.draw here. From the way the interface is written, I assume there is a larger plan to use this in many other ways.

Not really requesting anything here, just wondering if there is friendlier or more explicit way to do this?

Comment on lines +4 to +14
export interface LayoutPoint {
x: number
y: number
}

export interface LayoutSize {
width: number
height: number
}

export interface LayoutRect extends LayoutPoint, LayoutSize { }
Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 Is this a new design for litegraph structs? I do think we should stick to the same data types everywhere.

If it is, we should probably actually have a quick look at real world perf - size/pos/rect easily get to millions of ops per second, and we're only going to be increasing this as we implement more layout containers. We're GPU-constrained on most systems atm, but that will likely change at some point.

Comment on lines +1 to +3
import type { LayoutRect } from "../types/layout"

export function computeBounds(elements: LayoutRect[]): LayoutRect {
Copy link
Contributor

Choose a reason for hiding this comment

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

measure.ts turned into a layout-function dumping ground. Some of it can probably move in with this guy?

Comment on lines +8 to +11
const minX = Math.min(...elements.map(e => e.x))
const minY = Math.min(...elements.map(e => e.y))
const maxX = Math.max(...elements.map(e => e.x + e.width))
const maxY = Math.max(...elements.map(e => e.y + e.height))
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const minX = Math.min(...elements.map(e => e.x))
const minY = Math.min(...elements.map(e => e.y))
const maxX = Math.max(...elements.map(e => e.x + e.width))
const maxY = Math.max(...elements.map(e => e.y + e.height))
let minX = Infinity
let minY = Infinity
let maxX = -Infinity
let maxY = -Infinity
for (const e of elements) {
if (e.x < minX) minX = e.x
if (e.y < minY) minY = e.y
if (e.x + e.width > maxX) maxX = e.x + e.width
if (e.y + e.height > maxY) maxY = e.y + e.height
}

Prefer single loop to uhm, 12 haha. 😛

Copy link
Contributor

Choose a reason for hiding this comment

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

n.b. in an error state (e.g. no elements have a valid "width"), this will have +/- Infinity instead of NaN.

@@ -3242,36 +3238,117 @@ export class LGraphNode implements Positionable, IPinnable {
highlight,
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
}
}
#measureSlots<T extends INodeInputSlot | INodeOutputSlot, C extends NodeInputSlot | NodeOutputSlot>(
layoutSlots: ElementGroupLayout<C>,
slots: T[],
slotClass: new (plain: T) => C,
connectingLink: ConnectingLink | null,
) {
if (!slots?.length) return
for (const [index, input] of slots.entries()) {
const slot = toClass(slotClass, input)
const pos = this.getConnectionPos(true, index)
const isValid = slot.isValidTarget(connectingLink)
const highlight = isValid && this.mouseOver?.inputId === index
layoutSlots.elements.push({
type: "slot",
id: index,
x: pos[0] - this.pos[0],
y: pos[1] - this.pos[1],
width: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
height: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
data: slot,
highlight,
invalid: !isValid,
})
}
}

I couldn't stop myself!

(replaces forEach below)

Comment on lines +3265 to +3282
this.inputs?.forEach((input, index) => {
const slot = toClass(NodeInputSlot, input)
const pos = this.getConnectionPos(true, index)
const isValid = slot.isValidTarget(connectingLink)
const highlight = isValid && this.mouseOver?.inputId === index

layout.inputSlots.elements.push({
type: "slot",
id: index,
x: pos[0] - this.pos[0],
y: pos[1] - this.pos[1],
width: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
height: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
data: slot,
highlight,
invalid: !isValid,
})
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
this.inputs?.forEach((input, index) => {
const slot = toClass(NodeInputSlot, input)
const pos = this.getConnectionPos(true, index)
const isValid = slot.isValidTarget(connectingLink)
const highlight = isValid && this.mouseOver?.inputId === index
layout.inputSlots.elements.push({
type: "slot",
id: index,
x: pos[0] - this.pos[0],
y: pos[1] - this.pos[1],
width: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
height: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
data: slot,
highlight,
invalid: !isValid,
})
})
this.#measureSlots(layout.inputSlots, this.inputs, NodeInputSlot, connectingLink)

Comment on lines +3284 to +3286
this.outputs?.forEach((output, index) => {
const slot = toClass(NodeOutputSlot, output)
const pos = this.getConnectionPos(false, index)
Copy link
Contributor

@webfiltered webfiltered Feb 10, 2025

Choose a reason for hiding this comment

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

Suggested change
this.outputs?.forEach((output, index) => {
const slot = toClass(NodeOutputSlot, output)
const pos = this.getConnectionPos(false, index)

outputs 1/3

// change opacity of incompatible slots when dragging a connection
this.outputs?.forEach((output, index) => {
const slot = toClass(NodeOutputSlot, output)
const pos = this.getConnectionPos(false, index)
const isValid = slot.isValidTarget(connectingLink)
Copy link
Contributor

@webfiltered webfiltered Feb 10, 2025

Choose a reason for hiding this comment

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

Suggested change
const isValid = slot.isValidTarget(connectingLink)

outputs 2/3

Comment on lines +3288 to +3301
const highlight = isValid && this.mouseOver?.outputId === index

layout.outputSlots.elements.push({
type: "slot",
id: index,
x: pos[0] - this.pos[0],
y: pos[1] - this.pos[1],
width: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
height: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
data: slot,
highlight,
invalid: !isValid,
})
})
Copy link
Contributor

@webfiltered webfiltered Feb 10, 2025

Choose a reason for hiding this comment

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

Suggested change
const highlight = isValid && this.mouseOver?.outputId === index
layout.outputSlots.elements.push({
type: "slot",
id: index,
x: pos[0] - this.pos[0],
y: pos[1] - this.pos[1],
width: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
height: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
data: slot,
highlight,
invalid: !isValid,
})
})
this.#measureSlots(layout.outputSlots, this.outputs, NodeOutputSlot, connectingLink)

outputs 3/3

Comment on lines +3313 to +3316
let posY = this.widgets_up ? 2 : (this.widgets_start_y ?? widgetsStartY + 2)
if (this.horizontal) {
posY = 2
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let posY = this.widgets_up ? 2 : (this.widgets_start_y ?? widgetsStartY + 2)
if (this.horizontal) {
posY = 2
}
let posY = this.widgets_up || this.horizontal ? 2 : (this.widgets_start_y ?? widgetsStartY + 2)

nit

@huchenlei huchenlei closed this Feb 14, 2025
@huchenlei huchenlei deleted the layout_node branch February 14, 2025 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants