@@ -10294,6 +10294,119 @@ LGraphNode.prototype.executeAction = function(action)
1029410294 canvas . graph . add ( group ) ;
1029510295 } ;
1029610296
10297+ /**
10298+ * Determines the furthest nodes in each direction
10299+ * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted
10300+ * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode} }
10301+ */
10302+ LGraphCanvas . getBoundaryNodes = function ( nodes ) {
10303+ let top = null ;
10304+ let right = null ;
10305+ let bottom = null ;
10306+ let left = null ;
10307+ for ( const nID in nodes ) {
10308+ const node = nodes [ nID ] ;
10309+ const [ x , y ] = node . pos ;
10310+ const [ width , height ] = node . size ;
10311+
10312+ if ( top === null || y < top . pos [ 1 ] ) {
10313+ top = node ;
10314+ }
10315+ if ( right === null || x + width > right . pos [ 0 ] + right . size [ 0 ] ) {
10316+ right = node ;
10317+ }
10318+ if ( bottom === null || y + height > bottom . pos [ 1 ] + bottom . size [ 1 ] ) {
10319+ bottom = node ;
10320+ }
10321+ if ( left === null || x < left . pos [ 0 ] ) {
10322+ left = node ;
10323+ }
10324+ }
10325+
10326+ return {
10327+ "top" : top ,
10328+ "right" : right ,
10329+ "bottom" : bottom ,
10330+ "left" : left
10331+ } ;
10332+ }
10333+ /**
10334+ * Determines the furthest nodes in each direction for the currently selected nodes
10335+ * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode} }
10336+ */
10337+ LGraphCanvas . prototype . boundaryNodesForSelection = function ( ) {
10338+ return LGraphCanvas . getBoundaryNodes ( Object . values ( this . selected_nodes ) ) ;
10339+ }
10340+
10341+ /**
10342+ *
10343+ * @param {LGraphNode[] } nodes a list of nodes
10344+ * @param {"top"|"bottom"|"left"|"right" } direction Direction to align the nodes
10345+ * @param {LGraphNode? } align_to Node to align to (if null, align to the furthest node in the given direction)
10346+ */
10347+ LGraphCanvas . alignNodes = function ( nodes , direction , align_to ) {
10348+ if ( ! nodes ) {
10349+ return ;
10350+ }
10351+
10352+ const canvas = LGraphCanvas . active_canvas ;
10353+ let boundaryNodes = [ ]
10354+ if ( align_to === undefined ) {
10355+ boundaryNodes = LGraphCanvas . getBoundaryNodes ( nodes )
10356+ } else {
10357+ boundaryNodes = {
10358+ "top" : align_to ,
10359+ "right" : align_to ,
10360+ "bottom" : align_to ,
10361+ "left" : align_to
10362+ }
10363+ }
10364+
10365+ for ( const [ _ , node ] of Object . entries ( canvas . selected_nodes ) ) {
10366+ switch ( direction ) {
10367+ case "right" :
10368+ node . pos [ 0 ] = boundaryNodes [ "right" ] . pos [ 0 ] + boundaryNodes [ "right" ] . size [ 0 ] - node . size [ 0 ] ;
10369+ break ;
10370+ case "left" :
10371+ node . pos [ 0 ] = boundaryNodes [ "left" ] . pos [ 0 ] ;
10372+ break ;
10373+ case "top" :
10374+ node . pos [ 1 ] = boundaryNodes [ "top" ] . pos [ 1 ] ;
10375+ break ;
10376+ case "bottom" :
10377+ node . pos [ 1 ] = boundaryNodes [ "bottom" ] . pos [ 1 ] + boundaryNodes [ "bottom" ] . size [ 1 ] - node . size [ 1 ] ;
10378+ break ;
10379+ }
10380+ }
10381+
10382+ canvas . dirty_canvas = true ;
10383+ canvas . dirty_bgcanvas = true ;
10384+ } ;
10385+
10386+ LGraphCanvas . onNodeAlign = function ( value , options , event , prev_menu , node ) {
10387+ new LiteGraph . ContextMenu ( [ "Top" , "Bottom" , "Left" , "Right" ] , {
10388+ event : event ,
10389+ callback : inner_clicked ,
10390+ parentMenu : prev_menu ,
10391+ } ) ;
10392+
10393+ function inner_clicked ( value ) {
10394+ LGraphCanvas . alignNodes ( LGraphCanvas . active_canvas . selected_nodes , value . toLowerCase ( ) , node ) ;
10395+ }
10396+ }
10397+
10398+ LGraphCanvas . onGroupAlign = function ( value , options , event , prev_menu ) {
10399+ new LiteGraph . ContextMenu ( [ "Top" , "Bottom" , "Left" , "Right" ] , {
10400+ event : event ,
10401+ callback : inner_clicked ,
10402+ parentMenu : prev_menu ,
10403+ } ) ;
10404+
10405+ function inner_clicked ( value ) {
10406+ LGraphCanvas . alignNodes ( LGraphCanvas . active_canvas . selected_nodes , value . toLowerCase ( ) ) ;
10407+ }
10408+ }
10409+
1029710410 LGraphCanvas . onMenuAdd = function ( node , options , e , prev_menu , callback ) {
1029810411
1029910412 var canvas = LGraphCanvas . active_canvas ;
@@ -12894,6 +13007,14 @@ LGraphNode.prototype.executeAction = function(action)
1289413007 options.push({ content: "Options", callback: that.showShowGraphOptionsPanel });
1289513008 }*/
1289613009
13010+ if ( Object . keys ( this . selected_nodes ) . length > 1 ) {
13011+ options . push ( {
13012+ content : "Align" ,
13013+ has_submenu : true ,
13014+ callback : LGraphCanvas . onGroupAlign ,
13015+ } )
13016+ }
13017+
1289713018 if ( this . _graph_stack && this . _graph_stack . length > 0 ) {
1289813019 options . push ( null , {
1289913020 content : "Close subgraph" ,
@@ -13008,6 +13129,14 @@ LGraphNode.prototype.executeAction = function(action)
1300813129 callback : LGraphCanvas . onMenuNodeToSubgraph
1300913130 } ) ;
1301013131
13132+ if ( Object . keys ( this . selected_nodes ) . length > 1 ) {
13133+ options . push ( {
13134+ content : "Align Selected To" ,
13135+ has_submenu : true ,
13136+ callback : LGraphCanvas . onNodeAlign ,
13137+ } )
13138+ }
13139+
1301113140 options . push ( null , {
1301213141 content : "Remove" ,
1301313142 disabled : ! ( node . removable !== false && ! node . block_delete ) ,
0 commit comments