Skip to content

Commit 4cee8ca

Browse files
committed
[Major] Split layout and draw
1 parent 6dd93a5 commit 4cee8ca

File tree

5 files changed

+241
-75
lines changed

5 files changed

+241
-75
lines changed

src/LGraphCanvas.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import { CanvasPointer } from "./CanvasPointer"
6868
import { toClass } from "./utils/type"
6969
import { type ConnectionColorContext } from "./NodeSlot"
7070
import { WIDGET_TYPE_MAP } from "./widgets/widgetMap"
71+
import type { NodeLayout } from "./types/layout"
7172

7273
interface IShowSearchOptions {
7374
node_to?: LGraphNode
@@ -4698,17 +4699,25 @@ export class LGraphCanvas implements ConnectionColorContext {
46984699

46994700
// render inputs and outputs
47004701
if (!node.collapsed) {
4701-
const max_y = node.drawSlots(ctx, {
4702-
colorContext: this,
4702+
const layout = node.computeLayout({
47034703
connectingLink: this.connecting_links?.[0],
4704+
})
4705+
node.drawSlots(ctx, {
4706+
layoutSlots: layout.inputSlots,
4707+
colorContext: this,
4708+
editorAlpha: this.editor_alpha,
4709+
lowQuality: this.low_quality,
4710+
})
4711+
node.drawSlots(ctx, {
4712+
layoutSlots: layout.outputSlots,
4713+
colorContext: this,
47044714
editorAlpha: this.editor_alpha,
47054715
lowQuality: this.low_quality,
47064716
})
4707-
47084717
ctx.textAlign = "left"
47094718
ctx.globalAlpha = 1
47104719

4711-
this.drawNodeWidgets(node, max_y, ctx)
4720+
this.drawNodeWidgets(node, 0, ctx, layout.widgets)
47124721
} else if (this.render_collapsed_slots) {
47134722
node.drawCollapsedSlots(ctx)
47144723
}
@@ -5555,9 +5564,10 @@ export class LGraphCanvas implements ConnectionColorContext {
55555564
node: LGraphNode,
55565565
posY: number,
55575566
ctx: CanvasRenderingContext2D,
5567+
layoutWidgets: NodeLayout["widgets"],
55585568
): void {
55595569
node.drawWidgets(ctx, {
5560-
y: posY,
5570+
layoutWidgets,
55615571
colorContext: this,
55625572
linkOverWidget: this.link_over_widget,
55635573
linkOverWidgetType: this.link_over_widget_type,

src/LGraphNode.ts

Lines changed: 147 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type { LGraphCanvas } from "./LGraphCanvas"
2424
import type { CanvasMouseEvent } from "./types/events"
2525
import type { DragAndScale } from "./DragAndScale"
2626
import type { RerouteId } from "./Reroute"
27+
import type { ElementLayout, NodeLayout } from "@/types/layout"
2728
import {
2829
LGraphEventMode,
2930
NodeSlotType,
@@ -37,6 +38,7 @@ import { LLink } from "./LLink"
3738
import { ConnectionColorContext, NodeInputSlot, NodeOutputSlot } from "./NodeSlot"
3839
import { WIDGET_TYPE_MAP } from "./widgets/widgetMap"
3940
import { toClass } from "./utils/type"
41+
import { computeBounds } from "@/utils/layout"
4042
export type NodeId = number | string
4143

4244
export interface INodePropertyInfo {
@@ -162,6 +164,13 @@ export class LGraphNode implements Positionable, IPinnable {
162164
return `${LiteGraph.NODE_TEXT_SIZE}px Arial`
163165
}
164166

167+
/**
168+
* The height of the node's title text.
169+
*/
170+
get titleHeight(): number {
171+
return LiteGraph.NODE_TITLE_HEIGHT
172+
}
173+
165174
graph: LGraph | null = null
166175
id: NodeId
167176
type: string | null = null
@@ -3087,37 +3096,37 @@ export class LGraphNode implements Positionable, IPinnable {
30873096
}
30883097

30893098
drawWidgets(ctx: CanvasRenderingContext2D, options: {
3090-
y: number
3099+
layoutWidgets: NodeLayout["widgets"]
30913100
colorContext: ConnectionColorContext
30923101
linkOverWidget: IWidget
30933102
linkOverWidgetType: ISlotType
30943103
lowQuality?: boolean
30953104
editorAlpha?: number
30963105
}): void {
3097-
if (!this.widgets) return
3098-
3099-
const { y, colorContext, linkOverWidget, linkOverWidgetType, lowQuality = false, editorAlpha = 1 } = options
3100-
let posY = y
3101-
if (this.horizontal || this.widgets_up) {
3102-
posY = 2
3103-
}
3104-
if (this.widgets_start_y != null) posY = this.widgets_start_y
3106+
const {
3107+
layoutWidgets,
3108+
colorContext,
3109+
linkOverWidget,
3110+
linkOverWidgetType,
3111+
lowQuality = false,
3112+
editorAlpha = 1,
3113+
} = options
31053114

3106-
const width = this.size[0]
3107-
const widgets = this.widgets
3108-
posY += 2
3109-
const H = LiteGraph.NODE_WIDGET_HEIGHT
3115+
const widgets = layoutWidgets.elements
31103116
const show_text = !lowQuality
3117+
31113118
ctx.save()
31123119
ctx.globalAlpha = editorAlpha
3113-
const margin = 15
31143120

3115-
for (const w of widgets) {
3116-
if (w.hidden || (w.advanced && !this.showAdvanced)) continue
3117-
const y = w.y || posY
3118-
const outline_color = w.advanced ? LiteGraph.WIDGET_ADVANCED_OUTLINE_COLOR : LiteGraph.WIDGET_OUTLINE_COLOR
3121+
for (const layoutWidget of widgets) {
3122+
const widget = layoutWidget.data
3123+
3124+
const y = layoutWidget.y
3125+
const widget_width = layoutWidget.width
3126+
const outline_color =
3127+
widget.advanced ? LiteGraph.WIDGET_ADVANCED_OUTLINE_COLOR : LiteGraph.WIDGET_OUTLINE_COLOR
31193128

3120-
if (w === linkOverWidget) {
3129+
if (widget === linkOverWidget) {
31213130
// Manually draw a slot next to the widget simulating an input
31223131
new NodeInputSlot({
31233132
name: "",
@@ -3126,20 +3135,20 @@ export class LGraphNode implements Positionable, IPinnable {
31263135
}).draw(ctx, { pos: [10, y + 10], colorContext })
31273136
}
31283137

3129-
w.last_y = y
3138+
widget.last_y = y
3139+
31303140
ctx.strokeStyle = outline_color
31313141
ctx.fillStyle = "#222"
31323142
ctx.textAlign = "left"
3133-
if (w.disabled) ctx.globalAlpha *= 0.5
3134-
const widget_width = w.width || width
31353143

3136-
const WidgetClass = WIDGET_TYPE_MAP[w.type]
3144+
if (layoutWidget.disabled) ctx.globalAlpha *= 0.5
3145+
3146+
const WidgetClass = WIDGET_TYPE_MAP[widget.type]
31373147
if (WidgetClass) {
3138-
toClass(WidgetClass, w).drawWidget(ctx, { y, width: widget_width, show_text, margin })
3148+
toClass(WidgetClass, widget).drawWidget(ctx, { y, width: widget_width, show_text, margin: layoutWidget.x })
31393149
} else {
3140-
w.draw?.(ctx, this, widget_width, y, H)
3150+
widget.draw?.(ctx, this, widget_width, y, layoutWidget.height)
31413151
}
3142-
posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4
31433152
ctx.globalAlpha = editorAlpha
31443153
}
31453154
ctx.restore()
@@ -3200,40 +3209,27 @@ export class LGraphNode implements Positionable, IPinnable {
32003209

32013210
/**
32023211
* Draws the node's input and output slots.
3203-
* @returns The maximum y-coordinate of the slots.
3204-
* TODO: Calculate the bounding box of the slots and return it instead of the maximum y-coordinate.
32053212
*/
32063213
drawSlots(ctx: CanvasRenderingContext2D, options: {
3214+
layoutSlots: NodeLayout["inputSlots"] | NodeLayout["outputSlots"]
32073215
colorContext: ConnectionColorContext
3208-
connectingLink: ConnectingLink | null
32093216
editorAlpha: number
32103217
lowQuality: boolean
3211-
}): number {
3212-
const { colorContext, connectingLink, editorAlpha, lowQuality } = options
3213-
let max_y = 0
3218+
}): void {
3219+
const { layoutSlots, colorContext, editorAlpha, lowQuality } = options
32143220

32153221
// input connection slots
32163222
// Reuse slot_pos to avoid creating a new Float32Array on each iteration
3217-
const slot_pos = new Float32Array(2)
3218-
for (const [i, input] of (this.inputs ?? []).entries()) {
3219-
const slot = toClass(NodeInputSlot, input)
3220-
3221-
// change opacity of incompatible slots when dragging a connection
3222-
const isValid = slot.isValidTarget(connectingLink)
3223-
const highlight = isValid && this.mouseOver?.inputId === i
3223+
for (const slot of layoutSlots.elements) {
3224+
const isValid = !slot.invalid
3225+
const highlight = !!slot.highlight
32243226
const label_color = highlight
32253227
? this.highlightColor
32263228
: LiteGraph.NODE_TEXT_COLOR
32273229
ctx.globalAlpha = isValid ? editorAlpha : 0.4 * editorAlpha
32283230

3229-
const pos = this.getConnectionPos(true, i, /* out= */slot_pos)
3230-
pos[0] -= this.pos[0]
3231-
pos[1] -= this.pos[1]
3232-
3233-
max_y = Math.max(max_y, pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5)
3234-
3235-
slot.draw(ctx, {
3236-
pos,
3231+
slot.data.draw(ctx, {
3232+
pos: [slot.x, slot.y],
32373233
colorContext,
32383234
labelColor: label_color,
32393235
horizontal: this.horizontal,
@@ -3242,36 +3238,117 @@ export class LGraphNode implements Positionable, IPinnable {
32423238
highlight,
32433239
})
32443240
}
3241+
}
32453242

3246-
// output connection slots
3247-
for (const [i, output] of (this.outputs ?? []).entries()) {
3248-
const slot = toClass(NodeOutputSlot, output)
3243+
computeLayout(options: {
3244+
connectingLink: ConnectingLink | null
3245+
}): NodeLayout {
3246+
const { connectingLink } = options
3247+
3248+
const layout: NodeLayout = {
3249+
bounds: { x: 0, y: 0, width: this.size[0], height: this.size[1] },
3250+
inputSlots: {
3251+
elements: [],
3252+
bounds: { x: 0, y: 0, width: 0, height: 0 },
3253+
},
3254+
outputSlots: {
3255+
elements: [],
3256+
bounds: { x: 0, y: 0, width: 0, height: 0 },
3257+
},
3258+
widgets: {
3259+
elements: [],
3260+
bounds: { x: 0, y: 0, width: 0, height: 0 },
3261+
},
3262+
}
3263+
3264+
// Compute slots layout
3265+
this.inputs?.forEach((input, index) => {
3266+
const slot = toClass(NodeInputSlot, input)
3267+
const pos = this.getConnectionPos(true, index)
3268+
const isValid = slot.isValidTarget(connectingLink)
3269+
const highlight = isValid && this.mouseOver?.inputId === index
3270+
3271+
layout.inputSlots.elements.push({
3272+
type: "slot",
3273+
id: index,
3274+
x: pos[0] - this.pos[0],
3275+
y: pos[1] - this.pos[1],
3276+
width: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
3277+
height: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
3278+
data: slot,
3279+
highlight,
3280+
invalid: !isValid,
3281+
})
3282+
})
32493283

3250-
// change opacity of incompatible slots when dragging a connection
3284+
this.outputs?.forEach((output, index) => {
3285+
const slot = toClass(NodeOutputSlot, output)
3286+
const pos = this.getConnectionPos(false, index)
32513287
const isValid = slot.isValidTarget(connectingLink)
3252-
const highlight = isValid && this.mouseOver?.outputId === i
3253-
const label_color = highlight
3254-
? this.highlightColor
3255-
: LiteGraph.NODE_TEXT_COLOR
3256-
ctx.globalAlpha = isValid ? editorAlpha : 0.4 * editorAlpha
3288+
const highlight = isValid && this.mouseOver?.outputId === index
3289+
3290+
layout.outputSlots.elements.push({
3291+
type: "slot",
3292+
id: index,
3293+
x: pos[0] - this.pos[0],
3294+
y: pos[1] - this.pos[1],
3295+
width: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
3296+
height: LiteGraph.NODE_SLOT_HEIGHT * 0.5,
3297+
data: slot,
3298+
highlight,
3299+
invalid: !isValid,
3300+
})
3301+
})
32573302

3258-
const pos = this.getConnectionPos(false, i, /* out= */slot_pos)
3259-
pos[0] -= this.pos[0]
3260-
pos[1] -= this.pos[1]
3303+
layout.inputSlots.bounds = computeBounds(layout.inputSlots.elements)
3304+
layout.outputSlots.bounds = computeBounds(layout.outputSlots.elements)
3305+
const slotsBounds = computeBounds([
3306+
layout.inputSlots.bounds,
3307+
layout.outputSlots.bounds,
3308+
])
3309+
const widgetsStartY = slotsBounds.height + slotsBounds.y
32613310

3262-
max_y = Math.max(max_y, pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5)
3311+
// Compute widgets layout
3312+
if (this.widgets?.length) {
3313+
let posY = this.widgets_up ? 2 : (this.widgets_start_y ?? widgetsStartY + 2)
3314+
if (this.horizontal) {
3315+
posY = 2
3316+
}
32633317

3264-
slot.draw(ctx, {
3265-
pos,
3266-
colorContext,
3267-
labelColor: label_color,
3268-
horizontal: this.horizontal,
3269-
lowQuality,
3270-
renderText: !lowQuality,
3271-
highlight,
3272-
})
3318+
const widgetElements: ElementLayout[] = []
3319+
const margin = 15
3320+
const H = LiteGraph.NODE_WIDGET_HEIGHT
3321+
3322+
for (const widget of this.widgets) {
3323+
if (widget.hidden || (widget.advanced && !this.showAdvanced)) {
3324+
continue
3325+
}
3326+
3327+
const y = widget.y || posY
3328+
const widget_width = widget.width || this.size[0]
3329+
const height = widget.computeSize
3330+
? widget.computeSize(widget_width)[1]
3331+
: H
3332+
3333+
widgetElements.push({
3334+
type: "widget",
3335+
x: margin,
3336+
y,
3337+
width: widget_width,
3338+
height,
3339+
data: widget,
3340+
disabled: widget.disabled,
3341+
})
3342+
3343+
posY += height + 4
3344+
}
3345+
3346+
layout.widgets = {
3347+
elements: widgetElements,
3348+
bounds: computeBounds(widgetElements),
3349+
}
32733350
}
32743351

3275-
return max_y
3352+
return layout
32763353
}
32773354
}

0 commit comments

Comments
 (0)