@@ -36,6 +36,7 @@ import { getNodeInputOnPos, getNodeOutputOnPos } from "./canvas/measureSlots"
3636import { NullGraphError } from "./infrastructure/NullGraphError"
3737import { Rectangle } from "./infrastructure/Rectangle"
3838import { BadgePosition , LGraphBadge } from "./LGraphBadge"
39+ import { LGraphButton , type LGraphButtonOptions } from "./LGraphButton"
3940import { LGraphCanvas } from "./LGraphCanvas"
4041import { type LGraphNodeConstructor , LiteGraph , type Subgraph , type SubgraphNode } from "./litegraph"
4142import { LLink } from "./LLink"
@@ -52,6 +53,7 @@ import {
5253import { findFreeSlotOfType } from "./utils/collections"
5354import { warnDeprecated } from "./utils/feedback"
5455import { distributeSpace } from "./utils/spaceDistribution"
56+ import { truncateText } from "./utils/textUtils"
5557import { toClass } from "./utils/type"
5658import { BaseWidget } from "./widgets/BaseWidget"
5759import { toConcreteWidget , type WidgetTypeMap } from "./widgets/widgetMap"
@@ -326,6 +328,7 @@ export class LGraphNode implements NodeLike, Positionable, IPinnable, IColorable
326328 lostFocusAt ?: number
327329 gotFocusAt ?: number
328330 badges : ( LGraphBadge | ( ( ) => LGraphBadge ) ) [ ] = [ ]
331+ title_buttons : LGraphButton [ ] = [ ]
329332 badgePosition : BadgePosition = BadgePosition . TopLeft
330333 onOutputRemoved ?( this : LGraphNode , slot : number ) : void
331334 onInputRemoved ?( this : LGraphNode , slot : number , input : INodeInputSlot ) : void
@@ -687,6 +690,26 @@ export class LGraphNode implements NodeLike, Positionable, IPinnable, IColorable
687690 error : this . #getErrorStrokeStyle,
688691 selected : this . #getSelectedStrokeStyle,
689692 }
693+
694+ // Assign onMouseDown implementation
695+ this . onMouseDown = ( e : CanvasPointerEvent , pos : Point , canvas : LGraphCanvas ) : boolean => {
696+ // Check for title button clicks (only if not collapsed)
697+ if ( this . title_buttons ?. length && ! this . flags . collapsed ) {
698+ // pos contains the offset from the node's position, so we need to use node-relative coordinates
699+ const nodeRelativeX = pos [ 0 ]
700+ const nodeRelativeY = pos [ 1 ]
701+
702+ for ( let i = 0 ; i < this . title_buttons . length ; i ++ ) {
703+ const button = this . title_buttons [ i ]
704+ if ( button . visible && button . isPointInside ( nodeRelativeX , nodeRelativeY ) ) {
705+ this . onTitleButtonClick ( button , canvas )
706+ return true // Prevent default behavior
707+ }
708+ }
709+ }
710+
711+ return false // Allow default behavior
712+ }
690713 }
691714
692715 /** Internal callback for subgraph nodes. Do not implement externally. */
@@ -1794,6 +1817,21 @@ export class LGraphNode implements NodeLike, Positionable, IPinnable, IColorable
17941817 return widget
17951818 }
17961819
1820+ addTitleButton ( options : LGraphButtonOptions ) : LGraphButton {
1821+ this . title_buttons ||= [ ]
1822+ const button = new LGraphButton ( options )
1823+ this . title_buttons . push ( button )
1824+ return button
1825+ }
1826+
1827+ onTitleButtonClick ( button : LGraphButton , canvas : LGraphCanvas ) : void {
1828+ // Dispatch event for button click
1829+ canvas . dispatch ( "litegraph:node-title-button-clicked" , {
1830+ node : this ,
1831+ button : button ,
1832+ } )
1833+ }
1834+
17971835 removeWidgetByName ( name : string ) : void {
17981836 const widget = this . widgets ?. find ( x => x . name === name )
17991837 if ( widget ) this . removeWidget ( widget )
@@ -3372,23 +3410,43 @@ export class LGraphNode implements NodeLike, Positionable, IPinnable, IColorable
33723410 } else {
33733411 ctx . fillStyle = this . constructor . title_text_color || default_title_color
33743412 }
3413+
3414+ // Calculate available width for title
3415+ let availableWidth = size [ 0 ] - title_height * 2 // Basic margins
3416+
3417+ // Subtract space for title buttons
3418+ if ( this . title_buttons ?. length > 0 ) {
3419+ let buttonsWidth = 0
3420+ const savedFont = ctx . font // Save current font
3421+ for ( const button of this . title_buttons ) {
3422+ if ( button . visible ) {
3423+ buttonsWidth += button . getWidth ( ctx ) + 2 // button width + gap
3424+ }
3425+ }
3426+ ctx . font = savedFont // Restore font after button measurements
3427+ if ( buttonsWidth > 0 ) {
3428+ buttonsWidth += 10 // Extra margin before buttons
3429+ availableWidth -= buttonsWidth
3430+ }
3431+ }
3432+
3433+ // Truncate title if needed
3434+ let displayTitle = title
3435+
33753436 if ( this . collapsed ) {
3376- ctx . textAlign = "left"
3377- ctx . fillText (
3378- // avoid urls too long
3379- title . substr ( 0 , 20 ) ,
3380- title_height ,
3381- LiteGraph . NODE_TITLE_TEXT_Y - title_height ,
3382- )
3383- ctx . textAlign = "left"
3384- } else {
3385- ctx . textAlign = "left"
3386- ctx . fillText (
3387- title ,
3388- title_height ,
3389- LiteGraph . NODE_TITLE_TEXT_Y - title_height ,
3390- )
3437+ // For collapsed nodes, limit to 20 chars as before
3438+ displayTitle = title . substr ( 0 , 20 )
3439+ } else if ( availableWidth > 0 ) {
3440+ // For regular nodes, truncate based on available width
3441+ displayTitle = truncateText ( ctx , title , availableWidth )
33913442 }
3443+
3444+ ctx . textAlign = "left"
3445+ ctx . fillText (
3446+ displayTitle ,
3447+ title_height ,
3448+ LiteGraph . NODE_TITLE_TEXT_Y - title_height ,
3449+ )
33923450 }
33933451 }
33943452
0 commit comments