@@ -24,6 +24,7 @@ import type { LGraphCanvas } from "./LGraphCanvas"
2424import type { CanvasMouseEvent } from "./types/events"
2525import type { DragAndScale } from "./DragAndScale"
2626import type { RerouteId } from "./Reroute"
27+ import type { ElementLayout , NodeLayout } from "@/types/layout"
2728import {
2829 LGraphEventMode ,
2930 NodeSlotType ,
@@ -37,6 +38,7 @@ import { LLink } from "./LLink"
3738import { ConnectionColorContext , NodeInputSlot , NodeOutputSlot } from "./NodeSlot"
3839import { WIDGET_TYPE_MAP } from "./widgets/widgetMap"
3940import { toClass } from "./utils/type"
41+ import { computeBounds } from "@/utils/layout"
4042export type NodeId = number | string
4143
4244export 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