2023-01-03 06:53:32 +00:00
//packer version
( function ( global ) {
// *************************************************************
// LiteGraph CLASS *******
// *************************************************************
/ * *
* The Global Scope . It contains all the registered node classes .
*
* @ class LiteGraph
* @ constructor
* /
var LiteGraph = ( global . LiteGraph = {
VERSION : 0.4 ,
CANVAS _GRID _SIZE : 10 ,
NODE _TITLE _HEIGHT : 30 ,
NODE _TITLE _TEXT _Y : 20 ,
NODE _SLOT _HEIGHT : 20 ,
NODE _WIDGET _HEIGHT : 20 ,
NODE _WIDTH : 140 ,
NODE _MIN _WIDTH : 50 ,
NODE _COLLAPSED _RADIUS : 10 ,
NODE _COLLAPSED _WIDTH : 80 ,
NODE _TITLE _COLOR : "#999" ,
NODE _SELECTED _TITLE _COLOR : "#FFF" ,
NODE _TEXT _SIZE : 14 ,
NODE _TEXT _COLOR : "#AAA" ,
NODE _SUBTEXT _SIZE : 12 ,
NODE _DEFAULT _COLOR : "#333" ,
NODE _DEFAULT _BGCOLOR : "#353535" ,
NODE _DEFAULT _BOXCOLOR : "#666" ,
NODE _DEFAULT _SHAPE : "box" ,
NODE _BOX _OUTLINE _COLOR : "#FFF" ,
DEFAULT _SHADOW _COLOR : "rgba(0,0,0,0.5)" ,
DEFAULT _GROUP _FONT : 24 ,
WIDGET _BGCOLOR : "#222" ,
WIDGET _OUTLINE _COLOR : "#666" ,
WIDGET _TEXT _COLOR : "#DDD" ,
WIDGET _SECONDARY _TEXT _COLOR : "#999" ,
LINK _COLOR : "#9A9" ,
EVENT _LINK _COLOR : "#A86" ,
CONNECTING _LINK _COLOR : "#AFA" ,
MAX _NUMBER _OF _NODES : 1000 , //avoid infinite loops
DEFAULT _POSITION : [ 100 , 100 ] , //default node position
VALID _SHAPES : [ "default" , "box" , "round" , "card" ] , //,"circle"
//shapes are used for nodes but also for slots
BOX _SHAPE : 1 ,
ROUND _SHAPE : 2 ,
CIRCLE _SHAPE : 3 ,
CARD _SHAPE : 4 ,
ARROW _SHAPE : 5 ,
GRID _SHAPE : 6 , // intended for slot arrays
//enums
INPUT : 1 ,
OUTPUT : 2 ,
EVENT : - 1 , //for outputs
ACTION : - 1 , //for inputs
NODE _MODES : [ "Always" , "On Event" , "Never" , "On Trigger" ] , // helper, will add "On Request" and more in the future
NODE _MODES _COLORS : [ "#666" , "#422" , "#333" , "#224" , "#626" ] , // use with node_box_coloured_by_mode
ALWAYS : 0 ,
ON _EVENT : 1 ,
NEVER : 2 ,
ON _TRIGGER : 3 ,
UP : 1 ,
DOWN : 2 ,
LEFT : 3 ,
RIGHT : 4 ,
CENTER : 5 ,
LINK _RENDER _MODES : [ "Straight" , "Linear" , "Spline" ] , // helper
STRAIGHT _LINK : 0 ,
LINEAR _LINK : 1 ,
SPLINE _LINK : 2 ,
NORMAL _TITLE : 0 ,
NO _TITLE : 1 ,
TRANSPARENT _TITLE : 2 ,
AUTOHIDE _TITLE : 3 ,
2023-04-07 19:11:00 +00:00
VERTICAL _LAYOUT : "vertical" , // arrange nodes vertically
2023-01-03 06:53:32 +00:00
proxy : null , //used to redirect calls
node _images _path : "" ,
debug : false ,
catch _exceptions : true ,
throw _errors : true ,
allow _scripts : false , //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits
registered _node _types : { } , //nodetypes by string
node _types _by _file _extension : { } , //used for dropping files in the canvas
Nodes : { } , //node types by classname
Globals : { } , //used to store vars between graphs
searchbox _extras : { } , //used to add extra features to the search box
auto _sort _node _types : false , // [true!] If set to true, will automatically sort node types / categories in the context menus
node _box _coloured _when _on : false , // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback
node _box _coloured _by _mode : false , // [true!] nodebox based on node mode, visual feedback
2023-03-24 18:18:58 +00:00
dialog _close _on _mouse _leave : false , // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false
2023-01-03 06:53:32 +00:00
dialog _close _on _mouse _leave _delay : 500 ,
shift _click _do _break _link _from : false , // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys
click _do _break _link _to : false , // [false!]prefer false, way too easy to break links
search _hide _on _mouse _leave : true , // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false
search _filter _enabled : false , // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out]
search _show _all _on _open : true , // [true!] opens the results list when opening the search widget
auto _load _slot _types : false , // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out]
// set these values if not using auto_load_slot_types
registered _slot _in _types : { } , // slot types for nodeclass
registered _slot _out _types : { } , // slot types for nodeclass
slot _types _in : [ ] , // slot types IN
slot _types _out : [ ] , // slot types OUT
2023-04-07 19:11:00 +00:00
slot _types _default _in : [ ] , // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search
slot _types _default _out : [ ] , // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search
2023-01-03 06:53:32 +00:00
alt _drag _do _clone _nodes : false , // [true!] very handy, ALT click to clone and drag the new node
do _add _triggers _slots : false , // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this
2023-04-07 19:11:00 +00:00
allow _multi _output _for _events : true , // [false!] being events, it is strongly reccomended to use them sequentially, one by one
2023-01-03 06:53:32 +00:00
middle _click _slot _add _default _node : false , //[true!] allows to create and connect a ndoe clicking with the third button (wheel)
release _link _on _empty _shows _menu : false , //[true!] dragging a link to empty space will open a menu, add from list, search or defaults
2023-03-16 16:05:26 +00:00
pointerevents _method : "pointer" , // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now)
2023-01-03 06:53:32 +00:00
// TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary)
/ * *
* Register a node class so it can be listed when the user wants to create a new one
* @ method registerNodeType
* @ param { String } type name of the node and path
* @ param { Class } base _class class containing the structure of a node
* /
registerNodeType : function ( type , base _class ) {
if ( ! base _class . prototype ) {
throw "Cannot register a simple object, it must be a class with a prototype" ;
}
base _class . type = type ;
if ( LiteGraph . debug ) {
console . log ( "Node registered: " + type ) ;
}
2023-04-07 19:11:00 +00:00
const classname = base _class . name ;
2023-01-03 06:53:32 +00:00
2023-04-07 19:11:00 +00:00
const pos = type . lastIndexOf ( "/" ) ;
base _class . category = type . substring ( 0 , pos ) ;
2023-01-03 06:53:32 +00:00
if ( ! base _class . title ) {
base _class . title = classname ;
}
//extend class
2023-04-07 19:11:00 +00:00
for ( var i in LGraphNode . prototype ) {
if ( ! base _class . prototype [ i ] ) {
base _class . prototype [ i ] = LGraphNode . prototype [ i ] ;
2023-01-03 06:53:32 +00:00
}
}
2023-04-07 19:11:00 +00:00
const prev = this . registered _node _types [ type ] ;
if ( prev ) {
console . log ( "replacing node type: " + type ) ;
}
if ( ! Object . prototype . hasOwnProperty . call ( base _class . prototype , "shape" ) ) {
Object . defineProperty ( base _class . prototype , "shape" , {
set : function ( v ) {
switch ( v ) {
case "default" :
delete this . _shape ;
break ;
case "box" :
this . _shape = LiteGraph . BOX _SHAPE ;
break ;
case "round" :
this . _shape = LiteGraph . ROUND _SHAPE ;
break ;
case "circle" :
this . _shape = LiteGraph . CIRCLE _SHAPE ;
break ;
case "card" :
this . _shape = LiteGraph . CARD _SHAPE ;
break ;
default :
this . _shape = v ;
}
} ,
get : function ( ) {
return this . _shape ;
} ,
enumerable : true ,
configurable : true
} ) ;
2023-01-03 06:53:32 +00:00
2023-04-07 19:11:00 +00:00
//used to know which nodes to create when dragging files to the canvas
if ( base _class . supported _extensions ) {
for ( let i in base _class . supported _extensions ) {
const ext = base _class . supported _extensions [ i ] ;
if ( ext && ext . constructor === String ) {
this . node _types _by _file _extension [ ext . toLowerCase ( ) ] = base _class ;
}
}
}
}
2023-01-03 06:53:32 +00:00
this . registered _node _types [ type ] = base _class ;
if ( base _class . constructor . name ) {
this . Nodes [ classname ] = base _class ;
}
if ( LiteGraph . onNodeTypeRegistered ) {
LiteGraph . onNodeTypeRegistered ( type , base _class ) ;
}
if ( prev && LiteGraph . onNodeTypeReplaced ) {
LiteGraph . onNodeTypeReplaced ( type , base _class , prev ) ;
}
//warnings
if ( base _class . prototype . onPropertyChange ) {
console . warn (
"LiteGraph node class " +
type +
" has onPropertyChange method, it must be called onPropertyChanged with d at the end"
) ;
}
2023-04-07 19:11:00 +00:00
// TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types
if ( this . auto _load _slot _types ) {
new base _class ( base _class . title || "tmpnode" ) ;
}
2023-01-03 06:53:32 +00:00
} ,
/ * *
* removes a node type from the system
* @ method unregisterNodeType
* @ param { String | Object } type name of the node or the node constructor itself
* /
unregisterNodeType : function ( type ) {
var base _class = type . constructor === String ? this . registered _node _types [ type ] : type ;
if ( ! base _class )
throw ( "node type not found: " + type ) ;
delete this . registered _node _types [ base _class . type ] ;
if ( base _class . constructor . name )
delete this . Nodes [ base _class . constructor . name ] ;
} ,
/ * *
* Save a slot type and his node
* @ method registerSlotType
* @ param { String | Object } type name of the node or the node constructor itself
* @ param { String } slot _type name of the slot type ( variable type ) , eg . string , number , array , boolean , . .
* /
registerNodeAndSlotType : function ( type , slot _type , out ) {
out = out || false ;
var base _class = type . constructor === String && this . registered _node _types [ type ] !== "anonymous" ? this . registered _node _types [ type ] : type ;
var sCN = base _class . constructor . type ;
if ( typeof slot _type == "string" ) {
var aTypes = slot _type . split ( "," ) ;
} else if ( slot _type == this . EVENT || slot _type == this . ACTION ) {
var aTypes = [ "_event_" ] ;
} else {
var aTypes = [ "*" ] ;
}
for ( var i = 0 ; i < aTypes . length ; ++ i ) {
var sT = aTypes [ i ] ; //.toLowerCase();
if ( sT === "" ) {
sT = "*" ;
}
var registerTo = out ? "registered_slot_out_types" : "registered_slot_in_types" ;
if ( typeof this [ registerTo ] [ sT ] == "undefined" ) this [ registerTo ] [ sT ] = { nodes : [ ] } ;
this [ registerTo ] [ sT ] . nodes . push ( sCN ) ;
// check if is a new type
if ( ! out ) {
if ( ! this . slot _types _in . includes ( sT . toLowerCase ( ) ) ) {
this . slot _types _in . push ( sT . toLowerCase ( ) ) ;
this . slot _types _in . sort ( ) ;
}
} else {
if ( ! this . slot _types _out . includes ( sT . toLowerCase ( ) ) ) {
this . slot _types _out . push ( sT . toLowerCase ( ) ) ;
this . slot _types _out . sort ( ) ;
}
}
}
} ,
/ * *
* Create a new nodetype by passing a function , it wraps it with a proper class and generates inputs according to the parameters of the function .
* Useful to wrap simple methods that do not require properties , and that only process some input to generate an output .
* @ method wrapFunctionAsNode
* @ param { String } name node name with namespace ( p . e . : 'math/sum' )
* @ param { Function } func
* @ param { Array } param _types [ optional ] an array containing the type of every parameter , otherwise parameters will accept any type
* @ param { String } return _type [ optional ] string with the return type , otherwise it will be generic
* @ param { Object } properties [ optional ] properties to be configurable
* /
wrapFunctionAsNode : function (
name ,
func ,
param _types ,
return _type ,
properties
) {
var params = Array ( func . length ) ;
var code = "" ;
var names = LiteGraph . getParameterNames ( func ) ;
for ( var i = 0 ; i < names . length ; ++ i ) {
code +=
"this.addInput('" +
names [ i ] +
"'," +
( param _types && param _types [ i ]
? "'" + param _types [ i ] + "'"
: "0" ) +
");\n" ;
}
code +=
"this.addOutput('out'," +
( return _type ? "'" + return _type + "'" : 0 ) +
");\n" ;
if ( properties ) {
code +=
"this.properties = " + JSON . stringify ( properties ) + ";\n" ;
}
var classobj = Function ( code ) ;
classobj . title = name . split ( "/" ) . pop ( ) ;
classobj . desc = "Generated from " + func . name ;
classobj . prototype . onExecute = function onExecute ( ) {
for ( var i = 0 ; i < params . length ; ++ i ) {
params [ i ] = this . getInputData ( i ) ;
}
var r = func . apply ( this , params ) ;
this . setOutputData ( 0 , r ) ;
} ;
this . registerNodeType ( name , classobj ) ;
} ,
/ * *
* Removes all previously registered node ' s types
* /
clearRegisteredTypes : function ( ) {
this . registered _node _types = { } ;
this . node _types _by _file _extension = { } ;
this . Nodes = { } ;
this . searchbox _extras = { } ;
} ,
/ * *
* Adds this method to all nodetypes , existing and to be created
* ( You can add it to LGraphNode . prototype but then existing node types wont have it )
* @ method addNodeMethod
* @ param { Function } func
* /
addNodeMethod : function ( name , func ) {
LGraphNode . prototype [ name ] = func ;
for ( var i in this . registered _node _types ) {
var type = this . registered _node _types [ i ] ;
if ( type . prototype [ name ] ) {
type . prototype [ "_" + name ] = type . prototype [ name ] ;
} //keep old in case of replacing
type . prototype [ name ] = func ;
}
} ,
/ * *
* Create a node of a given type with a name . The node is not attached to any graph yet .
* @ method createNode
* @ param { String } type full name of the node class . p . e . "math/sin"
* @ param { String } name a name to distinguish from other nodes
* @ param { Object } options to set options
* /
createNode : function ( type , title , options ) {
var base _class = this . registered _node _types [ type ] ;
if ( ! base _class ) {
if ( LiteGraph . debug ) {
console . log (
'GraphNode type "' + type + '" not registered.'
) ;
}
return null ;
}
var prototype = base _class . prototype || base _class ;
title = title || base _class . title || type ;
var node = null ;
if ( LiteGraph . catch _exceptions ) {
try {
node = new base _class ( title ) ;
} catch ( err ) {
console . error ( err ) ;
return null ;
}
} else {
node = new base _class ( title ) ;
}
node . type = type ;
if ( ! node . title && title ) {
node . title = title ;
}
if ( ! node . properties ) {
node . properties = { } ;
}
if ( ! node . properties _info ) {
node . properties _info = [ ] ;
}
if ( ! node . flags ) {
node . flags = { } ;
}
if ( ! node . size ) {
node . size = node . computeSize ( ) ;
//call onresize?
}
if ( ! node . pos ) {
node . pos = LiteGraph . DEFAULT _POSITION . concat ( ) ;
}
if ( ! node . mode ) {
node . mode = LiteGraph . ALWAYS ;
}
//extra options
if ( options ) {
for ( var i in options ) {
node [ i ] = options [ i ] ;
}
}
// callback
if ( node . onNodeCreated ) {
node . onNodeCreated ( ) ;
}
return node ;
} ,
/ * *
* Returns a registered node type with a given name
* @ method getNodeType
* @ param { String } type full name of the node class . p . e . "math/sin"
* @ return { Class } the node class
* /
getNodeType : function ( type ) {
return this . registered _node _types [ type ] ;
} ,
/ * *
* Returns a list of node types matching one category
* @ method getNodeType
* @ param { String } category category name
* @ return { Array } array with all the node classes
* /
getNodeTypesInCategory : function ( category , filter ) {
var r = [ ] ;
for ( var i in this . registered _node _types ) {
var type = this . registered _node _types [ i ] ;
if ( type . filter != filter ) {
continue ;
}
if ( category == "" ) {
if ( type . category == null ) {
r . push ( type ) ;
}
} else if ( type . category == category ) {
r . push ( type ) ;
}
}
if ( this . auto _sort _node _types ) {
r . sort ( function ( a , b ) { return a . title . localeCompare ( b . title ) } ) ;
}
return r ;
} ,
/ * *
* Returns a list with all the node type categories
* @ method getNodeTypesCategories
* @ param { String } filter only nodes with ctor . filter equal can be shown
* @ return { Array } array with all the names of the categories
* /
getNodeTypesCategories : function ( filter ) {
var categories = { "" : 1 } ;
for ( var i in this . registered _node _types ) {
var type = this . registered _node _types [ i ] ;
if ( type . category && ! type . skip _list )
{
if ( type . filter != filter )
continue ;
categories [ type . category ] = 1 ;
}
}
var result = [ ] ;
for ( var i in categories ) {
result . push ( i ) ;
}
return this . auto _sort _node _types ? result . sort ( ) : result ;
} ,
//debug purposes: reloads all the js scripts that matches a wildcard
reloadNodes : function ( folder _wildcard ) {
var tmp = document . getElementsByTagName ( "script" ) ;
//weird, this array changes by its own, so we use a copy
var script _files = [ ] ;
for ( var i = 0 ; i < tmp . length ; i ++ ) {
script _files . push ( tmp [ i ] ) ;
}
var docHeadObj = document . getElementsByTagName ( "head" ) [ 0 ] ;
folder _wildcard = document . location . href + folder _wildcard ;
for ( var i = 0 ; i < script _files . length ; i ++ ) {
var src = script _files [ i ] . src ;
if (
! src ||
src . substr ( 0 , folder _wildcard . length ) != folder _wildcard
) {
continue ;
}
try {
if ( LiteGraph . debug ) {
console . log ( "Reloading: " + src ) ;
}
var dynamicScript = document . createElement ( "script" ) ;
dynamicScript . type = "text/javascript" ;
dynamicScript . src = src ;
docHeadObj . appendChild ( dynamicScript ) ;
docHeadObj . removeChild ( script _files [ i ] ) ;
} catch ( err ) {
if ( LiteGraph . throw _errors ) {
throw err ;
}
if ( LiteGraph . debug ) {
console . log ( "Error while reloading " + src ) ;
}
}
}
if ( LiteGraph . debug ) {
console . log ( "Nodes reloaded" ) ;
}
} ,
//separated just to improve if it doesn't work
cloneObject : function ( obj , target ) {
if ( obj == null ) {
return null ;
}
var r = JSON . parse ( JSON . stringify ( obj ) ) ;
if ( ! target ) {
return r ;
}
for ( var i in r ) {
target [ i ] = r [ i ] ;
}
return target ;
} ,
/ * *
* Returns if the types of two slots are compatible ( taking into account wildcards , etc )
* @ method isValidConnection
* @ param { String } type _a
* @ param { String } type _b
* @ return { Boolean } true if they can be connected
* /
isValidConnection : function ( type _a , type _b ) {
if ( type _a == "" || type _a === "*" ) type _a = 0 ;
if ( type _b == "" || type _b === "*" ) type _b = 0 ;
if (
! type _a //generic output
|| ! type _b // generic input
|| type _a == type _b //same type (is valid for triggers)
|| ( type _a == LiteGraph . EVENT && type _b == LiteGraph . ACTION )
) {
return true ;
}
// Enforce string type to handle toLowerCase call (-1 number not ok)
type _a = String ( type _a ) ;
type _b = String ( type _b ) ;
type _a = type _a . toLowerCase ( ) ;
type _b = type _b . toLowerCase ( ) ;
// For nodes supporting multiple connection types
if ( type _a . indexOf ( "," ) == - 1 && type _b . indexOf ( "," ) == - 1 ) {
return type _a == type _b ;
}
// Check all permutations to see if one is valid
var supported _types _a = type _a . split ( "," ) ;
var supported _types _b = type _b . split ( "," ) ;
for ( var i = 0 ; i < supported _types _a . length ; ++ i ) {
for ( var j = 0 ; j < supported _types _b . length ; ++ j ) {
if ( this . isValidConnection ( supported _types _a [ i ] , supported _types _b [ j ] ) ) {
//if (supported_types_a[i] == supported_types_b[j]) {
return true ;
}
}
}
return false ;
} ,
/ * *
* Register a string in the search box so when the user types it it will recommend this node
* @ method registerSearchboxExtra
* @ param { String } node _type the node recommended
* @ param { String } description text to show next to it
* @ param { Object } data it could contain info of how the node should be configured
* @ return { Boolean } true if they can be connected
* /
registerSearchboxExtra : function ( node _type , description , data ) {
this . searchbox _extras [ description . toLowerCase ( ) ] = {
type : node _type ,
desc : description ,
data : data
} ;
} ,
/ * *
* Wrapper to load files ( from url using fetch or from file using FileReader )
* @ method fetchFile
* @ param { String | File | Blob } url the url of the file ( or the file itself )
* @ param { String } type an string to know how to fetch it : "text" , "arraybuffer" , "json" , "blob"
* @ param { Function } on _complete callback ( data )
* @ param { Function } on _error in case of an error
* @ return { FileReader | Promise } returns the object used to
* /
fetchFile : function ( url , type , on _complete , on _error ) {
var that = this ;
if ( ! url )
return null ;
type = type || "text" ;
if ( url . constructor === String )
{
if ( url . substr ( 0 , 4 ) == "http" && LiteGraph . proxy ) {
url = LiteGraph . proxy + url . substr ( url . indexOf ( ":" ) + 3 ) ;
}
return fetch ( url )
. then ( function ( response ) {
if ( ! response . ok )
throw new Error ( "File not found" ) ; //it will be catch below
if ( type == "arraybuffer" )
return response . arrayBuffer ( ) ;
else if ( type == "text" || type == "string" )
return response . text ( ) ;
else if ( type == "json" )
return response . json ( ) ;
else if ( type == "blob" )
return response . blob ( ) ;
} )
. then ( function ( data ) {
if ( on _complete )
on _complete ( data ) ;
} )
. catch ( function ( error ) {
console . error ( "error fetching file:" , url ) ;
if ( on _error )
on _error ( error ) ;
} ) ;
}
else if ( url . constructor === File || url . constructor === Blob )
{
var reader = new FileReader ( ) ;
reader . onload = function ( e )
{
var v = e . target . result ;
if ( type == "json" )
v = JSON . parse ( v ) ;
if ( on _complete )
on _complete ( v ) ;
}
if ( type == "arraybuffer" )
return reader . readAsArrayBuffer ( url ) ;
else if ( type == "text" || type == "json" )
return reader . readAsText ( url ) ;
else if ( type == "blob" )
return reader . readAsBinaryString ( url ) ;
}
return null ;
}
} ) ;
//timer that works everywhere
if ( typeof performance != "undefined" ) {
LiteGraph . getTime = performance . now . bind ( performance ) ;
} else if ( typeof Date != "undefined" && Date . now ) {
LiteGraph . getTime = Date . now . bind ( Date ) ;
} else if ( typeof process != "undefined" ) {
LiteGraph . getTime = function ( ) {
var t = process . hrtime ( ) ;
return t [ 0 ] * 0.001 + t [ 1 ] * 1e-6 ;
} ;
} else {
LiteGraph . getTime = function getTime ( ) {
return new Date ( ) . getTime ( ) ;
} ;
}
//*********************************************************************************
// LGraph CLASS
//*********************************************************************************
/ * *
* LGraph is the class that contain a full graph . We instantiate one and add nodes to it , and then we can run the execution loop .
* supported callbacks :
+ onNodeAdded : when a new node is added to the graph
+ onNodeRemoved : when a node inside this graph is removed
+ onNodeConnectionChange : some connection has changed in the graph ( connected or disconnected )
*
* @ class LGraph
* @ constructor
* @ param { Object } o data from previous serialization [ optional ]
* /
function LGraph ( o ) {
if ( LiteGraph . debug ) {
console . log ( "Graph created" ) ;
}
this . list _of _graphcanvas = null ;
this . clear ( ) ;
if ( o ) {
this . configure ( o ) ;
}
}
global . LGraph = LiteGraph . LGraph = LGraph ;
//default supported types
LGraph . supported _types = [ "number" , "string" , "boolean" ] ;
//used to know which types of connections support this graph (some graphs do not allow certain types)
LGraph . prototype . getSupportedTypes = function ( ) {
return this . supported _types || LGraph . supported _types ;
} ;
LGraph . STATUS _STOPPED = 1 ;
LGraph . STATUS _RUNNING = 2 ;
/ * *
* Removes all nodes from this graph
* @ method clear
* /
LGraph . prototype . clear = function ( ) {
this . stop ( ) ;
this . status = LGraph . STATUS _STOPPED ;
this . last _node _id = 0 ;
this . last _link _id = 0 ;
this . _version = - 1 ; //used to detect changes
//safe clear
if ( this . _nodes ) {
for ( var i = 0 ; i < this . _nodes . length ; ++ i ) {
var node = this . _nodes [ i ] ;
if ( node . onRemoved ) {
node . onRemoved ( ) ;
}
}
}
//nodes
this . _nodes = [ ] ;
this . _nodes _by _id = { } ;
this . _nodes _in _order = [ ] ; //nodes sorted in execution order
this . _nodes _executable = null ; //nodes that contain onExecute sorted in execution order
//other scene stuff
this . _groups = [ ] ;
//links
this . links = { } ; //container with all the links
//iterations
this . iteration = 0 ;
//custom data
this . config = { } ;
this . vars = { } ;
this . extra = { } ; //to store custom data
//timing
this . globaltime = 0 ;
this . runningtime = 0 ;
this . fixedtime = 0 ;
this . fixedtime _lapse = 0.01 ;
this . elapsed _time = 0.01 ;
this . last _update _time = 0 ;
this . starttime = 0 ;
this . catch _errors = true ;
this . nodes _executing = [ ] ;
this . nodes _actioning = [ ] ;
this . nodes _executedAction = [ ] ;
//subgraph_data
this . inputs = { } ;
this . outputs = { } ;
//notify canvas to redraw
this . change ( ) ;
this . sendActionToCanvas ( "clear" ) ;
} ;
/ * *
* Attach Canvas to this graph
* @ method attachCanvas
* @ param { GraphCanvas } graph _canvas
* /
LGraph . prototype . attachCanvas = function ( graphcanvas ) {
if ( graphcanvas . constructor != LGraphCanvas ) {
throw "attachCanvas expects a LGraphCanvas instance" ;
}
if ( graphcanvas . graph && graphcanvas . graph != this ) {
graphcanvas . graph . detachCanvas ( graphcanvas ) ;
}
graphcanvas . graph = this ;
if ( ! this . list _of _graphcanvas ) {
this . list _of _graphcanvas = [ ] ;
}
this . list _of _graphcanvas . push ( graphcanvas ) ;
} ;
/ * *
* Detach Canvas from this graph
* @ method detachCanvas
* @ param { GraphCanvas } graph _canvas
* /
LGraph . prototype . detachCanvas = function ( graphcanvas ) {
if ( ! this . list _of _graphcanvas ) {
return ;
}
var pos = this . list _of _graphcanvas . indexOf ( graphcanvas ) ;
if ( pos == - 1 ) {
return ;
}
graphcanvas . graph = null ;
this . list _of _graphcanvas . splice ( pos , 1 ) ;
} ;
/ * *
* Starts running this graph every interval milliseconds .
* @ method start
* @ param { number } interval amount of milliseconds between executions , if 0 then it renders to the monitor refresh rate
* /
LGraph . prototype . start = function ( interval ) {
if ( this . status == LGraph . STATUS _RUNNING ) {
return ;
}
this . status = LGraph . STATUS _RUNNING ;
if ( this . onPlayEvent ) {
this . onPlayEvent ( ) ;
}
this . sendEventToAllNodes ( "onStart" ) ;
//launch
this . starttime = LiteGraph . getTime ( ) ;
this . last _update _time = this . starttime ;
interval = interval || 0 ;
var that = this ;
//execute once per frame
if ( interval == 0 && typeof window != "undefined" && window . requestAnimationFrame ) {
function on _frame ( ) {
if ( that . execution _timer _id != - 1 ) {
return ;
}
window . requestAnimationFrame ( on _frame ) ;
if ( that . onBeforeStep )
that . onBeforeStep ( ) ;
that . runStep ( 1 , ! that . catch _errors ) ;
if ( that . onAfterStep )
that . onAfterStep ( ) ;
}
this . execution _timer _id = - 1 ;
on _frame ( ) ;
} else { //execute every 'interval' ms
this . execution _timer _id = setInterval ( function ( ) {
//execute
if ( that . onBeforeStep )
that . onBeforeStep ( ) ;
that . runStep ( 1 , ! that . catch _errors ) ;
if ( that . onAfterStep )
that . onAfterStep ( ) ;
} , interval ) ;
}
} ;
/ * *
* Stops the execution loop of the graph
* @ method stop execution
* /
LGraph . prototype . stop = function ( ) {
if ( this . status == LGraph . STATUS _STOPPED ) {
return ;
}
this . status = LGraph . STATUS _STOPPED ;
if ( this . onStopEvent ) {
this . onStopEvent ( ) ;
}
if ( this . execution _timer _id != null ) {
if ( this . execution _timer _id != - 1 ) {
clearInterval ( this . execution _timer _id ) ;
}
this . execution _timer _id = null ;
}
this . sendEventToAllNodes ( "onStop" ) ;
} ;
/ * *
* Run N steps ( cycles ) of the graph
* @ method runStep
* @ param { number } num number of steps to run , default is 1
* @ param { Boolean } do _not _catch _errors [ optional ] if you want to try / c a t c h e r r o r s
* @ param { number } limit max number of nodes to execute ( used to execute from start to a node )
* /
LGraph . prototype . runStep = function ( num , do _not _catch _errors , limit ) {
num = num || 1 ;
var start = LiteGraph . getTime ( ) ;
this . globaltime = 0.001 * ( start - this . starttime ) ;
var nodes = this . _nodes _executable
? this . _nodes _executable
: this . _nodes ;
if ( ! nodes ) {
return ;
}
limit = limit || nodes . length ;
if ( do _not _catch _errors ) {
//iterations
for ( var i = 0 ; i < num ; i ++ ) {
for ( var j = 0 ; j < limit ; ++ j ) {
var node = nodes [ j ] ;
if ( node . mode == LiteGraph . ALWAYS && node . onExecute ) {
//wrap node.onExecute();
node . doExecute ( ) ;
}
}
this . fixedtime += this . fixedtime _lapse ;
if ( this . onExecuteStep ) {
this . onExecuteStep ( ) ;
}
}
if ( this . onAfterExecute ) {
this . onAfterExecute ( ) ;
}
} else {
try {
//iterations
for ( var i = 0 ; i < num ; i ++ ) {
for ( var j = 0 ; j < limit ; ++ j ) {
var node = nodes [ j ] ;
if ( node . mode == LiteGraph . ALWAYS && node . onExecute ) {
node . onExecute ( ) ;
}
}
this . fixedtime += this . fixedtime _lapse ;
if ( this . onExecuteStep ) {
this . onExecuteStep ( ) ;
}
}
if ( this . onAfterExecute ) {
this . onAfterExecute ( ) ;
}
this . errors _in _execution = false ;
} catch ( err ) {
this . errors _in _execution = true ;
if ( LiteGraph . throw _errors ) {
throw err ;
}
if ( LiteGraph . debug ) {
console . log ( "Error during execution: " + err ) ;
}
this . stop ( ) ;
}
}
var now = LiteGraph . getTime ( ) ;
var elapsed = now - start ;
if ( elapsed == 0 ) {
elapsed = 1 ;
}
this . execution _time = 0.001 * elapsed ;
this . globaltime += 0.001 * elapsed ;
this . iteration += 1 ;
this . elapsed _time = ( now - this . last _update _time ) * 0.001 ;
this . last _update _time = now ;
this . nodes _executing = [ ] ;
this . nodes _actioning = [ ] ;
this . nodes _executedAction = [ ] ;
} ;
/ * *
* Updates the graph execution order according to relevance of the nodes ( nodes with only outputs have more relevance than
* nodes with only inputs .
* @ method updateExecutionOrder
* /
LGraph . prototype . updateExecutionOrder = function ( ) {
this . _nodes _in _order = this . computeExecutionOrder ( false ) ;
this . _nodes _executable = [ ] ;
for ( var i = 0 ; i < this . _nodes _in _order . length ; ++ i ) {
if ( this . _nodes _in _order [ i ] . onExecute ) {
this . _nodes _executable . push ( this . _nodes _in _order [ i ] ) ;
}
}
} ;
//This is more internal, it computes the executable nodes in order and returns it
LGraph . prototype . computeExecutionOrder = function (
only _onExecute ,
set _level
) {
var L = [ ] ;
var S = [ ] ;
var M = { } ;
var visited _links = { } ; //to avoid repeating links
var remaining _links = { } ; //to a
//search for the nodes without inputs (starting nodes)
for ( var i = 0 , l = this . _nodes . length ; i < l ; ++ i ) {
var node = this . _nodes [ i ] ;
if ( only _onExecute && ! node . onExecute ) {
continue ;
}
M [ node . id ] = node ; //add to pending nodes
var num = 0 ; //num of input connections
if ( node . inputs ) {
for ( var j = 0 , l2 = node . inputs . length ; j < l2 ; j ++ ) {
if ( node . inputs [ j ] && node . inputs [ j ] . link != null ) {
num += 1 ;
}
}
}
if ( num == 0 ) {
//is a starting node
S . push ( node ) ;
if ( set _level ) {
node . _level = 1 ;
}
} //num of input links
else {
if ( set _level ) {
node . _level = 0 ;
}
remaining _links [ node . id ] = num ;
}
}
while ( true ) {
if ( S . length == 0 ) {
break ;
}
//get an starting node
var node = S . shift ( ) ;
L . push ( node ) ; //add to ordered list
delete M [ node . id ] ; //remove from the pending nodes
if ( ! node . outputs ) {
continue ;
}
//for every output
for ( var i = 0 ; i < node . outputs . length ; i ++ ) {
var output = node . outputs [ i ] ;
//not connected
if (
output == null ||
output . links == null ||
output . links . length == 0
) {
continue ;
}
//for every connection
for ( var j = 0 ; j < output . links . length ; j ++ ) {
var link _id = output . links [ j ] ;
var link = this . links [ link _id ] ;
if ( ! link ) {
continue ;
}
//already visited link (ignore it)
if ( visited _links [ link . id ] ) {
continue ;
}
var target _node = this . getNodeById ( link . target _id ) ;
if ( target _node == null ) {
visited _links [ link . id ] = true ;
continue ;
}
if (
set _level &&
( ! target _node . _level ||
target _node . _level <= node . _level )
) {
target _node . _level = node . _level + 1 ;
}
visited _links [ link . id ] = true ; //mark as visited
remaining _links [ target _node . id ] -= 1 ; //reduce the number of links remaining
if ( remaining _links [ target _node . id ] == 0 ) {
S . push ( target _node ) ;
} //if no more links, then add to starters array
}
}
}
//the remaining ones (loops)
for ( var i in M ) {
L . push ( M [ i ] ) ;
}
if ( L . length != this . _nodes . length && LiteGraph . debug ) {
console . warn ( "something went wrong, nodes missing" ) ;
}
var l = L . length ;
//save order number in the node
for ( var i = 0 ; i < l ; ++ i ) {
L [ i ] . order = i ;
}
//sort now by priority
L = L . sort ( function ( A , B ) {
var Ap = A . constructor . priority || A . priority || 0 ;
var Bp = B . constructor . priority || B . priority || 0 ;
if ( Ap == Bp ) {
//if same priority, sort by order
return A . order - B . order ;
}
return Ap - Bp ; //sort by priority
} ) ;
//save order number in the node, again...
for ( var i = 0 ; i < l ; ++ i ) {
L [ i ] . order = i ;
}
return L ;
} ;
/ * *
* Returns all the nodes that could affect this one ( ancestors ) by crawling all the inputs recursively .
* It doesn ' t include the node itself
* @ method getAncestors
* @ return { Array } an array with all the LGraphNodes that affect this node , in order of execution
* /
LGraph . prototype . getAncestors = function ( node ) {
var ancestors = [ ] ;
var pending = [ node ] ;
var visited = { } ;
while ( pending . length ) {
var current = pending . shift ( ) ;
if ( ! current . inputs ) {
continue ;
}
if ( ! visited [ current . id ] && current != node ) {
visited [ current . id ] = true ;
ancestors . push ( current ) ;
}
for ( var i = 0 ; i < current . inputs . length ; ++ i ) {
var input = current . getInputNode ( i ) ;
if ( input && ancestors . indexOf ( input ) == - 1 ) {
pending . push ( input ) ;
}
}
}
ancestors . sort ( function ( a , b ) {
return a . order - b . order ;
} ) ;
return ancestors ;
} ;
/ * *
* Positions every node in a more readable manner
* @ method arrange
* /
2023-04-07 19:11:00 +00:00
LGraph . prototype . arrange = function ( margin , layout ) {
2023-01-03 06:53:32 +00:00
margin = margin || 100 ;
2023-04-07 19:11:00 +00:00
const nodes = this . computeExecutionOrder ( false , true ) ;
const columns = [ ] ;
for ( let i = 0 ; i < nodes . length ; ++ i ) {
const node = nodes [ i ] ;
const col = node . _level || 1 ;
2023-01-03 06:53:32 +00:00
if ( ! columns [ col ] ) {
columns [ col ] = [ ] ;
}
columns [ col ] . push ( node ) ;
}
2023-04-07 19:11:00 +00:00
let x = margin ;
2023-01-03 06:53:32 +00:00
2023-04-07 19:11:00 +00:00
for ( let i = 0 ; i < columns . length ; ++ i ) {
const column = columns [ i ] ;
2023-01-03 06:53:32 +00:00
if ( ! column ) {
continue ;
}
2023-04-07 19:11:00 +00:00
let max _size = 100 ;
let y = margin + LiteGraph . NODE _TITLE _HEIGHT ;
for ( let j = 0 ; j < column . length ; ++ j ) {
const node = column [ j ] ;
node . pos [ 0 ] = ( layout == LiteGraph . VERTICAL _LAYOUT ) ? y : x ;
node . pos [ 1 ] = ( layout == LiteGraph . VERTICAL _LAYOUT ) ? x : y ;
const max _size _index = ( layout == LiteGraph . VERTICAL _LAYOUT ) ? 1 : 0 ;
if ( node . size [ max _size _index ] > max _size ) {
max _size = node . size [ max _size _index ] ;
2023-01-03 06:53:32 +00:00
}
2023-04-07 19:11:00 +00:00
const node _size _index = ( layout == LiteGraph . VERTICAL _LAYOUT ) ? 0 : 1 ;
y += node . size [ node _size _index ] + margin + LiteGraph . NODE _TITLE _HEIGHT ;
2023-01-03 06:53:32 +00:00
}
x += max _size + margin ;
}
this . setDirtyCanvas ( true , true ) ;
} ;
/ * *
* Returns the amount of time the graph has been running in milliseconds
* @ method getTime
* @ return { number } number of milliseconds the graph has been running
* /
LGraph . prototype . getTime = function ( ) {
return this . globaltime ;
} ;
/ * *
* Returns the amount of time accumulated using the fixedtime _lapse var . This is used in context where the time increments should be constant
* @ method getFixedTime
* @ return { number } number of milliseconds the graph has been running
* /
LGraph . prototype . getFixedTime = function ( ) {
return this . fixedtime ;
} ;
/ * *
* Returns the amount of time it took to compute the latest iteration . Take into account that this number could be not correct
* if the nodes are using graphical actions
* @ method getElapsedTime
* @ return { number } number of milliseconds it took the last cycle
* /
LGraph . prototype . getElapsedTime = function ( ) {
return this . elapsed _time ;
} ;
/ * *
* Sends an event to all the nodes , useful to trigger stuff
* @ method sendEventToAllNodes
* @ param { String } eventname the name of the event ( function to be called )
* @ param { Array } params parameters in array format
* /
LGraph . prototype . sendEventToAllNodes = function ( eventname , params , mode ) {
mode = mode || LiteGraph . ALWAYS ;
var nodes = this . _nodes _in _order ? this . _nodes _in _order : this . _nodes ;
if ( ! nodes ) {
return ;
}
for ( var j = 0 , l = nodes . length ; j < l ; ++ j ) {
var node = nodes [ j ] ;
if (
node . constructor === LiteGraph . Subgraph &&
eventname != "onExecute"
) {
if ( node . mode == mode ) {
node . sendEventToAllNodes ( eventname , params , mode ) ;
}
continue ;
}
if ( ! node [ eventname ] || node . mode != mode ) {
continue ;
}
if ( params === undefined ) {
node [ eventname ] ( ) ;
} else if ( params && params . constructor === Array ) {
node [ eventname ] . apply ( node , params ) ;
} else {
node [ eventname ] ( params ) ;
}
}
} ;
LGraph . prototype . sendActionToCanvas = function ( action , params ) {
if ( ! this . list _of _graphcanvas ) {
return ;
}
for ( var i = 0 ; i < this . list _of _graphcanvas . length ; ++ i ) {
var c = this . list _of _graphcanvas [ i ] ;
if ( c [ action ] ) {
c [ action ] . apply ( c , params ) ;
}
}
} ;
/ * *
* Adds a new node instance to this graph
* @ method add
* @ param { LGraphNode } node the instance of the node
* /
LGraph . prototype . add = function ( node , skip _compute _order ) {
if ( ! node ) {
return ;
}
//groups
if ( node . constructor === LGraphGroup ) {
this . _groups . push ( node ) ;
this . setDirtyCanvas ( true ) ;
this . change ( ) ;
node . graph = this ;
this . _version ++ ;
return ;
}
//nodes
if ( node . id != - 1 && this . _nodes _by _id [ node . id ] != null ) {
console . warn (
"LiteGraph: there is already a node with this ID, changing it"
) ;
node . id = ++ this . last _node _id ;
}
if ( this . _nodes . length >= LiteGraph . MAX _NUMBER _OF _NODES ) {
throw "LiteGraph: max number of nodes in a graph reached" ;
}
//give him an id
if ( node . id == null || node . id == - 1 ) {
node . id = ++ this . last _node _id ;
} else if ( this . last _node _id < node . id ) {
this . last _node _id = node . id ;
}
node . graph = this ;
this . _version ++ ;
this . _nodes . push ( node ) ;
this . _nodes _by _id [ node . id ] = node ;
if ( node . onAdded ) {
node . onAdded ( this ) ;
}
if ( this . config . align _to _grid ) {
node . alignToGrid ( ) ;
}
if ( ! skip _compute _order ) {
this . updateExecutionOrder ( ) ;
}
if ( this . onNodeAdded ) {
this . onNodeAdded ( node ) ;
}
this . setDirtyCanvas ( true ) ;
this . change ( ) ;
return node ; //to chain actions
} ;
/ * *
* Removes a node from the graph
* @ method remove
* @ param { LGraphNode } node the instance of the node
* /
LGraph . prototype . remove = function ( node ) {
if ( node . constructor === LiteGraph . LGraphGroup ) {
var index = this . _groups . indexOf ( node ) ;
if ( index != - 1 ) {
this . _groups . splice ( index , 1 ) ;
}
node . graph = null ;
this . _version ++ ;
this . setDirtyCanvas ( true , true ) ;
this . change ( ) ;
return ;
}
if ( this . _nodes _by _id [ node . id ] == null ) {
return ;
} //not found
if ( node . ignore _remove ) {
return ;
} //cannot be removed
this . beforeChange ( ) ; //sure? - almost sure is wrong
//disconnect inputs
if ( node . inputs ) {
for ( var i = 0 ; i < node . inputs . length ; i ++ ) {
var slot = node . inputs [ i ] ;
if ( slot . link != null ) {
node . disconnectInput ( i ) ;
}
}
}
//disconnect outputs
if ( node . outputs ) {
for ( var i = 0 ; i < node . outputs . length ; i ++ ) {
var slot = node . outputs [ i ] ;
if ( slot . links != null && slot . links . length ) {
node . disconnectOutput ( i ) ;
}
}
}
//node.id = -1; //why?
//callback
if ( node . onRemoved ) {
node . onRemoved ( ) ;
}
node . graph = null ;
this . _version ++ ;
//remove from canvas render
if ( this . list _of _graphcanvas ) {
for ( var i = 0 ; i < this . list _of _graphcanvas . length ; ++ i ) {
var canvas = this . list _of _graphcanvas [ i ] ;
if ( canvas . selected _nodes [ node . id ] ) {
delete canvas . selected _nodes [ node . id ] ;
}
if ( canvas . node _dragged == node ) {
canvas . node _dragged = null ;
}
}
}
//remove from containers
var pos = this . _nodes . indexOf ( node ) ;
if ( pos != - 1 ) {
this . _nodes . splice ( pos , 1 ) ;
}
delete this . _nodes _by _id [ node . id ] ;
if ( this . onNodeRemoved ) {
this . onNodeRemoved ( node ) ;
}
//close panels
this . sendActionToCanvas ( "checkPanels" ) ;
this . setDirtyCanvas ( true , true ) ;
this . afterChange ( ) ; //sure? - almost sure is wrong
this . change ( ) ;
this . updateExecutionOrder ( ) ;
} ;
/ * *
* Returns a node by its id .
* @ method getNodeById
* @ param { Number } id
* /
LGraph . prototype . getNodeById = function ( id ) {
if ( id == null ) {
return null ;
}
return this . _nodes _by _id [ id ] ;
} ;
/ * *
* Returns a list of nodes that matches a class
* @ method findNodesByClass
* @ param { Class } classObject the class itself ( not an string )
* @ return { Array } a list with all the nodes of this type
* /
LGraph . prototype . findNodesByClass = function ( classObject , result ) {
result = result || [ ] ;
result . length = 0 ;
for ( var i = 0 , l = this . _nodes . length ; i < l ; ++ i ) {
if ( this . _nodes [ i ] . constructor === classObject ) {
result . push ( this . _nodes [ i ] ) ;
}
}
return result ;
} ;
/ * *
* Returns a list of nodes that matches a type
* @ method findNodesByType
* @ param { String } type the name of the node type
* @ return { Array } a list with all the nodes of this type
* /
LGraph . prototype . findNodesByType = function ( type , result ) {
var type = type . toLowerCase ( ) ;
result = result || [ ] ;
result . length = 0 ;
for ( var i = 0 , l = this . _nodes . length ; i < l ; ++ i ) {
if ( this . _nodes [ i ] . type . toLowerCase ( ) == type ) {
result . push ( this . _nodes [ i ] ) ;
}
}
return result ;
} ;
/ * *
* Returns the first node that matches a name in its title
* @ method findNodeByTitle
* @ param { String } name the name of the node to search
* @ return { Node } the node or null
* /
LGraph . prototype . findNodeByTitle = function ( title ) {
for ( var i = 0 , l = this . _nodes . length ; i < l ; ++ i ) {
if ( this . _nodes [ i ] . title == title ) {
return this . _nodes [ i ] ;
}
}
return null ;
} ;
/ * *
* Returns a list of nodes that matches a name
* @ method findNodesByTitle
* @ param { String } name the name of the node to search
* @ return { Array } a list with all the nodes with this name
* /
LGraph . prototype . findNodesByTitle = function ( title ) {
var result = [ ] ;
for ( var i = 0 , l = this . _nodes . length ; i < l ; ++ i ) {
if ( this . _nodes [ i ] . title == title ) {
result . push ( this . _nodes [ i ] ) ;
}
}
return result ;
} ;
/ * *
* Returns the top - most node in this position of the canvas
* @ method getNodeOnPos
* @ param { number } x the x coordinate in canvas space
* @ param { number } y the y coordinate in canvas space
* @ param { Array } nodes _list a list with all the nodes to search from , by default is all the nodes in the graph
* @ return { LGraphNode } the node at this position or null
* /
LGraph . prototype . getNodeOnPos = function ( x , y , nodes _list , margin ) {
nodes _list = nodes _list || this . _nodes ;
var nRet = null ;
for ( var i = nodes _list . length - 1 ; i >= 0 ; i -- ) {
var n = nodes _list [ i ] ;
if ( n . isPointInside ( x , y , margin ) ) {
// check for lesser interest nodes (TODO check for overlapping, use the top)
/ * i f ( t y p e o f n = = " L G r a p h G r o u p " ) {
nRet = n ;
} else { * /
return n ;
/*}*/
}
}
return nRet ;
} ;
/ * *
* Returns the top - most group in that position
* @ method getGroupOnPos
* @ param { number } x the x coordinate in canvas space
* @ param { number } y the y coordinate in canvas space
* @ return { LGraphGroup } the group or null
* /
LGraph . prototype . getGroupOnPos = function ( x , y ) {
for ( var i = this . _groups . length - 1 ; i >= 0 ; i -- ) {
var g = this . _groups [ i ] ;
if ( g . isPointInside ( x , y , 2 , true ) ) {
return g ;
}
}
return null ;
} ;
/ * *
* Checks that the node type matches the node type registered , used when replacing a nodetype by a newer version during execution
* this replaces the ones using the old version with the new version
* @ method checkNodeTypes
* /
LGraph . prototype . checkNodeTypes = function ( ) {
var changes = false ;
for ( var i = 0 ; i < this . _nodes . length ; i ++ ) {
var node = this . _nodes [ i ] ;
var ctor = LiteGraph . registered _node _types [ node . type ] ;
if ( node . constructor == ctor ) {
continue ;
}
console . log ( "node being replaced by newer version: " + node . type ) ;
var newnode = LiteGraph . createNode ( node . type ) ;
changes = true ;
this . _nodes [ i ] = newnode ;
newnode . configure ( node . serialize ( ) ) ;
newnode . graph = this ;
this . _nodes _by _id [ newnode . id ] = newnode ;
if ( node . inputs ) {
newnode . inputs = node . inputs . concat ( ) ;
}
if ( node . outputs ) {
newnode . outputs = node . outputs . concat ( ) ;
}
}
this . updateExecutionOrder ( ) ;
} ;
// ********** GLOBALS *****************
LGraph . prototype . onAction = function ( action , param , options ) {
this . _input _nodes = this . findNodesByClass (
LiteGraph . GraphInput ,
this . _input _nodes
) ;
for ( var i = 0 ; i < this . _input _nodes . length ; ++ i ) {
var node = this . _input _nodes [ i ] ;
if ( node . properties . name != action ) {
continue ;
}
//wrap node.onAction(action, param);
node . actionDo ( action , param , options ) ;
break ;
}
} ;
LGraph . prototype . trigger = function ( action , param ) {
if ( this . onTrigger ) {
this . onTrigger ( action , param ) ;
}
} ;
/ * *
* Tell this graph it has a global graph input of this type
* @ method addGlobalInput
* @ param { String } name
* @ param { String } type
* @ param { * } value [ optional ]
* /
LGraph . prototype . addInput = function ( name , type , value ) {
var input = this . inputs [ name ] ;
if ( input ) {
//already exist
return ;
}
this . beforeChange ( ) ;
this . inputs [ name ] = { name : name , type : type , value : value } ;
this . _version ++ ;
this . afterChange ( ) ;
if ( this . onInputAdded ) {
this . onInputAdded ( name , type ) ;
}
if ( this . onInputsOutputsChange ) {
this . onInputsOutputsChange ( ) ;
}
} ;
/ * *
* Assign a data to the global graph input
* @ method setGlobalInputData
* @ param { String } name
* @ param { * } data
* /
LGraph . prototype . setInputData = function ( name , data ) {
var input = this . inputs [ name ] ;
if ( ! input ) {
return ;
}
input . value = data ;
} ;
/ * *
* Returns the current value of a global graph input
* @ method getInputData
* @ param { String } name
* @ return { * } the data
* /
LGraph . prototype . getInputData = function ( name ) {
var input = this . inputs [ name ] ;
if ( ! input ) {
return null ;
}
return input . value ;
} ;
/ * *
* Changes the name of a global graph input
* @ method renameInput
* @ param { String } old _name
* @ param { String } new _name
* /
LGraph . prototype . renameInput = function ( old _name , name ) {
if ( name == old _name ) {
return ;
}
if ( ! this . inputs [ old _name ] ) {
return false ;
}
if ( this . inputs [ name ] ) {
console . error ( "there is already one input with that name" ) ;
return false ;
}
this . inputs [ name ] = this . inputs [ old _name ] ;
delete this . inputs [ old _name ] ;
this . _version ++ ;
if ( this . onInputRenamed ) {
this . onInputRenamed ( old _name , name ) ;
}
if ( this . onInputsOutputsChange ) {
this . onInputsOutputsChange ( ) ;
}
} ;
/ * *
* Changes the type of a global graph input
* @ method changeInputType
* @ param { String } name
* @ param { String } type
* /
LGraph . prototype . changeInputType = function ( name , type ) {
if ( ! this . inputs [ name ] ) {
return false ;
}
if (
this . inputs [ name ] . type &&
String ( this . inputs [ name ] . type ) . toLowerCase ( ) ==
String ( type ) . toLowerCase ( )
) {
return ;
}
this . inputs [ name ] . type = type ;
this . _version ++ ;
if ( this . onInputTypeChanged ) {
this . onInputTypeChanged ( name , type ) ;
}
} ;
/ * *
* Removes a global graph input
* @ method removeInput
* @ param { String } name
* @ param { String } type
* /
LGraph . prototype . removeInput = function ( name ) {
if ( ! this . inputs [ name ] ) {
return false ;
}
delete this . inputs [ name ] ;
this . _version ++ ;
if ( this . onInputRemoved ) {
this . onInputRemoved ( name ) ;
}
if ( this . onInputsOutputsChange ) {
this . onInputsOutputsChange ( ) ;
}
return true ;
} ;
/ * *
* Creates a global graph output
* @ method addOutput
* @ param { String } name
* @ param { String } type
* @ param { * } value
* /
LGraph . prototype . addOutput = function ( name , type , value ) {
this . outputs [ name ] = { name : name , type : type , value : value } ;
this . _version ++ ;
if ( this . onOutputAdded ) {
this . onOutputAdded ( name , type ) ;
}
if ( this . onInputsOutputsChange ) {
this . onInputsOutputsChange ( ) ;
}
} ;
/ * *
* Assign a data to the global output
* @ method setOutputData
* @ param { String } name
* @ param { String } value
* /
LGraph . prototype . setOutputData = function ( name , value ) {
var output = this . outputs [ name ] ;
if ( ! output ) {
return ;
}
output . value = value ;
} ;
/ * *
* Returns the current value of a global graph output
* @ method getOutputData
* @ param { String } name
* @ return { * } the data
* /
LGraph . prototype . getOutputData = function ( name ) {
var output = this . outputs [ name ] ;
if ( ! output ) {
return null ;
}
return output . value ;
} ;
/ * *
* Renames a global graph output
* @ method renameOutput
* @ param { String } old _name
* @ param { String } new _name
* /
LGraph . prototype . renameOutput = function ( old _name , name ) {
if ( ! this . outputs [ old _name ] ) {
return false ;
}
if ( this . outputs [ name ] ) {
console . error ( "there is already one output with that name" ) ;
return false ;
}
this . outputs [ name ] = this . outputs [ old _name ] ;
delete this . outputs [ old _name ] ;
this . _version ++ ;
if ( this . onOutputRenamed ) {
this . onOutputRenamed ( old _name , name ) ;
}
if ( this . onInputsOutputsChange ) {
this . onInputsOutputsChange ( ) ;
}
} ;
/ * *
* Changes the type of a global graph output
* @ method changeOutputType
* @ param { String } name
* @ param { String } type
* /
LGraph . prototype . changeOutputType = function ( name , type ) {
if ( ! this . outputs [ name ] ) {
return false ;
}
if (
this . outputs [ name ] . type &&
String ( this . outputs [ name ] . type ) . toLowerCase ( ) ==
String ( type ) . toLowerCase ( )
) {
return ;
}
this . outputs [ name ] . type = type ;
this . _version ++ ;
if ( this . onOutputTypeChanged ) {
this . onOutputTypeChanged ( name , type ) ;
}
} ;
/ * *
* Removes a global graph output
* @ method removeOutput
* @ param { String } name
* /
LGraph . prototype . removeOutput = function ( name ) {
if ( ! this . outputs [ name ] ) {
return false ;
}
delete this . outputs [ name ] ;
this . _version ++ ;
if ( this . onOutputRemoved ) {
this . onOutputRemoved ( name ) ;
}
if ( this . onInputsOutputsChange ) {
this . onInputsOutputsChange ( ) ;
}
return true ;
} ;
LGraph . prototype . triggerInput = function ( name , value ) {
var nodes = this . findNodesByTitle ( name ) ;
for ( var i = 0 ; i < nodes . length ; ++ i ) {
nodes [ i ] . onTrigger ( value ) ;
}
} ;
LGraph . prototype . setCallback = function ( name , func ) {
var nodes = this . findNodesByTitle ( name ) ;
for ( var i = 0 ; i < nodes . length ; ++ i ) {
nodes [ i ] . setTrigger ( func ) ;
}
} ;
//used for undo, called before any change is made to the graph
LGraph . prototype . beforeChange = function ( info ) {
if ( this . onBeforeChange ) {
this . onBeforeChange ( this , info ) ;
}
this . sendActionToCanvas ( "onBeforeChange" , this ) ;
} ;
//used to resend actions, called after any change is made to the graph
LGraph . prototype . afterChange = function ( info ) {
if ( this . onAfterChange ) {
this . onAfterChange ( this , info ) ;
}
this . sendActionToCanvas ( "onAfterChange" , this ) ;
} ;
LGraph . prototype . connectionChange = function ( node , link _info ) {
this . updateExecutionOrder ( ) ;
if ( this . onConnectionChange ) {
this . onConnectionChange ( node ) ;
}
this . _version ++ ;
this . sendActionToCanvas ( "onConnectionChange" ) ;
} ;
/ * *
* returns if the graph is in live mode
* @ method isLive
* /
LGraph . prototype . isLive = function ( ) {
if ( ! this . list _of _graphcanvas ) {
return false ;
}
for ( var i = 0 ; i < this . list _of _graphcanvas . length ; ++ i ) {
var c = this . list _of _graphcanvas [ i ] ;
if ( c . live _mode ) {
return true ;
}
}
return false ;
} ;
/ * *
* clears the triggered slot animation in all links ( stop visual animation )
* @ method clearTriggeredSlots
* /
LGraph . prototype . clearTriggeredSlots = function ( ) {
for ( var i in this . links ) {
var link _info = this . links [ i ] ;
if ( ! link _info ) {
continue ;
}
if ( link _info . _last _time ) {
link _info . _last _time = 0 ;
}
}
} ;
/* Called when something visually changed (not the graph!) */
LGraph . prototype . change = function ( ) {
if ( LiteGraph . debug ) {
console . log ( "Graph changed" ) ;
}
this . sendActionToCanvas ( "setDirty" , [ true , true ] ) ;
if ( this . on _change ) {
this . on _change ( this ) ;
}
} ;
LGraph . prototype . setDirtyCanvas = function ( fg , bg ) {
this . sendActionToCanvas ( "setDirty" , [ fg , bg ] ) ;
} ;
/ * *
* Destroys a link
* @ method removeLink
* @ param { Number } link _id
* /
LGraph . prototype . removeLink = function ( link _id ) {
var link = this . links [ link _id ] ;
if ( ! link ) {
return ;
}
var node = this . getNodeById ( link . target _id ) ;
if ( node ) {
node . disconnectInput ( link . target _slot ) ;
}
} ;
//save and recover app state ***************************************
/ * *
* Creates a Object containing all the info about this graph , it can be serialized
* @ method serialize
* @ return { Object } value of the node
* /
LGraph . prototype . serialize = function ( ) {
var nodes _info = [ ] ;
for ( var i = 0 , l = this . _nodes . length ; i < l ; ++ i ) {
nodes _info . push ( this . _nodes [ i ] . serialize ( ) ) ;
}
//pack link info into a non-verbose format
var links = [ ] ;
for ( var i in this . links ) {
//links is an OBJECT
var link = this . links [ i ] ;
if ( ! link . serialize ) {
//weird bug I havent solved yet
console . warn (
"weird LLink bug, link info is not a LLink but a regular object"
) ;
var link2 = new LLink ( ) ;
for ( var j in link ) {
link2 [ j ] = link [ j ] ;
}
this . links [ i ] = link2 ;
link = link2 ;
}
links . push ( link . serialize ( ) ) ;
}
var groups _info = [ ] ;
for ( var i = 0 ; i < this . _groups . length ; ++ i ) {
groups _info . push ( this . _groups [ i ] . serialize ( ) ) ;
}
var data = {
last _node _id : this . last _node _id ,
last _link _id : this . last _link _id ,
nodes : nodes _info ,
links : links ,
groups : groups _info ,
config : this . config ,
extra : this . extra ,
version : LiteGraph . VERSION
} ;
if ( this . onSerialize )
this . onSerialize ( data ) ;
return data ;
} ;
/ * *
* Configure a graph from a JSON string
* @ method configure
* @ param { String } str configure a graph from a JSON string
* @ param { Boolean } returns if there was any error parsing
* /
LGraph . prototype . configure = function ( data , keep _old ) {
if ( ! data ) {
return ;
}
if ( ! keep _old ) {
this . clear ( ) ;
}
var nodes = data . nodes ;
//decode links info (they are very verbose)
if ( data . links && data . links . constructor === Array ) {
var links = [ ] ;
for ( var i = 0 ; i < data . links . length ; ++ i ) {
var link _data = data . links [ i ] ;
if ( ! link _data ) //weird bug
{
console . warn ( "serialized graph link data contains errors, skipping." ) ;
continue ;
}
var link = new LLink ( ) ;
link . configure ( link _data ) ;
links [ link . id ] = link ;
}
data . links = links ;
}
//copy all stored fields
for ( var i in data ) {
if ( i == "nodes" || i == "groups" ) //links must be accepted
continue ;
this [ i ] = data [ i ] ;
}
var error = false ;
//create nodes
this . _nodes = [ ] ;
if ( nodes ) {
for ( var i = 0 , l = nodes . length ; i < l ; ++ i ) {
var n _info = nodes [ i ] ; //stored info
var node = LiteGraph . createNode ( n _info . type , n _info . title ) ;
if ( ! node ) {
if ( LiteGraph . debug ) {
console . log (
"Node not found or has errors: " + n _info . type
) ;
}
//in case of error we create a replacement node to avoid losing info
node = new LGraphNode ( ) ;
node . last _serialization = n _info ;
node . has _errors = true ;
error = true ;
//continue;
}
node . id = n _info . id ; //id it or it will create a new id
this . add ( node , true ) ; //add before configure, otherwise configure cannot create links
}
//configure nodes afterwards so they can reach each other
for ( var i = 0 , l = nodes . length ; i < l ; ++ i ) {
var n _info = nodes [ i ] ;
var node = this . getNodeById ( n _info . id ) ;
if ( node ) {
node . configure ( n _info ) ;
}
}
}
//groups
this . _groups . length = 0 ;
if ( data . groups ) {
for ( var i = 0 ; i < data . groups . length ; ++ i ) {
var group = new LiteGraph . LGraphGroup ( ) ;
group . configure ( data . groups [ i ] ) ;
this . add ( group ) ;
}
}
this . updateExecutionOrder ( ) ;
this . extra = data . extra || { } ;
if ( this . onConfigure )
this . onConfigure ( data ) ;
this . _version ++ ;
this . setDirtyCanvas ( true , true ) ;
return error ;
} ;
LGraph . prototype . load = function ( url , callback ) {
var that = this ;
//from file
if ( url . constructor === File || url . constructor === Blob )
{
var reader = new FileReader ( ) ;
reader . addEventListener ( 'load' , function ( event ) {
var data = JSON . parse ( event . target . result ) ;
that . configure ( data ) ;
if ( callback )
callback ( ) ;
} ) ;
reader . readAsText ( url ) ;
return ;
}
//is a string, then an URL
var req = new XMLHttpRequest ( ) ;
req . open ( "GET" , url , true ) ;
req . send ( null ) ;
req . onload = function ( oEvent ) {
if ( req . status !== 200 ) {
console . error ( "Error loading graph:" , req . status , req . response ) ;
return ;
}
var data = JSON . parse ( req . response ) ;
that . configure ( data ) ;
if ( callback )
callback ( ) ;
} ;
req . onerror = function ( err ) {
console . error ( "Error loading graph:" , err ) ;
} ;
} ;
LGraph . prototype . onNodeTrace = function ( node , msg , color ) {
//TODO
} ;
//this is the class in charge of storing link information
function LLink ( id , type , origin _id , origin _slot , target _id , target _slot ) {
this . id = id ;
this . type = type ;
this . origin _id = origin _id ;
this . origin _slot = origin _slot ;
this . target _id = target _id ;
this . target _slot = target _slot ;
this . _data = null ;
this . _pos = new Float32Array ( 2 ) ; //center
}
LLink . prototype . configure = function ( o ) {
if ( o . constructor === Array ) {
this . id = o [ 0 ] ;
this . origin _id = o [ 1 ] ;
this . origin _slot = o [ 2 ] ;
this . target _id = o [ 3 ] ;
this . target _slot = o [ 4 ] ;
this . type = o [ 5 ] ;
} else {
this . id = o . id ;
this . type = o . type ;
this . origin _id = o . origin _id ;
this . origin _slot = o . origin _slot ;
this . target _id = o . target _id ;
this . target _slot = o . target _slot ;
}
} ;
LLink . prototype . serialize = function ( ) {
return [
this . id ,
this . origin _id ,
this . origin _slot ,
this . target _id ,
this . target _slot ,
this . type
] ;
} ;
LiteGraph . LLink = LLink ;
// *************************************************************
// Node CLASS *******
// *************************************************************
/ *
title : string
pos : [ x , y ]
size : [ x , y ]
input | output : every connection
+ { name : string , type : string , pos : [ x , y ] = Optional , direction : "input" | "output" , links : Array } ) ;
general properties :
+ clip _area : if you render outside the node , it will be clipped
+ unsafe _execution : not allowed for safe execution
+ skip _repeated _outputs : when adding new outputs , it wont show if there is one already connected
+ resizable : if set to false it wont be resizable with the mouse
+ horizontal : slots are distributed horizontally
+ widgets _start _y : widgets start at y distance from the top of the node
flags object :
+ collapsed : if it is collapsed
supported callbacks :
+ onAdded : when added to graph ( warning : this is called BEFORE the node is configured when loading )
+ onRemoved : when removed from graph
+ onStart : when the graph starts playing
+ onStop : when the graph stops playing
+ onDrawForeground : render the inside widgets inside the node
+ onDrawBackground : render the background area inside the node ( only in edit mode )
+ onMouseDown
+ onMouseMove
+ onMouseUp
+ onMouseEnter
+ onMouseLeave
+ onExecute : execute the node
+ onPropertyChanged : when a property is changed in the panel ( return true to skip default behaviour )
+ onGetInputs : returns an array of possible inputs
+ onGetOutputs : returns an array of possible outputs
+ onBounding : in case this node has a bigger bounding than the node itself ( the callback receives the bounding as [ x , y , w , h ] )
+ onDblClick : double clicked in the node
+ onInputDblClick : input slot double clicked ( can be used to automatically create a node connected )
+ onOutputDblClick : output slot double clicked ( can be used to automatically create a node connected )
+ onConfigure : called after the node has been configured
+ onSerialize : to add extra info when serializing ( the callback receives the object that should be filled with the data )
+ onSelected
+ onDeselected
+ onDropItem : DOM item dropped over the node
+ onDropFile : file dropped over the node
+ onConnectInput : if returns false the incoming connection will be canceled
+ onConnectionsChange : a connection changed ( new one or removed ) ( LiteGraph . INPUT or LiteGraph . OUTPUT , slot , true if connected , link _info , input _info )
+ onAction : action slot triggered
+ getExtraMenuOptions : to add option to context menu
* /
/ * *
* Base Class for all the node type classes
* @ class LGraphNode
* @ param { String } name a name for the node
* /
function LGraphNode ( title ) {
this . _ctor ( title ) ;
}
global . LGraphNode = LiteGraph . LGraphNode = LGraphNode ;
LGraphNode . prototype . _ctor = function ( title ) {
this . title = title || "Unnamed" ;
this . size = [ LiteGraph . NODE _WIDTH , 60 ] ;
this . graph = null ;
this . _pos = new Float32Array ( 10 , 10 ) ;
Object . defineProperty ( this , "pos" , {
set : function ( v ) {
if ( ! v || v . length < 2 ) {
return ;
}
this . _pos [ 0 ] = v [ 0 ] ;
this . _pos [ 1 ] = v [ 1 ] ;
} ,
get : function ( ) {
return this . _pos ;
} ,
enumerable : true
} ) ;
this . id = - 1 ; //not know till not added
this . type = null ;
//inputs available: array of inputs
this . inputs = [ ] ;
this . outputs = [ ] ;
this . connections = [ ] ;
//local data
this . properties = { } ; //for the values
this . properties _info = [ ] ; //for the info
this . flags = { } ;
} ;
/ * *
* configure a node from an object containing the serialized info
* @ method configure
* /
LGraphNode . prototype . configure = function ( info ) {
if ( this . graph ) {
this . graph . _version ++ ;
}
for ( var j in info ) {
if ( j == "properties" ) {
//i don't want to clone properties, I want to reuse the old container
for ( var k in info . properties ) {
this . properties [ k ] = info . properties [ k ] ;
if ( this . onPropertyChanged ) {
this . onPropertyChanged ( k , info . properties [ k ] ) ;
}
}
continue ;
}
if ( info [ j ] == null ) {
continue ;
} else if ( typeof info [ j ] == "object" ) {
//object
if ( this [ j ] && this [ j ] . configure ) {
this [ j ] . configure ( info [ j ] ) ;
} else {
this [ j ] = LiteGraph . cloneObject ( info [ j ] , this [ j ] ) ;
}
} //value
else {
this [ j ] = info [ j ] ;
}
}
if ( ! info . title ) {
this . title = this . constructor . title ;
}
2023-04-07 19:11:00 +00:00
if ( this . inputs ) {
for ( var i = 0 ; i < this . inputs . length ; ++ i ) {
var input = this . inputs [ i ] ;
var link _info = this . graph ? this . graph . links [ input . link ] : null ;
if ( this . onConnectionsChange )
this . onConnectionsChange ( LiteGraph . INPUT , i , true , link _info , input ) ; //link_info has been created now, so its updated
2023-01-03 06:53:32 +00:00
2023-04-07 19:11:00 +00:00
if ( this . onInputAdded )
this . onInputAdded ( input ) ;
}
}
if ( this . outputs ) {
for ( var i = 0 ; i < this . outputs . length ; ++ i ) {
var output = this . outputs [ i ] ;
if ( ! output . links ) {
continue ;
}
for ( var j = 0 ; j < output . links . length ; ++ j ) {
var link _info = this . graph ? this . graph . links [ output . links [ j ] ] : null ;
if ( this . onConnectionsChange )
this . onConnectionsChange ( LiteGraph . OUTPUT , i , true , link _info , output ) ; //link_info has been created now, so its updated
}
if ( this . onOutputAdded )
this . onOutputAdded ( output ) ;
}
2023-01-03 06:53:32 +00:00
}
if ( this . widgets )
{
for ( var i = 0 ; i < this . widgets . length ; ++ i )
{
var w = this . widgets [ i ] ;
if ( ! w )
continue ;
if ( w . options && w . options . property && this . properties [ w . options . property ] )
w . value = JSON . parse ( JSON . stringify ( this . properties [ w . options . property ] ) ) ;
}
if ( info . widgets _values ) {
for ( var i = 0 ; i < info . widgets _values . length ; ++ i ) {
if ( this . widgets [ i ] ) {
this . widgets [ i ] . value = info . widgets _values [ i ] ;
}
}
}
}
if ( this . onConfigure ) {
this . onConfigure ( info ) ;
}
} ;
/ * *
* serialize the content
* @ method serialize
* /
LGraphNode . prototype . serialize = function ( ) {
//create serialization object
var o = {
id : this . id ,
type : this . type ,
pos : this . pos ,
size : this . size ,
flags : LiteGraph . cloneObject ( this . flags ) ,
order : this . order ,
mode : this . mode
} ;
//special case for when there were errors
if ( this . constructor === LGraphNode && this . last _serialization ) {
return this . last _serialization ;
}
if ( this . inputs ) {
o . inputs = this . inputs ;
}
if ( this . outputs ) {
//clear outputs last data (because data in connections is never serialized but stored inside the outputs info)
for ( var i = 0 ; i < this . outputs . length ; i ++ ) {
delete this . outputs [ i ] . _data ;
}
o . outputs = this . outputs ;
}
if ( this . title && this . title != this . constructor . title ) {
o . title = this . title ;
}
if ( this . properties ) {
o . properties = LiteGraph . cloneObject ( this . properties ) ;
}
if ( this . widgets && this . serialize _widgets ) {
o . widgets _values = [ ] ;
for ( var i = 0 ; i < this . widgets . length ; ++ i ) {
if ( this . widgets [ i ] )
o . widgets _values [ i ] = this . widgets [ i ] . value ;
else
o . widgets _values [ i ] = null ;
}
}
if ( ! o . type ) {
o . type = this . constructor . type ;
}
if ( this . color ) {
o . color = this . color ;
}
if ( this . bgcolor ) {
o . bgcolor = this . bgcolor ;
}
if ( this . boxcolor ) {
o . boxcolor = this . boxcolor ;
}
if ( this . shape ) {
o . shape = this . shape ;
}
if ( this . onSerialize ) {
if ( this . onSerialize ( o ) ) {
console . warn (
"node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter"
) ;
}
}
return o ;
} ;
/* Creates a clone of this node */
LGraphNode . prototype . clone = function ( ) {
var node = LiteGraph . createNode ( this . type ) ;
if ( ! node ) {
return null ;
}
//we clone it because serialize returns shared containers
var data = LiteGraph . cloneObject ( this . serialize ( ) ) ;
//remove links
if ( data . inputs ) {
for ( var i = 0 ; i < data . inputs . length ; ++ i ) {
data . inputs [ i ] . link = null ;
}
}
if ( data . outputs ) {
for ( var i = 0 ; i < data . outputs . length ; ++ i ) {
if ( data . outputs [ i ] . links ) {
data . outputs [ i ] . links . length = 0 ;
}
}
}
delete data [ "id" ] ;
//remove links
node . configure ( data ) ;
return node ;
} ;
/ * *
* serialize and stringify
* @ method toString
* /
LGraphNode . prototype . toString = function ( ) {
return JSON . stringify ( this . serialize ( ) ) ;
} ;
//LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph
/ * *
* get the title string
* @ method getTitle
* /
LGraphNode . prototype . getTitle = function ( ) {
return this . title || this . constructor . title ;
} ;
/ * *
* sets the value of a property
* @ method setProperty
* @ param { String } name
* @ param { * } value
* /
LGraphNode . prototype . setProperty = function ( name , value ) {
if ( ! this . properties ) {
this . properties = { } ;
}
if ( value === this . properties [ name ] )
return ;
var prev _value = this . properties [ name ] ;
this . properties [ name ] = value ;
if ( this . onPropertyChanged ) {
if ( this . onPropertyChanged ( name , value , prev _value ) === false ) //abort change
this . properties [ name ] = prev _value ;
}
if ( this . widgets ) //widgets could be linked to properties
for ( var i = 0 ; i < this . widgets . length ; ++ i )
{
var w = this . widgets [ i ] ;
if ( ! w )
continue ;
if ( w . options . property == name )
{
w . value = value ;
break ;
}
}
} ;
// Execution *************************
/ * *
* sets the output data
* @ method setOutputData
* @ param { number } slot
* @ param { * } data
* /
LGraphNode . prototype . setOutputData = function ( slot , data ) {
if ( ! this . outputs ) {
return ;
}
//this maybe slow and a niche case
//if(slot && slot.constructor === String)
// slot = this.findOutputSlot(slot);
if ( slot == - 1 || slot >= this . outputs . length ) {
return ;
}
var output _info = this . outputs [ slot ] ;
if ( ! output _info ) {
return ;
}
//store data in the output itself in case we want to debug
output _info . _data = data ;
//if there are connections, pass the data to the connections
if ( this . outputs [ slot ] . links ) {
for ( var i = 0 ; i < this . outputs [ slot ] . links . length ; i ++ ) {
var link _id = this . outputs [ slot ] . links [ i ] ;
var link = this . graph . links [ link _id ] ;
if ( link )
link . data = data ;
}
}
} ;
/ * *
* sets the output data type , useful when you want to be able to overwrite the data type
* @ method setOutputDataType
* @ param { number } slot
* @ param { String } datatype
* /
LGraphNode . prototype . setOutputDataType = function ( slot , type ) {
if ( ! this . outputs ) {
return ;
}
if ( slot == - 1 || slot >= this . outputs . length ) {
return ;
}
var output _info = this . outputs [ slot ] ;
if ( ! output _info ) {
return ;
}
//store data in the output itself in case we want to debug
output _info . type = type ;
//if there are connections, pass the data to the connections
if ( this . outputs [ slot ] . links ) {
for ( var i = 0 ; i < this . outputs [ slot ] . links . length ; i ++ ) {
var link _id = this . outputs [ slot ] . links [ i ] ;
this . graph . links [ link _id ] . type = type ;
}
}
} ;
/ * *
* Retrieves the input data ( data traveling through the connection ) from one slot
* @ method getInputData
* @ param { number } slot
* @ param { boolean } force _update if set to true it will force the connected node of this slot to output data into this link
* @ return { * } data or if it is not connected returns undefined
* /
LGraphNode . prototype . getInputData = function ( slot , force _update ) {
if ( ! this . inputs ) {
return ;
} //undefined;
if ( slot >= this . inputs . length || this . inputs [ slot ] . link == null ) {
return ;
}
var link _id = this . inputs [ slot ] . link ;
var link = this . graph . links [ link _id ] ;
if ( ! link ) {
//bug: weird case but it happens sometimes
return null ;
}
if ( ! force _update ) {
return link . data ;
}
//special case: used to extract data from the incoming connection before the graph has been executed
var node = this . graph . getNodeById ( link . origin _id ) ;
if ( ! node ) {
return link . data ;
}
if ( node . updateOutputData ) {
node . updateOutputData ( link . origin _slot ) ;
} else if ( node . onExecute ) {
node . onExecute ( ) ;
}
return link . data ;
} ;
/ * *
* Retrieves the input data type ( in case this supports multiple input types )
* @ method getInputDataType
* @ param { number } slot
* @ return { String } datatype in string format
* /
LGraphNode . prototype . getInputDataType = function ( slot ) {
if ( ! this . inputs ) {
return null ;
} //undefined;
if ( slot >= this . inputs . length || this . inputs [ slot ] . link == null ) {
return null ;
}
var link _id = this . inputs [ slot ] . link ;
var link = this . graph . links [ link _id ] ;
if ( ! link ) {
//bug: weird case but it happens sometimes
return null ;
}
var node = this . graph . getNodeById ( link . origin _id ) ;
if ( ! node ) {
return link . type ;
}
var output _info = node . outputs [ link . origin _slot ] ;
if ( output _info ) {
return output _info . type ;
}
return null ;
} ;
/ * *
* Retrieves the input data from one slot using its name instead of slot number
* @ method getInputDataByName
* @ param { String } slot _name
* @ param { boolean } force _update if set to true it will force the connected node of this slot to output data into this link
* @ return { * } data or if it is not connected returns null
* /
LGraphNode . prototype . getInputDataByName = function (
slot _name ,
force _update
) {
var slot = this . findInputSlot ( slot _name ) ;
if ( slot == - 1 ) {
return null ;
}
return this . getInputData ( slot , force _update ) ;
} ;
/ * *
* tells you if there is a connection in one input slot
* @ method isInputConnected
* @ param { number } slot
* @ return { boolean }
* /
LGraphNode . prototype . isInputConnected = function ( slot ) {
if ( ! this . inputs ) {
return false ;
}
return slot < this . inputs . length && this . inputs [ slot ] . link != null ;
} ;
/ * *
* tells you info about an input connection ( which node , type , etc )
* @ method getInputInfo
* @ param { number } slot
* @ return { Object } object or null { link : id , name : string , type : string or 0 }
* /
LGraphNode . prototype . getInputInfo = function ( slot ) {
if ( ! this . inputs ) {
return null ;
}
if ( slot < this . inputs . length ) {
return this . inputs [ slot ] ;
}
return null ;
} ;
/ * *
* Returns the link info in the connection of an input slot
* @ method getInputLink
* @ param { number } slot
* @ return { LLink } object or null
* /
LGraphNode . prototype . getInputLink = function ( slot ) {
if ( ! this . inputs ) {
return null ;
}
if ( slot < this . inputs . length ) {
var slot _info = this . inputs [ slot ] ;
return this . graph . links [ slot _info . link ] ;
}
return null ;
} ;
/ * *
* returns the node connected in the input slot
* @ method getInputNode
* @ param { number } slot
* @ return { LGraphNode } node or null
* /
LGraphNode . prototype . getInputNode = function ( slot ) {
if ( ! this . inputs ) {
return null ;
}
if ( slot >= this . inputs . length ) {
return null ;
}
var input = this . inputs [ slot ] ;
if ( ! input || input . link === null ) {
return null ;
}
var link _info = this . graph . links [ input . link ] ;
if ( ! link _info ) {
return null ;
}
return this . graph . getNodeById ( link _info . origin _id ) ;
} ;
/ * *
* returns the value of an input with this name , otherwise checks if there is a property with that name
* @ method getInputOrProperty
* @ param { string } name
* @ return { * } value
* /
LGraphNode . prototype . getInputOrProperty = function ( name ) {
if ( ! this . inputs || ! this . inputs . length ) {
return this . properties ? this . properties [ name ] : null ;
}
for ( var i = 0 , l = this . inputs . length ; i < l ; ++ i ) {
var input _info = this . inputs [ i ] ;
if ( name == input _info . name && input _info . link != null ) {
var link = this . graph . links [ input _info . link ] ;
if ( link ) {
return link . data ;
}
}
}
return this . properties [ name ] ;
} ;
/ * *
* tells you the last output data that went in that slot
* @ method getOutputData
* @ param { number } slot
* @ return { Object } object or null
* /
LGraphNode . prototype . getOutputData = function ( slot ) {
if ( ! this . outputs ) {
return null ;
}
if ( slot >= this . outputs . length ) {
return null ;
}
var info = this . outputs [ slot ] ;
return info . _data ;
} ;
/ * *
* tells you info about an output connection ( which node , type , etc )
* @ method getOutputInfo
* @ param { number } slot
* @ return { Object } object or null { name : string , type : string , links : [ ids of links in number ] }
* /
LGraphNode . prototype . getOutputInfo = function ( slot ) {
if ( ! this . outputs ) {
return null ;
}
if ( slot < this . outputs . length ) {
return this . outputs [ slot ] ;
}
return null ;
} ;
/ * *
* tells you if there is a connection in one output slot
* @ method isOutputConnected
* @ param { number } slot
* @ return { boolean }
* /
LGraphNode . prototype . isOutputConnected = function ( slot ) {
if ( ! this . outputs ) {
return false ;
}
return (
slot < this . outputs . length &&
this . outputs [ slot ] . links &&
this . outputs [ slot ] . links . length
) ;
} ;
/ * *
* tells you if there is any connection in the output slots
* @ method isAnyOutputConnected
* @ return { boolean }
* /
LGraphNode . prototype . isAnyOutputConnected = function ( ) {
if ( ! this . outputs ) {
return false ;
}
for ( var i = 0 ; i < this . outputs . length ; ++ i ) {
if ( this . outputs [ i ] . links && this . outputs [ i ] . links . length ) {
return true ;
}
}
return false ;
} ;
/ * *
* retrieves all the nodes connected to this output slot
* @ method getOutputNodes
* @ param { number } slot
* @ return { array }
* /
LGraphNode . prototype . getOutputNodes = function ( slot ) {
if ( ! this . outputs || this . outputs . length == 0 ) {
return null ;
}
if ( slot >= this . outputs . length ) {
return null ;
}
var output = this . outputs [ slot ] ;
if ( ! output . links || output . links . length == 0 ) {
return null ;
}
var r = [ ] ;
for ( var i = 0 ; i < output . links . length ; i ++ ) {
var link _id = output . links [ i ] ;
var link = this . graph . links [ link _id ] ;
if ( link ) {
var target _node = this . graph . getNodeById ( link . target _id ) ;
if ( target _node ) {
r . push ( target _node ) ;
}
}
}
return r ;
} ;
LGraphNode . prototype . addOnTriggerInput = function ( ) {
var trigS = this . findInputSlot ( "onTrigger" ) ;
if ( trigS == - 1 ) { //!trigS ||
var input = this . addInput ( "onTrigger" , LiteGraph . EVENT , { optional : true , nameLocked : true } ) ;
return this . findInputSlot ( "onTrigger" ) ;
}
return trigS ;
}
LGraphNode . prototype . addOnExecutedOutput = function ( ) {
var trigS = this . findOutputSlot ( "onExecuted" ) ;
if ( trigS == - 1 ) { //!trigS ||
var output = this . addOutput ( "onExecuted" , LiteGraph . ACTION , { optional : true , nameLocked : true } ) ;
return this . findOutputSlot ( "onExecuted" ) ;
}
return trigS ;
}
LGraphNode . prototype . onAfterExecuteNode = function ( param , options ) {
var trigS = this . findOutputSlot ( "onExecuted" ) ;
if ( trigS != - 1 ) {
//console.debug(this.id+":"+this.order+" triggering slot onAfterExecute");
//console.debug(param);
//console.debug(options);
this . triggerSlot ( trigS , param , null , options ) ;
}
}
LGraphNode . prototype . changeMode = function ( modeTo ) {
switch ( modeTo ) {
case LiteGraph . ON _EVENT :
// this.addOnExecutedOutput();
break ;
case LiteGraph . ON _TRIGGER :
this . addOnTriggerInput ( ) ;
this . addOnExecutedOutput ( ) ;
break ;
case LiteGraph . NEVER :
break ;
case LiteGraph . ALWAYS :
break ;
case LiteGraph . ON _REQUEST :
break ;
default :
return false ;
break ;
}
this . mode = modeTo ;
return true ;
} ;
/ * *
* Triggers the node code execution , place a boolean / counter to mark the node as being executed
* @ method execute
* @ param { * } param
* @ param { * } options
* /
LGraphNode . prototype . doExecute = function ( param , options ) {
options = options || { } ;
if ( this . onExecute ) {
// enable this to give the event an ID
if ( ! options . action _call ) options . action _call = this . id + "_exec_" + Math . floor ( Math . random ( ) * 9999 ) ;
this . graph . nodes _executing [ this . id ] = true ; //.push(this.id);
this . onExecute ( param , options ) ;
this . graph . nodes _executing [ this . id ] = false ; //.pop();
// save execution/action ref
this . exec _version = this . graph . iteration ;
if ( options && options . action _call ) {
this . action _call = options . action _call ; // if (param)
this . graph . nodes _executedAction [ this . id ] = options . action _call ;
}
}
this . execute _triggered = 2 ; // the nFrames it will be used (-- each step), means "how old" is the event
if ( this . onAfterExecuteNode ) this . onAfterExecuteNode ( param , options ) ; // callback
} ;
/ * *
* Triggers an action , wrapped by logics to control execution flow
* @ method actionDo
* @ param { String } action name
* @ param { * } param
* /
LGraphNode . prototype . actionDo = function ( action , param , options ) {
options = options || { } ;
if ( this . onAction ) {
// enable this to give the event an ID
if ( ! options . action _call ) options . action _call = this . id + "_" + ( action ? action : "action" ) + "_" + Math . floor ( Math . random ( ) * 9999 ) ;
this . graph . nodes _actioning [ this . id ] = ( action ? action : "actioning" ) ; //.push(this.id);
this . onAction ( action , param , options ) ;
this . graph . nodes _actioning [ this . id ] = false ; //.pop();
// save execution/action ref
if ( options && options . action _call ) {
this . action _call = options . action _call ; // if (param)
this . graph . nodes _executedAction [ this . id ] = options . action _call ;
}
}
this . action _triggered = 2 ; // the nFrames it will be used (-- each step), means "how old" is the event
if ( this . onAfterExecuteNode ) this . onAfterExecuteNode ( param , options ) ;
} ;
/ * *
* Triggers an event in this node , this will trigger any output with the same name
* @ method trigger
* @ param { String } event name ( "on_play" , ... ) if action is equivalent to false then the event is send to all
* @ param { * } param
* /
LGraphNode . prototype . trigger = function ( action , param , options ) {
if ( ! this . outputs || ! this . outputs . length ) {
return ;
}
if ( this . graph )
this . graph . _last _trigger _time = LiteGraph . getTime ( ) ;
for ( var i = 0 ; i < this . outputs . length ; ++ i ) {
var output = this . outputs [ i ] ;
if ( ! output || output . type !== LiteGraph . EVENT || ( action && output . name != action ) )
continue ;
this . triggerSlot ( i , param , null , options ) ;
}
} ;
/ * *
* Triggers a slot event in this node : cycle output slots and launch execute / action on connected nodes
* @ method triggerSlot
* @ param { Number } slot the index of the output slot
* @ param { * } param
* @ param { Number } link _id [ optional ] in case you want to trigger and specific output link in a slot
* /
LGraphNode . prototype . triggerSlot = function ( slot , param , link _id , options ) {
options = options || { } ;
if ( ! this . outputs ) {
return ;
}
2023-04-07 19:11:00 +00:00
if ( slot == null )
{
console . error ( "slot must be a number" ) ;
return ;
}
if ( slot . constructor !== Number )
console . warn ( "slot must be a number, use node.trigger('name') if you want to use a string" ) ;
2023-01-03 06:53:32 +00:00
var output = this . outputs [ slot ] ;
if ( ! output ) {
return ;
}
var links = output . links ;
if ( ! links || ! links . length ) {
return ;
}
if ( this . graph ) {
this . graph . _last _trigger _time = LiteGraph . getTime ( ) ;
}
//for every link attached here
for ( var k = 0 ; k < links . length ; ++ k ) {
var id = links [ k ] ;
if ( link _id != null && link _id != id ) {
//to skip links
continue ;
}
var link _info = this . graph . links [ links [ k ] ] ;
if ( ! link _info ) {
//not connected
continue ;
}
link _info . _last _time = LiteGraph . getTime ( ) ;
var node = this . graph . getNodeById ( link _info . target _id ) ;
if ( ! node ) {
//node not found?
continue ;
}
//used to mark events in graph
var target _connection = node . inputs [ link _info . target _slot ] ;
if ( node . mode === LiteGraph . ON _TRIGGER )
{
// generate unique trigger ID if not present
if ( ! options . action _call ) options . action _call = this . id + "_trigg_" + Math . floor ( Math . random ( ) * 9999 ) ;
if ( node . onExecute ) {
// -- wrapping node.onExecute(param); --
node . doExecute ( param , options ) ;
}
}
else if ( node . onAction ) {
// generate unique action ID if not present
if ( ! options . action _call ) options . action _call = this . id + "_act_" + Math . floor ( Math . random ( ) * 9999 ) ;
//pass the action name
var target _connection = node . inputs [ link _info . target _slot ] ;
// wrap node.onAction(target_connection.name, param);
node . actionDo ( target _connection . name , param , options ) ;
}
}
} ;
/ * *
* clears the trigger slot animation
* @ method clearTriggeredSlot
* @ param { Number } slot the index of the output slot
* @ param { Number } link _id [ optional ] in case you want to trigger and specific output link in a slot
* /
LGraphNode . prototype . clearTriggeredSlot = function ( slot , link _id ) {
if ( ! this . outputs ) {
return ;
}
var output = this . outputs [ slot ] ;
if ( ! output ) {
return ;
}
var links = output . links ;
if ( ! links || ! links . length ) {
return ;
}
//for every link attached here
for ( var k = 0 ; k < links . length ; ++ k ) {
var id = links [ k ] ;
if ( link _id != null && link _id != id ) {
//to skip links
continue ;
}
var link _info = this . graph . links [ links [ k ] ] ;
if ( ! link _info ) {
//not connected
continue ;
}
link _info . _last _time = 0 ;
}
} ;
/ * *
* changes node size and triggers callback
* @ method setSize
* @ param { vec2 } size
* /
LGraphNode . prototype . setSize = function ( size )
{
this . size = size ;
if ( this . onResize )
this . onResize ( this . size ) ;
}
/ * *
* add a new property to this node
* @ method addProperty
* @ param { string } name
* @ param { * } default _value
* @ param { string } type string defining the output type ( "vec3" , "number" , ... )
* @ param { Object } extra _info this can be used to have special properties of the property ( like values , etc )
* /
LGraphNode . prototype . addProperty = function (
name ,
default _value ,
type ,
extra _info
) {
var o = { name : name , type : type , default _value : default _value } ;
if ( extra _info ) {
for ( var i in extra _info ) {
o [ i ] = extra _info [ i ] ;
}
}
if ( ! this . properties _info ) {
this . properties _info = [ ] ;
}
this . properties _info . push ( o ) ;
if ( ! this . properties ) {
this . properties = { } ;
}
this . properties [ name ] = default _value ;
return o ;
} ;
//connections
/ * *
* add a new output slot to use in this node
* @ method addOutput
* @ param { string } name
* @ param { string } type string defining the output type ( "vec3" , "number" , ... )
* @ param { Object } extra _info this can be used to have special properties of an output ( label , special color , position , etc )
* /
LGraphNode . prototype . addOutput = function ( name , type , extra _info ) {
2023-04-07 19:11:00 +00:00
var output = { name : name , type : type , links : null } ;
2023-01-03 06:53:32 +00:00
if ( extra _info ) {
for ( var i in extra _info ) {
2023-04-07 19:11:00 +00:00
output [ i ] = extra _info [ i ] ;
2023-01-03 06:53:32 +00:00
}
}
if ( ! this . outputs ) {
this . outputs = [ ] ;
}
2023-04-07 19:11:00 +00:00
this . outputs . push ( output ) ;
2023-01-03 06:53:32 +00:00
if ( this . onOutputAdded ) {
2023-04-07 19:11:00 +00:00
this . onOutputAdded ( output ) ;
2023-01-03 06:53:32 +00:00
}
if ( LiteGraph . auto _load _slot _types ) LiteGraph . registerNodeAndSlotType ( this , type , true ) ;
this . setSize ( this . computeSize ( ) ) ;
this . setDirtyCanvas ( true , true ) ;
2023-04-07 19:11:00 +00:00
return output ;
2023-01-03 06:53:32 +00:00
} ;
/ * *
* add a new output slot to use in this node
* @ method addOutputs
* @ param { Array } array of triplets like [ [ name , type , extra _info ] , [ ... ] ]
* /
LGraphNode . prototype . addOutputs = function ( array ) {
for ( var i = 0 ; i < array . length ; ++ i ) {
var info = array [ i ] ;
var o = { name : info [ 0 ] , type : info [ 1 ] , link : null } ;
if ( array [ 2 ] ) {
for ( var j in info [ 2 ] ) {
o [ j ] = info [ 2 ] [ j ] ;
}
}
if ( ! this . outputs ) {
this . outputs = [ ] ;
}
this . outputs . push ( o ) ;
if ( this . onOutputAdded ) {
this . onOutputAdded ( o ) ;
}
if ( LiteGraph . auto _load _slot _types ) LiteGraph . registerNodeAndSlotType ( this , info [ 1 ] , true ) ;
}
this . setSize ( this . computeSize ( ) ) ;
this . setDirtyCanvas ( true , true ) ;
} ;
/ * *
* remove an existing output slot
* @ method removeOutput
* @ param { number } slot
* /
LGraphNode . prototype . removeOutput = function ( slot ) {
this . disconnectOutput ( slot ) ;
this . outputs . splice ( slot , 1 ) ;
for ( var i = slot ; i < this . outputs . length ; ++ i ) {
if ( ! this . outputs [ i ] || ! this . outputs [ i ] . links ) {
continue ;
}
var links = this . outputs [ i ] . links ;
for ( var j = 0 ; j < links . length ; ++ j ) {
var link = this . graph . links [ links [ j ] ] ;
if ( ! link ) {
continue ;
}
link . origin _slot -= 1 ;
}
}
this . setSize ( this . computeSize ( ) ) ;
if ( this . onOutputRemoved ) {
this . onOutputRemoved ( slot ) ;
}
this . setDirtyCanvas ( true , true ) ;
} ;
/ * *
* add a new input slot to use in this node
* @ method addInput
* @ param { string } name
* @ param { string } type string defining the input type ( "vec3" , "number" , ... ) , it its a generic one use 0
* @ param { Object } extra _info this can be used to have special properties of an input ( label , color , position , etc )
* /
LGraphNode . prototype . addInput = function ( name , type , extra _info ) {
type = type || 0 ;
2023-04-07 19:11:00 +00:00
var input = { name : name , type : type , link : null } ;
2023-01-03 06:53:32 +00:00
if ( extra _info ) {
for ( var i in extra _info ) {
2023-04-07 19:11:00 +00:00
input [ i ] = extra _info [ i ] ;
2023-01-03 06:53:32 +00:00
}
}
if ( ! this . inputs ) {
this . inputs = [ ] ;
}
2023-04-07 19:11:00 +00:00
this . inputs . push ( input ) ;
2023-01-03 06:53:32 +00:00
this . setSize ( this . computeSize ( ) ) ;
if ( this . onInputAdded ) {
2023-04-07 19:11:00 +00:00
this . onInputAdded ( input ) ;
2023-01-03 06:53:32 +00:00
}
LiteGraph . registerNodeAndSlotType ( this , type ) ;
this . setDirtyCanvas ( true , true ) ;
2023-04-07 19:11:00 +00:00
return input ;
2023-01-03 06:53:32 +00:00
} ;
/ * *
* add several new input slots in this node
* @ method addInputs
* @ param { Array } array of triplets like [ [ name , type , extra _info ] , [ ... ] ]
* /
LGraphNode . prototype . addInputs = function ( array ) {
for ( var i = 0 ; i < array . length ; ++ i ) {
var info = array [ i ] ;
var o = { name : info [ 0 ] , type : info [ 1 ] , link : null } ;
if ( array [ 2 ] ) {
for ( var j in info [ 2 ] ) {
o [ j ] = info [ 2 ] [ j ] ;
}
}
if ( ! this . inputs ) {
this . inputs = [ ] ;
}
this . inputs . push ( o ) ;
if ( this . onInputAdded ) {
this . onInputAdded ( o ) ;
}
LiteGraph . registerNodeAndSlotType ( this , info [ 1 ] ) ;
}
this . setSize ( this . computeSize ( ) ) ;
this . setDirtyCanvas ( true , true ) ;
} ;
/ * *
* remove an existing input slot
* @ method removeInput
* @ param { number } slot
* /
LGraphNode . prototype . removeInput = function ( slot ) {
this . disconnectInput ( slot ) ;
var slot _info = this . inputs . splice ( slot , 1 ) ;
for ( var i = slot ; i < this . inputs . length ; ++ i ) {
if ( ! this . inputs [ i ] ) {
continue ;
}
var link = this . graph . links [ this . inputs [ i ] . link ] ;
if ( ! link ) {
continue ;
}
link . target _slot -= 1 ;
}
this . setSize ( this . computeSize ( ) ) ;
if ( this . onInputRemoved ) {
this . onInputRemoved ( slot , slot _info [ 0 ] ) ;
}
this . setDirtyCanvas ( true , true ) ;
} ;
/ * *
* add an special connection to this node ( used for special kinds of graphs )
* @ method addConnection
* @ param { string } name
* @ param { string } type string defining the input type ( "vec3" , "number" , ... )
* @ param { [ x , y ] } pos position of the connection inside the node
* @ param { string } direction if is input or output
* /
LGraphNode . prototype . addConnection = function ( name , type , pos , direction ) {
var o = {
name : name ,
type : type ,
pos : pos ,
direction : direction ,
links : null
} ;
this . connections . push ( o ) ;
return o ;
} ;
/ * *
* computes the minimum size of a node according to its inputs and output slots
* @ method computeSize
* @ param { number } minHeight
* @ return { number } the total size
* /
LGraphNode . prototype . computeSize = function ( out ) {
if ( this . constructor . size ) {
return this . constructor . size . concat ( ) ;
}
var rows = Math . max (
this . inputs ? this . inputs . length : 1 ,
this . outputs ? this . outputs . length : 1
) ;
var size = out || new Float32Array ( [ 0 , 0 ] ) ;
rows = Math . max ( rows , 1 ) ;
var font _size = LiteGraph . NODE _TEXT _SIZE ; //although it should be graphcanvas.inner_text_font size
var title _width = compute _text _size ( this . title ) ;
var input _width = 0 ;
var output _width = 0 ;
if ( this . inputs ) {
for ( var i = 0 , l = this . inputs . length ; i < l ; ++ i ) {
var input = this . inputs [ i ] ;
var text = input . label || input . name || "" ;
var text _width = compute _text _size ( text ) ;
if ( input _width < text _width ) {
input _width = text _width ;
}
}
}
if ( this . outputs ) {
for ( var i = 0 , l = this . outputs . length ; i < l ; ++ i ) {
var output = this . outputs [ i ] ;
var text = output . label || output . name || "" ;
var text _width = compute _text _size ( text ) ;
if ( output _width < text _width ) {
output _width = text _width ;
}
}
}
size [ 0 ] = Math . max ( input _width + output _width + 10 , title _width ) ;
size [ 0 ] = Math . max ( size [ 0 ] , LiteGraph . NODE _WIDTH ) ;
if ( this . widgets && this . widgets . length ) {
size [ 0 ] = Math . max ( size [ 0 ] , LiteGraph . NODE _WIDTH * 1.5 ) ;
}
size [ 1 ] = ( this . constructor . slot _start _y || 0 ) + rows * LiteGraph . NODE _SLOT _HEIGHT ;
var widgets _height = 0 ;
if ( this . widgets && this . widgets . length ) {
for ( var i = 0 , l = this . widgets . length ; i < l ; ++ i ) {
if ( this . widgets [ i ] . computeSize )
widgets _height += this . widgets [ i ] . computeSize ( size [ 0 ] ) [ 1 ] + 4 ;
else
widgets _height += LiteGraph . NODE _WIDGET _HEIGHT + 4 ;
}
widgets _height += 8 ;
}
//compute height using widgets height
if ( this . widgets _up )
size [ 1 ] = Math . max ( size [ 1 ] , widgets _height ) ;
else if ( this . widgets _start _y != null )
size [ 1 ] = Math . max ( size [ 1 ] , widgets _height + this . widgets _start _y ) ;
else
size [ 1 ] += widgets _height ;
function compute _text _size ( text ) {
if ( ! text ) {
return 0 ;
}
return font _size * text . length * 0.6 ;
}
if (
this . constructor . min _height &&
size [ 1 ] < this . constructor . min _height
) {
size [ 1 ] = this . constructor . min _height ;
}
size [ 1 ] += 6 ; //margin
return size ;
} ;
/ * *
* returns all the info available about a property of this node .
*
* @ method getPropertyInfo
* @ param { String } property name of the property
* @ return { Object } the object with all the available info
* /
LGraphNode . prototype . getPropertyInfo = function ( property )
{
var info = null ;
//there are several ways to define info about a property
//legacy mode
if ( this . properties _info ) {
for ( var i = 0 ; i < this . properties _info . length ; ++ i ) {
if ( this . properties _info [ i ] . name == property ) {
info = this . properties _info [ i ] ;
break ;
}
}
}
//litescene mode using the constructor
if ( this . constructor [ "@" + property ] )
info = this . constructor [ "@" + property ] ;
if ( this . constructor . widgets _info && this . constructor . widgets _info [ property ] )
info = this . constructor . widgets _info [ property ] ;
//litescene mode using the constructor
if ( ! info && this . onGetPropertyInfo ) {
info = this . onGetPropertyInfo ( property ) ;
}
if ( ! info )
info = { } ;
if ( ! info . type )
info . type = typeof this . properties [ property ] ;
if ( info . widget == "combo" )
info . type = "enum" ;
return info ;
}
/ * *
* Defines a widget inside the node , it will be rendered on top of the node , you can control lots of properties
*
* @ method addWidget
* @ param { String } type the widget type ( could be "number" , "string" , "combo"
* @ param { String } name the text to show on the widget
* @ param { String } value the default value
* @ param { Function | String } callback function to call when it changes ( optionally , it can be the name of the property to modify )
* @ param { Object } options the object that contains special properties of this widget
* @ return { Object } the created widget object
* /
LGraphNode . prototype . addWidget = function ( type , name , value , callback , options )
{
if ( ! this . widgets ) {
this . widgets = [ ] ;
}
if ( ! options && callback && callback . constructor === Object )
{
options = callback ;
callback = null ;
}
if ( options && options . constructor === String ) //options can be the property name
options = { property : options } ;
if ( callback && callback . constructor === String ) //callback can be the property name
{
if ( ! options )
options = { } ;
options . property = callback ;
callback = null ;
}
if ( callback && callback . constructor !== Function )
{
console . warn ( "addWidget: callback must be a function" ) ;
callback = null ;
}
var w = {
type : type . toLowerCase ( ) ,
name : name ,
value : value ,
callback : callback ,
options : options || { }
} ;
if ( w . options . y !== undefined ) {
w . y = w . options . y ;
}
if ( ! callback && ! w . options . callback && ! w . options . property ) {
console . warn ( "LiteGraph addWidget(...) without a callback or property assigned" ) ;
}
if ( type == "combo" && ! w . options . values ) {
throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }" ;
}
this . widgets . push ( w ) ;
this . setSize ( this . computeSize ( ) ) ;
return w ;
} ;
LGraphNode . prototype . addCustomWidget = function ( custom _widget ) {
if ( ! this . widgets ) {
this . widgets = [ ] ;
}
this . widgets . push ( custom _widget ) ;
return custom _widget ;
} ;
/ * *
* returns the bounding of the object , used for rendering purposes
* bounding is : [ topleft _cornerx , topleft _cornery , width , height ]
* @ method getBounding
* @ return { Float32Array [ 4 ] } the total size
* /
LGraphNode . prototype . getBounding = function ( out ) {
out = out || new Float32Array ( 4 ) ;
out [ 0 ] = this . pos [ 0 ] - 4 ;
out [ 1 ] = this . pos [ 1 ] - LiteGraph . NODE _TITLE _HEIGHT ;
out [ 2 ] = this . size [ 0 ] + 4 ;
out [ 3 ] = this . flags . collapsed ? LiteGraph . NODE _TITLE _HEIGHT : this . size [ 1 ] + LiteGraph . NODE _TITLE _HEIGHT ;
if ( this . onBounding ) {
this . onBounding ( out ) ;
}
return out ;
} ;
/ * *
* checks if a point is inside the shape of a node
* @ method isPointInside
* @ param { number } x
* @ param { number } y
* @ return { boolean }
* /
LGraphNode . prototype . isPointInside = function ( x , y , margin , skip _title ) {
margin = margin || 0 ;
var margin _top = this . graph && this . graph . isLive ( ) ? 0 : LiteGraph . NODE _TITLE _HEIGHT ;
if ( skip _title ) {
margin _top = 0 ;
}
if ( this . flags && this . flags . collapsed ) {
//if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS)
if (
isInsideRectangle (
x ,
y ,
this . pos [ 0 ] - margin ,
this . pos [ 1 ] - LiteGraph . NODE _TITLE _HEIGHT - margin ,
( this . _collapsed _width || LiteGraph . NODE _COLLAPSED _WIDTH ) +
2 * margin ,
LiteGraph . NODE _TITLE _HEIGHT + 2 * margin
)
) {
return true ;
}
} else if (
this . pos [ 0 ] - 4 - margin < x &&
this . pos [ 0 ] + this . size [ 0 ] + 4 + margin > x &&
this . pos [ 1 ] - margin _top - margin < y &&
this . pos [ 1 ] + this . size [ 1 ] + margin > y
) {
return true ;
}
return false ;
} ;
/ * *
* checks if a point is inside a node slot , and returns info about which slot
* @ method getSlotInPosition
* @ param { number } x
* @ param { number } y
* @ return { Object } if found the object contains { input | output : slot object , slot : number , link _pos : [ x , y ] }
* /
LGraphNode . prototype . getSlotInPosition = function ( x , y ) {
//search for inputs
var link _pos = new Float32Array ( 2 ) ;
if ( this . inputs ) {
for ( var i = 0 , l = this . inputs . length ; i < l ; ++ i ) {
var input = this . inputs [ i ] ;
this . getConnectionPos ( true , i , link _pos ) ;
if (
isInsideRectangle (
x ,
y ,
link _pos [ 0 ] - 10 ,
link _pos [ 1 ] - 5 ,
20 ,
10
)
) {
return { input : input , slot : i , link _pos : link _pos } ;
}
}
}
if ( this . outputs ) {
for ( var i = 0 , l = this . outputs . length ; i < l ; ++ i ) {
var output = this . outputs [ i ] ;
this . getConnectionPos ( false , i , link _pos ) ;
if (
isInsideRectangle (
x ,
y ,
link _pos [ 0 ] - 10 ,
link _pos [ 1 ] - 5 ,
20 ,
10
)
) {
return { output : output , slot : i , link _pos : link _pos } ;
}
}
}
return null ;
} ;
/ * *
* returns the input slot with a given name ( used for dynamic slots ) , - 1 if not found
* @ method findInputSlot
* @ param { string } name the name of the slot
* @ param { boolean } returnObj if the obj itself wanted
* @ return { number _or _object } the slot ( - 1 if not found )
* /
LGraphNode . prototype . findInputSlot = function ( name , returnObj ) {
if ( ! this . inputs ) {
return - 1 ;
}
for ( var i = 0 , l = this . inputs . length ; i < l ; ++ i ) {
if ( name == this . inputs [ i ] . name ) {
return ! returnObj ? i : this . inputs [ i ] ;
}
}
return - 1 ;
} ;
/ * *
* returns the output slot with a given name ( used for dynamic slots ) , - 1 if not found
* @ method findOutputSlot
* @ param { string } name the name of the slot
* @ param { boolean } returnObj if the obj itself wanted
* @ return { number _or _object } the slot ( - 1 if not found )
* /
LGraphNode . prototype . findOutputSlot = function ( name , returnObj ) {
returnObj = returnObj || false ;
if ( ! this . outputs ) {
return - 1 ;
}
for ( var i = 0 , l = this . outputs . length ; i < l ; ++ i ) {
if ( name == this . outputs [ i ] . name ) {
return ! returnObj ? i : this . outputs [ i ] ;
}
}
return - 1 ;
} ;
// TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options
/ * *
* returns the first free input slot
* @ method findInputSlotFree
* @ param { object } options
* @ return { number _or _object } the slot ( - 1 if not found )
* /
LGraphNode . prototype . findInputSlotFree = function ( optsIn ) {
var optsIn = optsIn || { } ;
var optsDef = { returnObj : false
, typesNotAccepted : [ ]
} ;
var opts = Object . assign ( optsDef , optsIn ) ;
if ( ! this . inputs ) {
return - 1 ;
}
for ( var i = 0 , l = this . inputs . length ; i < l ; ++ i ) {
if ( this . inputs [ i ] . link && this . inputs [ i ] . link != null ) {
continue ;
}
if ( opts . typesNotAccepted && opts . typesNotAccepted . includes && opts . typesNotAccepted . includes ( this . inputs [ i ] . type ) ) {
continue ;
}
return ! opts . returnObj ? i : this . inputs [ i ] ;
}
return - 1 ;
} ;
/ * *
* returns the first output slot free
* @ method findOutputSlotFree
* @ param { object } options
* @ return { number _or _object } the slot ( - 1 if not found )
* /
LGraphNode . prototype . findOutputSlotFree = function ( optsIn ) {
var optsIn = optsIn || { } ;
var optsDef = { returnObj : false
, typesNotAccepted : [ ]
} ;
var opts = Object . assign ( optsDef , optsIn ) ;
if ( ! this . outputs ) {
return - 1 ;
}
for ( var i = 0 , l = this . outputs . length ; i < l ; ++ i ) {
if ( this . outputs [ i ] . links && this . outputs [ i ] . links != null ) {
continue ;
}
if ( opts . typesNotAccepted && opts . typesNotAccepted . includes && opts . typesNotAccepted . includes ( this . outputs [ i ] . type ) ) {
continue ;
}
return ! opts . returnObj ? i : this . outputs [ i ] ;
}
return - 1 ;
} ;
/ * *
* findSlotByType for INPUTS
* /
LGraphNode . prototype . findInputSlotByType = function ( type , returnObj , preferFreeSlot , doNotUseOccupied ) {
return this . findSlotByType ( true , type , returnObj , preferFreeSlot , doNotUseOccupied ) ;
} ;
/ * *
* findSlotByType for OUTPUTS
* /
LGraphNode . prototype . findOutputSlotByType = function ( type , returnObj , preferFreeSlot , doNotUseOccupied ) {
return this . findSlotByType ( false , type , returnObj , preferFreeSlot , doNotUseOccupied ) ;
} ;
/ * *
* returns the output ( or input ) slot with a given type , - 1 if not found
* @ method findSlotByType
* @ param { boolean } input uise inputs instead of outputs
* @ param { string } type the type of the slot
* @ param { boolean } returnObj if the obj itself wanted
* @ param { boolean } preferFreeSlot if we want a free slot ( if not found , will return the first of the type anyway )
* @ return { number _or _object } the slot ( - 1 if not found )
* /
LGraphNode . prototype . findSlotByType = function ( input , type , returnObj , preferFreeSlot , doNotUseOccupied ) {
input = input || false ;
returnObj = returnObj || false ;
preferFreeSlot = preferFreeSlot || false ;
doNotUseOccupied = doNotUseOccupied || false ;
var aSlots = input ? this . inputs : this . outputs ;
if ( ! aSlots ) {
return - 1 ;
}
// !! empty string type is considered 0, * !!
if ( type == "" || type == "*" ) type = 0 ;
for ( var i = 0 , l = aSlots . length ; i < l ; ++ i ) {
var tFound = false ;
var aSource = ( type + "" ) . toLowerCase ( ) . split ( "," ) ;
var aDest = aSlots [ i ] . type == "0" || aSlots [ i ] . type == "*" ? "0" : aSlots [ i ] . type ;
aDest = ( aDest + "" ) . toLowerCase ( ) . split ( "," ) ;
for ( sI = 0 ; sI < aSource . length ; sI ++ ) {
for ( dI = 0 ; dI < aDest . length ; dI ++ ) {
if ( aSource [ sI ] == "_event_" ) aSource [ sI ] = LiteGraph . EVENT ;
if ( aDest [ sI ] == "_event_" ) aDest [ sI ] = LiteGraph . EVENT ;
if ( aSource [ sI ] == "*" ) aSource [ sI ] = 0 ;
if ( aDest [ sI ] == "*" ) aDest [ sI ] = 0 ;
if ( aSource [ sI ] == aDest [ dI ] ) {
if ( preferFreeSlot && aSlots [ i ] . links && aSlots [ i ] . links !== null ) continue ;
return ! returnObj ? i : aSlots [ i ] ;
}
}
}
}
// if didnt find some, stop checking for free slots
if ( preferFreeSlot && ! doNotUseOccupied ) {
for ( var i = 0 , l = aSlots . length ; i < l ; ++ i ) {
var tFound = false ;
var aSource = ( type + "" ) . toLowerCase ( ) . split ( "," ) ;
var aDest = aSlots [ i ] . type == "0" || aSlots [ i ] . type == "*" ? "0" : aSlots [ i ] . type ;
aDest = ( aDest + "" ) . toLowerCase ( ) . split ( "," ) ;
for ( sI = 0 ; sI < aSource . length ; sI ++ ) {
for ( dI = 0 ; dI < aDest . length ; dI ++ ) {
if ( aSource [ sI ] == "*" ) aSource [ sI ] = 0 ;
if ( aDest [ sI ] == "*" ) aDest [ sI ] = 0 ;
if ( aSource [ sI ] == aDest [ dI ] ) {
return ! returnObj ? i : aSlots [ i ] ;
}
}
}
}
}
return - 1 ;
} ;
/ * *
* connect this node output to the input of another node BY TYPE
* @ method connectByType
* @ param { number _or _string } slot ( could be the number of the slot or the string with the name of the slot )
* @ param { LGraphNode } node the target node
* @ param { string } target _type the input slot type of the target node
* @ return { Object } the link _info is created , otherwise null
* /
LGraphNode . prototype . connectByType = function ( slot , target _node , target _slotType , optsIn ) {
var optsIn = optsIn || { } ;
var optsDef = { createEventInCase : true
, firstFreeIfOutputGeneralInCase : true
, generalTypeInCase : true
} ;
var opts = Object . assign ( optsDef , optsIn ) ;
if ( target _node && target _node . constructor === Number ) {
target _node = this . graph . getNodeById ( target _node ) ;
}
target _slot = target _node . findInputSlotByType ( target _slotType , false , true ) ;
if ( target _slot >= 0 && target _slot !== null ) {
//console.debug("CONNbyTYPE type "+target_slotType+" for "+target_slot)
return this . connect ( slot , target _node , target _slot ) ;
} else {
//console.log("type "+target_slotType+" not found or not free?")
if ( opts . createEventInCase && target _slotType == LiteGraph . EVENT ) {
// WILL CREATE THE onTrigger IN SLOT
//console.debug("connect WILL CREATE THE onTrigger "+target_slotType+" to "+target_node);
return this . connect ( slot , target _node , - 1 ) ;
}
// connect to the first general output slot if not found a specific type and
if ( opts . generalTypeInCase ) {
var target _slot = target _node . findInputSlotByType ( 0 , false , true , true ) ;
//console.debug("connect TO a general type (*, 0), if not found the specific type ",target_slotType," to ",target_node,"RES_SLOT:",target_slot);
if ( target _slot >= 0 ) {
return this . connect ( slot , target _node , target _slot ) ;
}
}
// connect to the first free input slot if not found a specific type and this output is general
if ( opts . firstFreeIfOutputGeneralInCase && ( target _slotType == 0 || target _slotType == "*" || target _slotType == "" ) ) {
var target _slot = target _node . findInputSlotFree ( { typesNotAccepted : [ LiteGraph . EVENT ] } ) ;
//console.debug("connect TO TheFirstFREE ",target_slotType," to ",target_node,"RES_SLOT:",target_slot);
if ( target _slot >= 0 ) {
return this . connect ( slot , target _node , target _slot ) ;
}
}
console . debug ( "no way to connect type: " , target _slotType , " to targetNODE " , target _node ) ;
//TODO filter
return null ;
}
}
/ * *
* connect this node input to the output of another node BY TYPE
* @ method connectByType
* @ param { number _or _string } slot ( could be the number of the slot or the string with the name of the slot )
* @ param { LGraphNode } node the target node
* @ param { string } target _type the output slot type of the target node
* @ return { Object } the link _info is created , otherwise null
* /
LGraphNode . prototype . connectByTypeOutput = function ( slot , source _node , source _slotType , optsIn ) {
var optsIn = optsIn || { } ;
var optsDef = { createEventInCase : true
, firstFreeIfInputGeneralInCase : true
, generalTypeInCase : true
} ;
var opts = Object . assign ( optsDef , optsIn ) ;
if ( source _node && source _node . constructor === Number ) {
source _node = this . graph . getNodeById ( source _node ) ;
}
source _slot = source _node . findOutputSlotByType ( source _slotType , false , true ) ;
if ( source _slot >= 0 && source _slot !== null ) {
//console.debug("CONNbyTYPE OUT! type "+source_slotType+" for "+source_slot)
return source _node . connect ( source _slot , this , slot ) ;
} else {
// connect to the first general output slot if not found a specific type and
if ( opts . generalTypeInCase ) {
var source _slot = source _node . findOutputSlotByType ( 0 , false , true , true ) ;
if ( source _slot >= 0 ) {
return source _node . connect ( source _slot , this , slot ) ;
}
}
if ( opts . createEventInCase && source _slotType == LiteGraph . EVENT ) {
// WILL CREATE THE onExecuted OUT SLOT
if ( LiteGraph . do _add _triggers _slots ) {
var source _slot = source _node . addOnExecutedOutput ( ) ;
return source _node . connect ( source _slot , this , slot ) ;
}
}
// connect to the first free output slot if not found a specific type and this input is general
if ( opts . firstFreeIfInputGeneralInCase && ( source _slotType == 0 || source _slotType == "*" || source _slotType == "" ) ) {
var source _slot = source _node . findOutputSlotFree ( { typesNotAccepted : [ LiteGraph . EVENT ] } ) ;
if ( source _slot >= 0 ) {
return source _node . connect ( source _slot , this , slot ) ;
}
}
console . debug ( "no way to connect byOUT type: " , source _slotType , " to sourceNODE " , source _node ) ;
//TODO filter
//console.log("type OUT! "+source_slotType+" not found or not free?")
return null ;
}
}
/ * *
* connect this node output to the input of another node
* @ method connect
* @ param { number _or _string } slot ( could be the number of the slot or the string with the name of the slot )
* @ param { LGraphNode } node the target node
* @ param { number _or _string } target _slot the input slot of the target node ( could be the number of the slot or the string with the name of the slot , or - 1 to connect a trigger )
* @ return { Object } the link _info is created , otherwise null
* /
LGraphNode . prototype . connect = function ( slot , target _node , target _slot ) {
target _slot = target _slot || 0 ;
if ( ! this . graph ) {
//could be connected before adding it to a graph
console . log (
"Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them."
) ; //due to link ids being associated with graphs
return null ;
}
//seek for the output slot
if ( slot . constructor === String ) {
slot = this . findOutputSlot ( slot ) ;
if ( slot == - 1 ) {
if ( LiteGraph . debug ) {
console . log ( "Connect: Error, no slot of name " + slot ) ;
}
return null ;
}
} else if ( ! this . outputs || slot >= this . outputs . length ) {
if ( LiteGraph . debug ) {
console . log ( "Connect: Error, slot number not found" ) ;
}
return null ;
}
if ( target _node && target _node . constructor === Number ) {
target _node = this . graph . getNodeById ( target _node ) ;
}
if ( ! target _node ) {
throw "target node is null" ;
}
//avoid loopback
if ( target _node == this ) {
return null ;
}
//you can specify the slot by name
if ( target _slot . constructor === String ) {
target _slot = target _node . findInputSlot ( target _slot ) ;
if ( target _slot == - 1 ) {
if ( LiteGraph . debug ) {
console . log (
"Connect: Error, no slot of name " + target _slot
) ;
}
return null ;
}
} else if ( target _slot === LiteGraph . EVENT ) {
if ( LiteGraph . do _add _triggers _slots ) {
//search for first slot with event? :: NO this is done outside
//console.log("Connect: Creating triggerEvent");
// force mode
target _node . changeMode ( LiteGraph . ON _TRIGGER ) ;
target _slot = target _node . findInputSlot ( "onTrigger" ) ;
} else {
return null ; // -- break --
}
} else if (
! target _node . inputs ||
target _slot >= target _node . inputs . length
) {
if ( LiteGraph . debug ) {
console . log ( "Connect: Error, slot number not found" ) ;
}
return null ;
}
var changed = false ;
var input = target _node . inputs [ target _slot ] ;
var link _info = null ;
var output = this . outputs [ slot ] ;
if ( ! this . outputs [ slot ] ) {
/ * c o n s o l e . d e b u g ( " I n v a l i d s l o t p a s s e d : " + s l o t ) ;
console . debug ( this . outputs ) ; * /
return null ;
}
// allow target node to change slot
if ( target _node . onBeforeConnectInput ) {
// This way node can choose another slot (or make a new one?)
target _slot = target _node . onBeforeConnectInput ( target _slot ) ; //callback
}
//check target_slot and check connection types
if ( target _slot === false || target _slot === null || ! LiteGraph . isValidConnection ( output . type , input . type ) )
{
this . setDirtyCanvas ( false , true ) ;
if ( changed )
this . graph . connectionChange ( this , link _info ) ;
return null ;
} else {
//console.debug("valid connection",output.type, input.type);
}
//allows nodes to block connection, callback
if ( target _node . onConnectInput ) {
if ( target _node . onConnectInput ( target _slot , output . type , output , this , slot ) === false ) {
return null ;
}
}
if ( this . onConnectOutput ) { // callback
if ( this . onConnectOutput ( slot , input . type , input , target _node , target _slot ) === false ) {
return null ;
}
}
//if there is something already plugged there, disconnect
if ( target _node . inputs [ target _slot ] && target _node . inputs [ target _slot ] . link != null ) {
this . graph . beforeChange ( ) ;
target _node . disconnectInput ( target _slot , { doProcessChange : false } ) ;
changed = true ;
}
if ( output . links !== null && output . links . length ) {
switch ( output . type ) {
case LiteGraph . EVENT :
if ( ! LiteGraph . allow _multi _output _for _events ) {
this . graph . beforeChange ( ) ;
this . disconnectOutput ( slot , false , { doProcessChange : false } ) ; // Input(target_slot, {doProcessChange: false});
changed = true ;
}
break ;
default :
break ;
}
}
//create link class
link _info = new LLink (
++ this . graph . last _link _id ,
input . type || output . type ,
this . id ,
slot ,
target _node . id ,
target _slot
) ;
//add to graph links list
this . graph . links [ link _info . id ] = link _info ;
//connect in output
if ( output . links == null ) {
output . links = [ ] ;
}
output . links . push ( link _info . id ) ;
//connect in input
target _node . inputs [ target _slot ] . link = link _info . id ;
if ( this . graph ) {
this . graph . _version ++ ;
}
if ( this . onConnectionsChange ) {
this . onConnectionsChange (
LiteGraph . OUTPUT ,
slot ,
true ,
link _info ,
output
) ;
} //link_info has been created now, so its updated
if ( target _node . onConnectionsChange ) {
target _node . onConnectionsChange (
LiteGraph . INPUT ,
target _slot ,
true ,
link _info ,
input
) ;
}
if ( this . graph && this . graph . onNodeConnectionChange ) {
this . graph . onNodeConnectionChange (
LiteGraph . INPUT ,
target _node ,
target _slot ,
this ,
slot
) ;
this . graph . onNodeConnectionChange (
LiteGraph . OUTPUT ,
this ,
slot ,
target _node ,
target _slot
) ;
}
this . setDirtyCanvas ( false , true ) ;
this . graph . afterChange ( ) ;
this . graph . connectionChange ( this , link _info ) ;
return link _info ;
} ;
/ * *
* disconnect one output to an specific node
* @ method disconnectOutput
* @ param { number _or _string } slot ( could be the number of the slot or the string with the name of the slot )
* @ param { LGraphNode } target _node the target node to which this slot is connected [ Optional , if not target _node is specified all nodes will be disconnected ]
* @ return { boolean } if it was disconnected successfully
* /
LGraphNode . prototype . disconnectOutput = function ( slot , target _node ) {
if ( slot . constructor === String ) {
slot = this . findOutputSlot ( slot ) ;
if ( slot == - 1 ) {
if ( LiteGraph . debug ) {
console . log ( "Connect: Error, no slot of name " + slot ) ;
}
return false ;
}
} else if ( ! this . outputs || slot >= this . outputs . length ) {
if ( LiteGraph . debug ) {
console . log ( "Connect: Error, slot number not found" ) ;
}
return false ;
}
//get output slot
var output = this . outputs [ slot ] ;
if ( ! output || ! output . links || output . links . length == 0 ) {
return false ;
}
//one of the output links in this slot
if ( target _node ) {
if ( target _node . constructor === Number ) {
target _node = this . graph . getNodeById ( target _node ) ;
}
if ( ! target _node ) {
throw "Target Node not found" ;
}
for ( var i = 0 , l = output . links . length ; i < l ; i ++ ) {
var link _id = output . links [ i ] ;
var link _info = this . graph . links [ link _id ] ;
//is the link we are searching for...
if ( link _info . target _id == target _node . id ) {
output . links . splice ( i , 1 ) ; //remove here
var input = target _node . inputs [ link _info . target _slot ] ;
input . link = null ; //remove there
delete this . graph . links [ link _id ] ; //remove the link from the links pool
if ( this . graph ) {
this . graph . _version ++ ;
}
if ( target _node . onConnectionsChange ) {
target _node . onConnectionsChange (
LiteGraph . INPUT ,
link _info . target _slot ,
false ,
link _info ,
input
) ;
} //link_info hasn't been modified so its ok
if ( this . onConnectionsChange ) {
this . onConnectionsChange (
LiteGraph . OUTPUT ,
slot ,
false ,
link _info ,
output
) ;
}
if ( this . graph && this . graph . onNodeConnectionChange ) {
this . graph . onNodeConnectionChange (
LiteGraph . OUTPUT ,
this ,
slot
) ;
}
if ( this . graph && this . graph . onNodeConnectionChange ) {
this . graph . onNodeConnectionChange (
LiteGraph . OUTPUT ,
this ,
slot
) ;
this . graph . onNodeConnectionChange (
LiteGraph . INPUT ,
target _node ,
link _info . target _slot
) ;
}
break ;
}
}
} //all the links in this output slot
else {
for ( var i = 0 , l = output . links . length ; i < l ; i ++ ) {
var link _id = output . links [ i ] ;
var link _info = this . graph . links [ link _id ] ;
if ( ! link _info ) {
//bug: it happens sometimes
continue ;
}
var target _node = this . graph . getNodeById ( link _info . target _id ) ;
var input = null ;
if ( this . graph ) {
this . graph . _version ++ ;
}
if ( target _node ) {
input = target _node . inputs [ link _info . target _slot ] ;
input . link = null ; //remove other side link
if ( target _node . onConnectionsChange ) {
target _node . onConnectionsChange (
LiteGraph . INPUT ,
link _info . target _slot ,
false ,
link _info ,
input
) ;
} //link_info hasn't been modified so its ok
if ( this . graph && this . graph . onNodeConnectionChange ) {
this . graph . onNodeConnectionChange (
LiteGraph . INPUT ,
target _node ,
link _info . target _slot
) ;
}
}
delete this . graph . links [ link _id ] ; //remove the link from the links pool
if ( this . onConnectionsChange ) {
this . onConnectionsChange (
LiteGraph . OUTPUT ,
slot ,
false ,
link _info ,
output
) ;
}
if ( this . graph && this . graph . onNodeConnectionChange ) {
this . graph . onNodeConnectionChange (
LiteGraph . OUTPUT ,
this ,
slot
) ;
this . graph . onNodeConnectionChange (
LiteGraph . INPUT ,
target _node ,
link _info . target _slot
) ;
}
}
output . links = null ;
}
this . setDirtyCanvas ( false , true ) ;
this . graph . connectionChange ( this ) ;
return true ;
} ;
/ * *
* disconnect one input
* @ method disconnectInput
* @ param { number _or _string } slot ( could be the number of the slot or the string with the name of the slot )
* @ return { boolean } if it was disconnected successfully
* /
LGraphNode . prototype . disconnectInput = function ( slot ) {
//seek for the output slot
if ( slot . constructor === String ) {
slot = this . findInputSlot ( slot ) ;
if ( slot == - 1 ) {
if ( LiteGraph . debug ) {
console . log ( "Connect: Error, no slot of name " + slot ) ;
}
return false ;
}
} else if ( ! this . inputs || slot >= this . inputs . length ) {
if ( LiteGraph . debug ) {
console . log ( "Connect: Error, slot number not found" ) ;
}
return false ;
}
var input = this . inputs [ slot ] ;
if ( ! input ) {
return false ;
}
var link _id = this . inputs [ slot ] . link ;
if ( link _id != null )
{
this . inputs [ slot ] . link = null ;
//remove other side
var link _info = this . graph . links [ link _id ] ;
if ( link _info ) {
var target _node = this . graph . getNodeById ( link _info . origin _id ) ;
if ( ! target _node ) {
return false ;
}
var output = target _node . outputs [ link _info . origin _slot ] ;
if ( ! output || ! output . links || output . links . length == 0 ) {
return false ;
}
//search in the inputs list for this link
for ( var i = 0 , l = output . links . length ; i < l ; i ++ ) {
if ( output . links [ i ] == link _id ) {
output . links . splice ( i , 1 ) ;
break ;
}
}
delete this . graph . links [ link _id ] ; //remove from the pool
if ( this . graph ) {
this . graph . _version ++ ;
}
if ( this . onConnectionsChange ) {
this . onConnectionsChange (
LiteGraph . INPUT ,
slot ,
false ,
link _info ,
input
) ;
}
if ( target _node . onConnectionsChange ) {
target _node . onConnectionsChange (
LiteGraph . OUTPUT ,
i ,
false ,
link _info ,
output
) ;
}
if ( this . graph && this . graph . onNodeConnectionChange ) {
this . graph . onNodeConnectionChange (
LiteGraph . OUTPUT ,
target _node ,
i
) ;
this . graph . onNodeConnectionChange ( LiteGraph . INPUT , this , slot ) ;
}
}
} //link != null
this . setDirtyCanvas ( false , true ) ;
if ( this . graph )
this . graph . connectionChange ( this ) ;
return true ;
} ;
/ * *
* returns the center of a connection point in canvas coords
* @ method getConnectionPos
* @ param { boolean } is _input true if if a input slot , false if it is an output
* @ param { number _or _string } slot ( could be the number of the slot or the string with the name of the slot )
* @ param { vec2 } out [ optional ] a place to store the output , to free garbage
* @ return { [ x , y ] } the position
* * /
LGraphNode . prototype . getConnectionPos = function (
is _input ,
slot _number ,
out
) {
out = out || new Float32Array ( 2 ) ;
var num _slots = 0 ;
if ( is _input && this . inputs ) {
num _slots = this . inputs . length ;
}
if ( ! is _input && this . outputs ) {
num _slots = this . outputs . length ;
}
var offset = LiteGraph . NODE _SLOT _HEIGHT * 0.5 ;
if ( this . flags . collapsed ) {
var w = this . _collapsed _width || LiteGraph . NODE _COLLAPSED _WIDTH ;
if ( this . horizontal ) {
out [ 0 ] = this . pos [ 0 ] + w * 0.5 ;
if ( is _input ) {
out [ 1 ] = this . pos [ 1 ] - LiteGraph . NODE _TITLE _HEIGHT ;
} else {
out [ 1 ] = this . pos [ 1 ] ;
}
} else {
if ( is _input ) {
out [ 0 ] = this . pos [ 0 ] ;
} else {
out [ 0 ] = this . pos [ 0 ] + w ;
}
out [ 1 ] = this . pos [ 1 ] - LiteGraph . NODE _TITLE _HEIGHT * 0.5 ;
}
return out ;
}
//weird feature that never got finished
if ( is _input && slot _number == - 1 ) {
out [ 0 ] = this . pos [ 0 ] + LiteGraph . NODE _TITLE _HEIGHT * 0.5 ;
out [ 1 ] = this . pos [ 1 ] + LiteGraph . NODE _TITLE _HEIGHT * 0.5 ;
return out ;
}
//hard-coded pos
if (
is _input &&
num _slots > slot _number &&
this . inputs [ slot _number ] . pos
) {
out [ 0 ] = this . pos [ 0 ] + this . inputs [ slot _number ] . pos [ 0 ] ;
out [ 1 ] = this . pos [ 1 ] + this . inputs [ slot _number ] . pos [ 1 ] ;
return out ;
} else if (
! is _input &&
num _slots > slot _number &&
this . outputs [ slot _number ] . pos
) {
out [ 0 ] = this . pos [ 0 ] + this . outputs [ slot _number ] . pos [ 0 ] ;
out [ 1 ] = this . pos [ 1 ] + this . outputs [ slot _number ] . pos [ 1 ] ;
return out ;
}
//horizontal distributed slots
if ( this . horizontal ) {
out [ 0 ] =
this . pos [ 0 ] + ( slot _number + 0.5 ) * ( this . size [ 0 ] / num _slots ) ;
if ( is _input ) {
out [ 1 ] = this . pos [ 1 ] - LiteGraph . NODE _TITLE _HEIGHT ;
} else {
out [ 1 ] = this . pos [ 1 ] + this . size [ 1 ] ;
}
return out ;
}
//default vertical slots
if ( is _input ) {
out [ 0 ] = this . pos [ 0 ] + offset ;
} else {
out [ 0 ] = this . pos [ 0 ] + this . size [ 0 ] + 1 - offset ;
}
out [ 1 ] =
this . pos [ 1 ] +
( slot _number + 0.7 ) * LiteGraph . NODE _SLOT _HEIGHT +
( this . constructor . slot _start _y || 0 ) ;
return out ;
} ;
/* Force align to grid */
LGraphNode . prototype . alignToGrid = function ( ) {
this . pos [ 0 ] =
LiteGraph . CANVAS _GRID _SIZE *
Math . round ( this . pos [ 0 ] / LiteGraph . CANVAS _GRID _SIZE ) ;
this . pos [ 1 ] =
LiteGraph . CANVAS _GRID _SIZE *
Math . round ( this . pos [ 1 ] / LiteGraph . CANVAS _GRID _SIZE ) ;
} ;
/* Console output */
LGraphNode . prototype . trace = function ( msg ) {
if ( ! this . console ) {
this . console = [ ] ;
}
this . console . push ( msg ) ;
if ( this . console . length > LGraphNode . MAX _CONSOLE ) {
this . console . shift ( ) ;
}
if ( this . graph . onNodeTrace )
this . graph . onNodeTrace ( this , msg ) ;
} ;
/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */
LGraphNode . prototype . setDirtyCanvas = function (
dirty _foreground ,
dirty _background
) {
if ( ! this . graph ) {
return ;
}
this . graph . sendActionToCanvas ( "setDirty" , [
dirty _foreground ,
dirty _background
] ) ;
} ;
LGraphNode . prototype . loadImage = function ( url ) {
var img = new Image ( ) ;
img . src = LiteGraph . node _images _path + url ;
img . ready = false ;
var that = this ;
img . onload = function ( ) {
this . ready = true ;
that . setDirtyCanvas ( true ) ;
} ;
return img ;
} ;
//safe LGraphNode action execution (not sure if safe)
/ *
LGraphNode . prototype . executeAction = function ( action )
{
if ( action == "" ) return false ;
if ( action . indexOf ( ";" ) != - 1 || action . indexOf ( "}" ) != - 1 )
{
this . trace ( "Error: Action contains unsafe characters" ) ;
return false ;
}
var tokens = action . split ( "(" ) ;
var func _name = tokens [ 0 ] ;
if ( typeof ( this [ func _name ] ) != "function" )
{
this . trace ( "Error: Action not found on node: " + func _name ) ;
return false ;
}
var code = action ;
try
{
var _foo = eval ;
eval = null ;
( new Function ( "with(this) { " + code + "}" ) ) . call ( this ) ;
eval = _foo ;
}
catch ( err )
{
this . trace ( "Error executing action {" + action + "} :" + err ) ;
return false ;
}
return true ;
}
* /
/* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */
LGraphNode . prototype . captureInput = function ( v ) {
if ( ! this . graph || ! this . graph . list _of _graphcanvas ) {
return ;
}
var list = this . graph . list _of _graphcanvas ;
for ( var i = 0 ; i < list . length ; ++ i ) {
var c = list [ i ] ;
//releasing somebody elses capture?!
if ( ! v && c . node _capturing _input != this ) {
continue ;
}
//change
c . node _capturing _input = v ? this : null ;
}
} ;
/ * *
* Collapse the node to make it smaller on the canvas
* @ method collapse
* * /
LGraphNode . prototype . collapse = function ( force ) {
this . graph . _version ++ ;
if ( this . constructor . collapsable === false && ! force ) {
return ;
}
if ( ! this . flags . collapsed ) {
this . flags . collapsed = true ;
} else {
this . flags . collapsed = false ;
}
this . setDirtyCanvas ( true , true ) ;
} ;
/ * *
* Forces the node to do not move or realign on Z
* @ method pin
* * /
LGraphNode . prototype . pin = function ( v ) {
this . graph . _version ++ ;
if ( v === undefined ) {
this . flags . pinned = ! this . flags . pinned ;
} else {
this . flags . pinned = v ;
}
} ;
LGraphNode . prototype . localToScreen = function ( x , y , graphcanvas ) {
return [
( x + this . pos [ 0 ] ) * graphcanvas . scale + graphcanvas . offset [ 0 ] ,
( y + this . pos [ 1 ] ) * graphcanvas . scale + graphcanvas . offset [ 1 ]
] ;
} ;
function LGraphGroup ( title ) {
this . _ctor ( title ) ;
}
global . LGraphGroup = LiteGraph . LGraphGroup = LGraphGroup ;
LGraphGroup . prototype . _ctor = function ( title ) {
this . title = title || "Group" ;
this . font _size = 24 ;
this . color = LGraphCanvas . node _colors . pale _blue
? LGraphCanvas . node _colors . pale _blue . groupcolor
: "#AAA" ;
this . _bounding = new Float32Array ( [ 10 , 10 , 140 , 80 ] ) ;
this . _pos = this . _bounding . subarray ( 0 , 2 ) ;
this . _size = this . _bounding . subarray ( 2 , 4 ) ;
this . _nodes = [ ] ;
this . graph = null ;
Object . defineProperty ( this , "pos" , {
set : function ( v ) {
if ( ! v || v . length < 2 ) {
return ;
}
this . _pos [ 0 ] = v [ 0 ] ;
this . _pos [ 1 ] = v [ 1 ] ;
} ,
get : function ( ) {
return this . _pos ;
} ,
enumerable : true
} ) ;
Object . defineProperty ( this , "size" , {
set : function ( v ) {
if ( ! v || v . length < 2 ) {
return ;
}
this . _size [ 0 ] = Math . max ( 140 , v [ 0 ] ) ;
this . _size [ 1 ] = Math . max ( 80 , v [ 1 ] ) ;
} ,
get : function ( ) {
return this . _size ;
} ,
enumerable : true
} ) ;
} ;
LGraphGroup . prototype . configure = function ( o ) {
this . title = o . title ;
this . _bounding . set ( o . bounding ) ;
this . color = o . color ;
this . font = o . font ;
} ;
LGraphGroup . prototype . serialize = function ( ) {
var b = this . _bounding ;
return {
title : this . title ,
bounding : [
Math . round ( b [ 0 ] ) ,
Math . round ( b [ 1 ] ) ,
Math . round ( b [ 2 ] ) ,
Math . round ( b [ 3 ] )
] ,
color : this . color ,
font : this . font
} ;
} ;
LGraphGroup . prototype . move = function ( deltax , deltay , ignore _nodes ) {
this . _pos [ 0 ] += deltax ;
this . _pos [ 1 ] += deltay ;
if ( ignore _nodes ) {
return ;
}
for ( var i = 0 ; i < this . _nodes . length ; ++ i ) {
var node = this . _nodes [ i ] ;
node . pos [ 0 ] += deltax ;
node . pos [ 1 ] += deltay ;
}
} ;
LGraphGroup . prototype . recomputeInsideNodes = function ( ) {
this . _nodes . length = 0 ;
var nodes = this . graph . _nodes ;
var node _bounding = new Float32Array ( 4 ) ;
for ( var i = 0 ; i < nodes . length ; ++ i ) {
var node = nodes [ i ] ;
node . getBounding ( node _bounding ) ;
if ( ! overlapBounding ( this . _bounding , node _bounding ) ) {
continue ;
} //out of the visible area
this . _nodes . push ( node ) ;
}
} ;
LGraphGroup . prototype . isPointInside = LGraphNode . prototype . isPointInside ;
LGraphGroup . prototype . setDirtyCanvas = LGraphNode . prototype . setDirtyCanvas ;
//****************************************
//Scale and Offset
function DragAndScale ( element , skip _events ) {
this . offset = new Float32Array ( [ 0 , 0 ] ) ;
this . scale = 1 ;
this . max _scale = 10 ;
this . min _scale = 0.1 ;
this . onredraw = null ;
this . enabled = true ;
this . last _mouse = [ 0 , 0 ] ;
this . element = null ;
this . visible _area = new Float32Array ( 4 ) ;
if ( element ) {
this . element = element ;
if ( ! skip _events ) {
this . bindEvents ( element ) ;
}
}
}
LiteGraph . DragAndScale = DragAndScale ;
DragAndScale . prototype . bindEvents = function ( element ) {
this . last _mouse = new Float32Array ( 2 ) ;
this . _binded _mouse _callback = this . onMouse . bind ( this ) ;
LiteGraph . pointerListenerAdd ( element , "down" , this . _binded _mouse _callback ) ;
LiteGraph . pointerListenerAdd ( element , "move" , this . _binded _mouse _callback ) ;
LiteGraph . pointerListenerAdd ( element , "up" , this . _binded _mouse _callback ) ;
element . addEventListener (
"mousewheel" ,
this . _binded _mouse _callback ,
false
) ;
element . addEventListener ( "wheel" , this . _binded _mouse _callback , false ) ;
} ;
DragAndScale . prototype . computeVisibleArea = function ( viewport ) {
if ( ! this . element ) {
this . visible _area [ 0 ] = this . visible _area [ 1 ] = this . visible _area [ 2 ] = this . visible _area [ 3 ] = 0 ;
return ;
}
var width = this . element . width ;
var height = this . element . height ;
var startx = - this . offset [ 0 ] ;
var starty = - this . offset [ 1 ] ;
if ( viewport )
{
startx += viewport [ 0 ] / this . scale ;
starty += viewport [ 1 ] / this . scale ;
width = viewport [ 2 ] ;
height = viewport [ 3 ] ;
}
var endx = startx + width / this . scale ;
var endy = starty + height / this . scale ;
this . visible _area [ 0 ] = startx ;
this . visible _area [ 1 ] = starty ;
this . visible _area [ 2 ] = endx - startx ;
this . visible _area [ 3 ] = endy - starty ;
} ;
DragAndScale . prototype . onMouse = function ( e ) {
if ( ! this . enabled ) {
return ;
}
var canvas = this . element ;
var rect = canvas . getBoundingClientRect ( ) ;
var x = e . clientX - rect . left ;
var y = e . clientY - rect . top ;
e . canvasx = x ;
e . canvasy = y ;
e . dragging = this . dragging ;
var is _inside = ! this . viewport || ( this . viewport && x >= this . viewport [ 0 ] && x < ( this . viewport [ 0 ] + this . viewport [ 2 ] ) && y >= this . viewport [ 1 ] && y < ( this . viewport [ 1 ] + this . viewport [ 3 ] ) ) ;
//console.log("pointerevents: DragAndScale onMouse "+e.type+" "+is_inside);
var ignore = false ;
if ( this . onmouse ) {
ignore = this . onmouse ( e ) ;
}
if ( e . type == LiteGraph . pointerevents _method + "down" && is _inside ) {
this . dragging = true ;
LiteGraph . pointerListenerRemove ( canvas , "move" , this . _binded _mouse _callback ) ;
LiteGraph . pointerListenerAdd ( document , "move" , this . _binded _mouse _callback ) ;
LiteGraph . pointerListenerAdd ( document , "up" , this . _binded _mouse _callback ) ;
} else if ( e . type == LiteGraph . pointerevents _method + "move" ) {
if ( ! ignore ) {
var deltax = x - this . last _mouse [ 0 ] ;
var deltay = y - this . last _mouse [ 1 ] ;
if ( this . dragging ) {
this . mouseDrag ( deltax , deltay ) ;
}
}
} else if ( e . type == LiteGraph . pointerevents _method + "up" ) {
this . dragging = false ;
LiteGraph . pointerListenerRemove ( document , "move" , this . _binded _mouse _callback ) ;
LiteGraph . pointerListenerRemove ( document , "up" , this . _binded _mouse _callback ) ;
LiteGraph . pointerListenerAdd ( canvas , "move" , this . _binded _mouse _callback ) ;
} else if ( is _inside &&
( e . type == "mousewheel" ||
e . type == "wheel" ||
e . type == "DOMMouseScroll" )
) {
e . eventType = "mousewheel" ;
if ( e . type == "wheel" ) {
e . wheel = - e . deltaY ;
} else {
e . wheel =
e . wheelDeltaY != null ? e . wheelDeltaY : e . detail * - 60 ;
}
//from stack overflow
e . delta = e . wheelDelta
? e . wheelDelta / 40
: e . deltaY
? - e . deltaY / 3
: 0 ;
this . changeDeltaScale ( 1.0 + e . delta * 0.05 ) ;
}
this . last _mouse [ 0 ] = x ;
this . last _mouse [ 1 ] = y ;
if ( is _inside )
{
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
return false ;
}
} ;
DragAndScale . prototype . toCanvasContext = function ( ctx ) {
ctx . scale ( this . scale , this . scale ) ;
ctx . translate ( this . offset [ 0 ] , this . offset [ 1 ] ) ;
} ;
DragAndScale . prototype . convertOffsetToCanvas = function ( pos ) {
//return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]];
return [
( pos [ 0 ] + this . offset [ 0 ] ) * this . scale ,
( pos [ 1 ] + this . offset [ 1 ] ) * this . scale
] ;
} ;
DragAndScale . prototype . convertCanvasToOffset = function ( pos , out ) {
out = out || [ 0 , 0 ] ;
out [ 0 ] = pos [ 0 ] / this . scale - this . offset [ 0 ] ;
out [ 1 ] = pos [ 1 ] / this . scale - this . offset [ 1 ] ;
return out ;
} ;
DragAndScale . prototype . mouseDrag = function ( x , y ) {
this . offset [ 0 ] += x / this . scale ;
this . offset [ 1 ] += y / this . scale ;
if ( this . onredraw ) {
this . onredraw ( this ) ;
}
} ;
DragAndScale . prototype . changeScale = function ( value , zooming _center ) {
if ( value < this . min _scale ) {
value = this . min _scale ;
} else if ( value > this . max _scale ) {
value = this . max _scale ;
}
if ( value == this . scale ) {
return ;
}
if ( ! this . element ) {
return ;
}
var rect = this . element . getBoundingClientRect ( ) ;
if ( ! rect ) {
return ;
}
zooming _center = zooming _center || [
rect . width * 0.5 ,
rect . height * 0.5
] ;
var center = this . convertCanvasToOffset ( zooming _center ) ;
this . scale = value ;
if ( Math . abs ( this . scale - 1 ) < 0.01 ) {
this . scale = 1 ;
}
var new _center = this . convertCanvasToOffset ( zooming _center ) ;
var delta _offset = [
new _center [ 0 ] - center [ 0 ] ,
new _center [ 1 ] - center [ 1 ]
] ;
this . offset [ 0 ] += delta _offset [ 0 ] ;
this . offset [ 1 ] += delta _offset [ 1 ] ;
if ( this . onredraw ) {
this . onredraw ( this ) ;
}
} ;
DragAndScale . prototype . changeDeltaScale = function ( value , zooming _center ) {
this . changeScale ( this . scale * value , zooming _center ) ;
} ;
DragAndScale . prototype . reset = function ( ) {
this . scale = 1 ;
this . offset [ 0 ] = 0 ;
this . offset [ 1 ] = 0 ;
} ;
//*********************************************************************************
// LGraphCanvas: LGraph renderer CLASS
//*********************************************************************************
/ * *
* This class is in charge of rendering one graph inside a canvas . And provides all the interaction required .
* Valid callbacks are : onNodeSelected , onNodeDeselected , onShowNodePanel , onNodeDblClicked
*
* @ class LGraphCanvas
* @ constructor
* @ param { HTMLCanvas } canvas the canvas where you want to render ( it accepts a selector in string format or the canvas element itself )
* @ param { LGraph } graph [ optional ]
* @ param { Object } options [ optional ] { skip _rendering , autoresize , viewport }
* /
function LGraphCanvas ( canvas , graph , options ) {
this . options = options = options || { } ;
//if(graph === undefined)
// throw ("No graph assigned");
this . background _image = LGraphCanvas . DEFAULT _BACKGROUND _IMAGE ;
if ( canvas && canvas . constructor === String ) {
canvas = document . querySelector ( canvas ) ;
}
this . ds = new DragAndScale ( ) ;
this . zoom _modify _alpha = true ; //otherwise it generates ugly patterns when scaling down too much
this . title _text _font = "" + LiteGraph . NODE _TEXT _SIZE + "px Arial" ;
this . inner _text _font =
"normal " + LiteGraph . NODE _SUBTEXT _SIZE + "px Arial" ;
this . node _title _color = LiteGraph . NODE _TITLE _COLOR ;
this . default _link _color = LiteGraph . LINK _COLOR ;
this . default _connection _color = {
input _off : "#778" ,
input _on : "#7F7" , //"#BBD"
output _off : "#778" ,
output _on : "#7F7" //"#BBD"
} ;
this . default _connection _color _byType = {
/ * n u m b e r : " # 7 F 7 " ,
string : "#77F" ,
boolean : "#F77" , * /
}
this . default _connection _color _byTypeOff = {
/ * n u m b e r : " # 4 7 4 " ,
string : "#447" ,
boolean : "#744" , * /
} ;
this . highquality _render = true ;
this . use _gradients = false ; //set to true to render titlebar with gradients
this . editor _alpha = 1 ; //used for transition
this . pause _rendering = false ;
this . clear _background = true ;
this . read _only = false ; //if set to true users cannot modify the graph
this . render _only _selected = true ;
this . live _mode = false ;
this . show _info = true ;
this . allow _dragcanvas = true ;
this . allow _dragnodes = true ;
this . allow _interaction = true ; //allow to control widgets, buttons, collapse, etc
2023-04-07 19:11:00 +00:00
this . multi _select = false ; //allow selecting multi nodes without pressing extra keys
2023-01-03 06:53:32 +00:00
this . allow _searchbox = true ;
this . allow _reconnect _links = true ; //allows to change a connection with having to redo it again
this . align _to _grid = false ; //snap to grid
this . drag _mode = false ;
this . dragging _rectangle = null ;
this . filter = null ; //allows to filter to only accept some type of nodes in a graph
this . set _canvas _dirty _on _mouse _event = true ; //forces to redraw the canvas if the mouse does anything
this . always _render _background = false ;
this . render _shadows = true ;
this . render _canvas _border = true ;
this . render _connections _shadows = false ; //too much cpu
this . render _connections _border = true ;
this . render _curved _connections = false ;
this . render _connection _arrows = false ;
this . render _collapsed _slots = true ;
this . render _execution _order = false ;
this . render _title _colored = true ;
this . render _link _tooltip = true ;
this . links _render _mode = LiteGraph . SPLINE _LINK ;
this . mouse = [ 0 , 0 ] ; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle
this . graph _mouse = [ 0 , 0 ] ; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle
this . canvas _mouse = this . graph _mouse ; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD
//to personalize the search box
this . onSearchBox = null ;
this . onSearchBoxSelection = null ;
//callbacks
this . onMouse = null ;
this . onDrawBackground = null ; //to render background objects (behind nodes and connections) in the canvas affected by transform
this . onDrawForeground = null ; //to render foreground objects (above nodes and connections) in the canvas affected by transform
this . onDrawOverlay = null ; //to render foreground objects not affected by transform (for GUIs)
this . onDrawLinkTooltip = null ; //called when rendering a tooltip
this . onNodeMoved = null ; //called after moving a node
this . onSelectionChange = null ; //called if the selection changes
this . onConnectingChange = null ; //called before any link changes
this . onBeforeChange = null ; //called before modifying the graph
this . onAfterChange = null ; //called after modifying the graph
this . connections _width = 3 ;
this . round _radius = 8 ;
this . current _node = null ;
this . node _widget = null ; //used for widgets
this . over _link _center = null ;
this . last _mouse _position = [ 0 , 0 ] ;
this . visible _area = this . ds . visible _area ;
this . visible _links = [ ] ;
this . viewport = options . viewport || null ; //to constraint render area to a portion of the canvas
//link canvas and graph
if ( graph ) {
graph . attachCanvas ( this ) ;
}
this . setCanvas ( canvas , options . skip _events ) ;
this . clear ( ) ;
if ( ! options . skip _render ) {
this . startRendering ( ) ;
}
this . autoresize = options . autoresize ;
}
global . LGraphCanvas = LiteGraph . LGraphCanvas = LGraphCanvas ;
LGraphCanvas . DEFAULT _BACKGROUND _IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=" ;
LGraphCanvas . link _type _colors = {
"-1" : LiteGraph . EVENT _LINK _COLOR ,
number : "#AAA" ,
node : "#DCA"
} ;
LGraphCanvas . gradients = { } ; //cache of gradients
/ * *
* clears all the data inside
*
* @ method clear
* /
LGraphCanvas . prototype . clear = function ( ) {
this . frame = 0 ;
this . last _draw _time = 0 ;
this . render _time = 0 ;
this . fps = 0 ;
//this.scale = 1;
//this.offset = [0,0];
this . dragging _rectangle = null ;
this . selected _nodes = { } ;
this . selected _group = null ;
this . visible _nodes = [ ] ;
this . node _dragged = null ;
this . node _over = null ;
this . node _capturing _input = null ;
this . connecting _node = null ;
this . highlighted _links = { } ;
this . dragging _canvas = false ;
this . dirty _canvas = true ;
this . dirty _bgcanvas = true ;
this . dirty _area = null ;
this . node _in _panel = null ;
this . node _widget = null ;
this . last _mouse = [ 0 , 0 ] ;
this . last _mouseclick = 0 ;
this . pointer _is _down = false ;
this . pointer _is _double = false ;
this . visible _area . set ( [ 0 , 0 , 0 , 0 ] ) ;
if ( this . onClear ) {
this . onClear ( ) ;
}
} ;
/ * *
* assigns a graph , you can reassign graphs to the same canvas
*
* @ method setGraph
* @ param { LGraph } graph
* /
LGraphCanvas . prototype . setGraph = function ( graph , skip _clear ) {
if ( this . graph == graph ) {
return ;
}
if ( ! skip _clear ) {
this . clear ( ) ;
}
if ( ! graph && this . graph ) {
this . graph . detachCanvas ( this ) ;
return ;
}
graph . attachCanvas ( this ) ;
//remove the graph stack in case a subgraph was open
if ( this . _graph _stack )
this . _graph _stack = null ;
this . setDirty ( true , true ) ;
} ;
/ * *
* returns the top level graph ( in case there are subgraphs open on the canvas )
*
* @ method getTopGraph
* @ return { LGraph } graph
* /
LGraphCanvas . prototype . getTopGraph = function ( )
{
if ( this . _graph _stack . length )
return this . _graph _stack [ 0 ] ;
return this . graph ;
}
/ * *
* opens a graph contained inside a node in the current graph
*
* @ method openSubgraph
* @ param { LGraph } graph
* /
LGraphCanvas . prototype . openSubgraph = function ( graph ) {
if ( ! graph ) {
throw "graph cannot be null" ;
}
if ( this . graph == graph ) {
throw "graph cannot be the same" ;
}
this . clear ( ) ;
if ( this . graph ) {
if ( ! this . _graph _stack ) {
this . _graph _stack = [ ] ;
}
this . _graph _stack . push ( this . graph ) ;
}
graph . attachCanvas ( this ) ;
this . checkPanels ( ) ;
this . setDirty ( true , true ) ;
} ;
/ * *
* closes a subgraph contained inside a node
*
* @ method closeSubgraph
* @ param { LGraph } assigns a graph
* /
LGraphCanvas . prototype . closeSubgraph = function ( ) {
if ( ! this . _graph _stack || this . _graph _stack . length == 0 ) {
return ;
}
var subgraph _node = this . graph . _subgraph _node ;
var graph = this . _graph _stack . pop ( ) ;
this . selected _nodes = { } ;
this . highlighted _links = { } ;
graph . attachCanvas ( this ) ;
this . setDirty ( true , true ) ;
if ( subgraph _node ) {
this . centerOnNode ( subgraph _node ) ;
this . selectNodes ( [ subgraph _node ] ) ;
}
// when close sub graph back to offset [0, 0] scale 1
this . ds . offset = [ 0 , 0 ]
this . ds . scale = 1
} ;
/ * *
2023-04-07 19:11:00 +00:00
* returns the visually active graph ( in case there are more in the stack )
2023-01-03 06:53:32 +00:00
* @ method getCurrentGraph
* @ return { LGraph } the active graph
* /
LGraphCanvas . prototype . getCurrentGraph = function ( ) {
return this . graph ;
} ;
/ * *
* assigns a canvas
*
* @ method setCanvas
* @ param { Canvas } assigns a canvas ( also accepts the ID of the element ( not a selector )
* /
LGraphCanvas . prototype . setCanvas = function ( canvas , skip _events ) {
var that = this ;
if ( canvas ) {
if ( canvas . constructor === String ) {
canvas = document . getElementById ( canvas ) ;
if ( ! canvas ) {
throw "Error creating LiteGraph canvas: Canvas not found" ;
}
}
}
if ( canvas === this . canvas ) {
return ;
}
if ( ! canvas && this . canvas ) {
//maybe detach events from old_canvas
if ( ! skip _events ) {
this . unbindEvents ( ) ;
}
}
this . canvas = canvas ;
this . ds . element = canvas ;
if ( ! canvas ) {
return ;
}
//this.canvas.tabindex = "1000";
canvas . className += " lgraphcanvas" ;
canvas . data = this ;
canvas . tabindex = "1" ; //to allow key events
//bg canvas: used for non changing stuff
this . bgcanvas = null ;
if ( ! this . bgcanvas ) {
this . bgcanvas = document . createElement ( "canvas" ) ;
this . bgcanvas . width = this . canvas . width ;
this . bgcanvas . height = this . canvas . height ;
}
if ( canvas . getContext == null ) {
if ( canvas . localName != "canvas" ) {
throw "Element supplied for LGraphCanvas must be a <canvas> element, you passed a " +
canvas . localName ;
}
throw "This browser doesn't support Canvas" ;
}
var ctx = ( this . ctx = canvas . getContext ( "2d" ) ) ;
if ( ctx == null ) {
if ( ! canvas . webgl _enabled ) {
console . warn (
"This canvas seems to be WebGL, enabling WebGL renderer"
) ;
}
this . enableWebGL ( ) ;
}
//input: (move and up could be unbinded)
// why here? this._mousemove_callback = this.processMouseMove.bind(this);
// why here? this._mouseup_callback = this.processMouseUp.bind(this);
if ( ! skip _events ) {
this . bindEvents ( ) ;
}
} ;
//used in some events to capture them
LGraphCanvas . prototype . _doNothing = function doNothing ( e ) {
//console.log("pointerevents: _doNothing "+e.type);
e . preventDefault ( ) ;
return false ;
} ;
LGraphCanvas . prototype . _doReturnTrue = function doNothing ( e ) {
e . preventDefault ( ) ;
return true ;
} ;
/ * *
* binds mouse , keyboard , touch and drag events to the canvas
* @ method bindEvents
* * /
LGraphCanvas . prototype . bindEvents = function ( ) {
if ( this . _events _binded ) {
console . warn ( "LGraphCanvas: events already binded" ) ;
return ;
}
//console.log("pointerevents: bindEvents");
var canvas = this . canvas ;
var ref _window = this . getCanvasWindow ( ) ;
var document = ref _window . document ; //hack used when moving canvas between windows
this . _mousedown _callback = this . processMouseDown . bind ( this ) ;
this . _mousewheel _callback = this . processMouseWheel . bind ( this ) ;
// why mousemove and mouseup were not binded here?
this . _mousemove _callback = this . processMouseMove . bind ( this ) ;
this . _mouseup _callback = this . processMouseUp . bind ( this ) ;
//touch events -- TODO IMPLEMENT
//this._touch_callback = this.touchHandler.bind(this);
LiteGraph . pointerListenerAdd ( canvas , "down" , this . _mousedown _callback , true ) ; //down do not need to store the binded
canvas . addEventListener ( "mousewheel" , this . _mousewheel _callback , false ) ;
LiteGraph . pointerListenerAdd ( canvas , "up" , this . _mouseup _callback , true ) ; // CHECK: ??? binded or not
LiteGraph . pointerListenerAdd ( canvas , "move" , this . _mousemove _callback ) ;
canvas . addEventListener ( "contextmenu" , this . _doNothing ) ;
canvas . addEventListener (
"DOMMouseScroll" ,
this . _mousewheel _callback ,
false
) ;
//touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents
/ * i f ( ' t o u c h s t a r t ' i n d o c u m e n t . d o c u m e n t E l e m e n t )
{
canvas . addEventListener ( "touchstart" , this . _touch _callback , true ) ;
canvas . addEventListener ( "touchmove" , this . _touch _callback , true ) ;
canvas . addEventListener ( "touchend" , this . _touch _callback , true ) ;
canvas . addEventListener ( "touchcancel" , this . _touch _callback , true ) ;
} * /
//Keyboard ******************
this . _key _callback = this . processKey . bind ( this ) ;
canvas . addEventListener ( "keydown" , this . _key _callback , true ) ;
document . addEventListener ( "keyup" , this . _key _callback , true ) ; //in document, otherwise it doesn't fire keyup
//Dropping Stuff over nodes ************************************
this . _ondrop _callback = this . processDrop . bind ( this ) ;
canvas . addEventListener ( "dragover" , this . _doNothing , false ) ;
canvas . addEventListener ( "dragend" , this . _doNothing , false ) ;
canvas . addEventListener ( "drop" , this . _ondrop _callback , false ) ;
canvas . addEventListener ( "dragenter" , this . _doReturnTrue , false ) ;
this . _events _binded = true ;
} ;
/ * *
* unbinds mouse events from the canvas
* @ method unbindEvents
* * /
LGraphCanvas . prototype . unbindEvents = function ( ) {
if ( ! this . _events _binded ) {
console . warn ( "LGraphCanvas: no events binded" ) ;
return ;
}
//console.log("pointerevents: unbindEvents");
var ref _window = this . getCanvasWindow ( ) ;
var document = ref _window . document ;
LiteGraph . pointerListenerRemove ( this . canvas , "move" , this . _mousedown _callback ) ;
LiteGraph . pointerListenerRemove ( this . canvas , "up" , this . _mousedown _callback ) ;
LiteGraph . pointerListenerRemove ( this . canvas , "down" , this . _mousedown _callback ) ;
this . canvas . removeEventListener (
"mousewheel" ,
this . _mousewheel _callback
) ;
this . canvas . removeEventListener (
"DOMMouseScroll" ,
this . _mousewheel _callback
) ;
this . canvas . removeEventListener ( "keydown" , this . _key _callback ) ;
document . removeEventListener ( "keyup" , this . _key _callback ) ;
this . canvas . removeEventListener ( "contextmenu" , this . _doNothing ) ;
this . canvas . removeEventListener ( "drop" , this . _ondrop _callback ) ;
this . canvas . removeEventListener ( "dragenter" , this . _doReturnTrue ) ;
//touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents
/ * t h i s . c a n v a s . r e m o v e E v e n t L i s t e n e r ( " t o u c h s t a r t " , t h i s . _ t o u c h _ c a l l b a c k ) ;
this . canvas . removeEventListener ( "touchmove" , this . _touch _callback ) ;
this . canvas . removeEventListener ( "touchend" , this . _touch _callback ) ;
this . canvas . removeEventListener ( "touchcancel" , this . _touch _callback ) ; * /
this . _mousedown _callback = null ;
this . _mousewheel _callback = null ;
this . _key _callback = null ;
this . _ondrop _callback = null ;
this . _events _binded = false ;
} ;
LGraphCanvas . getFileExtension = function ( url ) {
var question = url . indexOf ( "?" ) ;
if ( question != - 1 ) {
url = url . substr ( 0 , question ) ;
}
var point = url . lastIndexOf ( "." ) ;
if ( point == - 1 ) {
return "" ;
}
return url . substr ( point + 1 ) . toLowerCase ( ) ;
} ;
/ * *
* this function allows to render the canvas using WebGL instead of Canvas2D
* this is useful if you plant to render 3 D objects inside your nodes , it uses litegl . js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL
* @ method enableWebGL
* * /
LGraphCanvas . prototype . enableWebGL = function ( ) {
if ( typeof GL === undefined ) {
throw "litegl.js must be included to use a WebGL canvas" ;
}
if ( typeof enableWebGLCanvas === undefined ) {
throw "webglCanvas.js must be included to use this feature" ;
}
this . gl = this . ctx = enableWebGLCanvas ( this . canvas ) ;
this . ctx . webgl = true ;
this . bgcanvas = this . canvas ;
this . bgctx = this . gl ;
this . canvas . webgl _enabled = true ;
/ *
GL . create ( { canvas : this . bgcanvas } ) ;
this . bgctx = enableWebGLCanvas ( this . bgcanvas ) ;
window . gl = this . gl ;
* /
} ;
/ * *
* marks as dirty the canvas , this way it will be rendered again
*
* @ class LGraphCanvas
* @ method setDirty
* @ param { bool } fgcanvas if the foreground canvas is dirty ( the one containing the nodes )
* @ param { bool } bgcanvas if the background canvas is dirty ( the one containing the wires )
* /
LGraphCanvas . prototype . setDirty = function ( fgcanvas , bgcanvas ) {
if ( fgcanvas ) {
this . dirty _canvas = true ;
}
if ( bgcanvas ) {
this . dirty _bgcanvas = true ;
}
} ;
/ * *
* Used to attach the canvas in a popup
*
* @ method getCanvasWindow
* @ return { window } returns the window where the canvas is attached ( the DOM root node )
* /
LGraphCanvas . prototype . getCanvasWindow = function ( ) {
if ( ! this . canvas ) {
return window ;
}
var doc = this . canvas . ownerDocument ;
return doc . defaultView || doc . parentWindow ;
} ;
/ * *
* starts rendering the content of the canvas when needed
*
* @ method startRendering
* /
LGraphCanvas . prototype . startRendering = function ( ) {
if ( this . is _rendering ) {
return ;
} //already rendering
this . is _rendering = true ;
renderFrame . call ( this ) ;
function renderFrame ( ) {
if ( ! this . pause _rendering ) {
this . draw ( ) ;
}
var window = this . getCanvasWindow ( ) ;
if ( this . is _rendering ) {
window . requestAnimationFrame ( renderFrame . bind ( this ) ) ;
}
}
} ;
/ * *
* stops rendering the content of the canvas ( to save resources )
*
* @ method stopRendering
* /
LGraphCanvas . prototype . stopRendering = function ( ) {
this . is _rendering = false ;
/ *
if ( this . rendering _timer _id )
{
clearInterval ( this . rendering _timer _id ) ;
this . rendering _timer _id = null ;
}
* /
} ;
/* LiteGraphCanvas input */
//used to block future mouse events (because of im gui)
LGraphCanvas . prototype . blockClick = function ( )
{
this . block _click = true ;
this . last _mouseclick = 0 ;
}
LGraphCanvas . prototype . processMouseDown = function ( e ) {
if ( this . set _canvas _dirty _on _mouse _event )
this . dirty _canvas = true ;
if ( ! this . graph ) {
return ;
}
this . adjustMouseEvent ( e ) ;
var ref _window = this . getCanvasWindow ( ) ;
var document = ref _window . document ;
LGraphCanvas . active _canvas = this ;
var that = this ;
var x = e . clientX ;
var y = e . clientY ;
//console.log(y,this.viewport);
//console.log("pointerevents: processMouseDown pointerId:"+e.pointerId+" which:"+e.which+" isPrimary:"+e.isPrimary+" :: x y "+x+" "+y);
this . ds . viewport = this . viewport ;
var is _inside = ! this . viewport || ( this . viewport && x >= this . viewport [ 0 ] && x < ( this . viewport [ 0 ] + this . viewport [ 2 ] ) && y >= this . viewport [ 1 ] && y < ( this . viewport [ 1 ] + this . viewport [ 3 ] ) ) ;
//move mouse move event to the window in case it drags outside of the canvas
if ( ! this . options . skip _events )
{
LiteGraph . pointerListenerRemove ( this . canvas , "move" , this . _mousemove _callback ) ;
LiteGraph . pointerListenerAdd ( ref _window . document , "move" , this . _mousemove _callback , true ) ; //catch for the entire window
LiteGraph . pointerListenerAdd ( ref _window . document , "up" , this . _mouseup _callback , true ) ;
}
if ( ! is _inside ) {
return ;
}
var node = this . graph . getNodeOnPos ( e . canvasX , e . canvasY , this . visible _nodes , 5 ) ;
var skip _dragging = false ;
var skip _action = false ;
var now = LiteGraph . getTime ( ) ;
var is _primary = ( e . isPrimary === undefined || ! e . isPrimary ) ;
2023-03-23 09:06:06 +00:00
var is _double _click = ( now - this . last _mouseclick < 300 ) ;
2023-01-03 06:53:32 +00:00
this . mouse [ 0 ] = e . clientX ;
this . mouse [ 1 ] = e . clientY ;
this . graph _mouse [ 0 ] = e . canvasX ;
this . graph _mouse [ 1 ] = e . canvasY ;
this . last _click _position = [ this . mouse [ 0 ] , this . mouse [ 1 ] ] ;
if ( this . pointer _is _down && is _primary ) {
this . pointer _is _double = true ;
//console.log("pointerevents: pointer_is_double start");
} else {
this . pointer _is _double = false ;
}
this . pointer _is _down = true ;
this . canvas . focus ( ) ;
LiteGraph . closeAllContextMenus ( ref _window ) ;
if ( this . onMouse )
{
if ( this . onMouse ( e ) == true )
return ;
}
//left button mouse / single finger
if ( e . which == 1 && ! this . pointer _is _double )
{
if ( e . ctrlKey )
{
this . dragging _rectangle = new Float32Array ( 4 ) ;
this . dragging _rectangle [ 0 ] = e . canvasX ;
this . dragging _rectangle [ 1 ] = e . canvasY ;
this . dragging _rectangle [ 2 ] = 1 ;
this . dragging _rectangle [ 3 ] = 1 ;
skip _action = true ;
}
// clone node ALT dragging
if ( LiteGraph . alt _drag _do _clone _nodes && e . altKey && node && this . allow _interaction && ! skip _action && ! this . read _only )
{
if ( cloned = node . clone ( ) ) {
cloned . pos [ 0 ] += 5 ;
cloned . pos [ 1 ] += 5 ;
this . graph . add ( cloned , false , { doCalcSize : false } ) ;
node = cloned ;
skip _action = true ;
if ( ! block _drag _node ) {
if ( this . allow _dragnodes ) {
this . graph . beforeChange ( ) ;
this . node _dragged = node ;
}
if ( ! this . selected _nodes [ node . id ] ) {
this . processNodeSelected ( node , e ) ;
}
}
}
}
var clicking _canvas _bg = false ;
//when clicked on top of a node
//and it is not interactive
if ( node && this . allow _interaction && ! skip _action && ! this . read _only ) {
if ( ! this . live _mode && ! node . flags . pinned ) {
this . bringToFront ( node ) ;
} //if it wasn't selected?
//not dragging mouse to connect two slots
if ( ! this . connecting _node && ! node . flags . collapsed && ! this . live _mode ) {
//Search for corner for resize
if ( ! skip _action &&
node . resizable !== false &&
isInsideRectangle ( e . canvasX ,
e . canvasY ,
node . pos [ 0 ] + node . size [ 0 ] - 5 ,
node . pos [ 1 ] + node . size [ 1 ] - 5 ,
10 ,
10
)
) {
this . graph . beforeChange ( ) ;
this . resizing _node = node ;
this . canvas . style . cursor = "se-resize" ;
skip _action = true ;
} else {
//search for outputs
if ( node . outputs ) {
for ( var i = 0 , l = node . outputs . length ; i < l ; ++ i ) {
var output = node . outputs [ i ] ;
var link _pos = node . getConnectionPos ( false , i ) ;
if (
isInsideRectangle (
e . canvasX ,
e . canvasY ,
link _pos [ 0 ] - 15 ,
link _pos [ 1 ] - 10 ,
30 ,
20
)
) {
this . connecting _node = node ;
this . connecting _output = output ;
this . connecting _output . slot _index = i ;
this . connecting _pos = node . getConnectionPos ( false , i ) ;
this . connecting _slot = i ;
if ( LiteGraph . shift _click _do _break _link _from ) {
if ( e . shiftKey ) {
node . disconnectOutput ( i ) ;
}
}
if ( is _double _click ) {
if ( node . onOutputDblClick ) {
node . onOutputDblClick ( i , e ) ;
}
} else {
if ( node . onOutputClick ) {
node . onOutputClick ( i , e ) ;
}
}
skip _action = true ;
break ;
}
}
}
//search for inputs
if ( node . inputs ) {
for ( var i = 0 , l = node . inputs . length ; i < l ; ++ i ) {
var input = node . inputs [ i ] ;
var link _pos = node . getConnectionPos ( true , i ) ;
if (
isInsideRectangle (
e . canvasX ,
e . canvasY ,
link _pos [ 0 ] - 15 ,
link _pos [ 1 ] - 10 ,
30 ,
20
)
) {
if ( is _double _click ) {
if ( node . onInputDblClick ) {
node . onInputDblClick ( i , e ) ;
}
} else {
if ( node . onInputClick ) {
node . onInputClick ( i , e ) ;
}
}
if ( input . link !== null ) {
var link _info = this . graph . links [
input . link
] ; //before disconnecting
if ( LiteGraph . click _do _break _link _to ) {
node . disconnectInput ( i ) ;
this . dirty _bgcanvas = true ;
skip _action = true ;
} else {
// do same action as has not node ?
}
if (
this . allow _reconnect _links ||
//this.move_destination_link_without_shift ||
e . shiftKey
) {
if ( ! LiteGraph . click _do _break _link _to ) {
node . disconnectInput ( i ) ;
}
this . connecting _node = this . graph . _nodes _by _id [
link _info . origin _id
] ;
this . connecting _slot =
link _info . origin _slot ;
this . connecting _output = this . connecting _node . outputs [
this . connecting _slot
] ;
this . connecting _pos = this . connecting _node . getConnectionPos ( false , this . connecting _slot ) ;
this . dirty _bgcanvas = true ;
skip _action = true ;
}
} else {
// has not node
}
if ( ! skip _action ) {
// connect from in to out, from to to from
this . connecting _node = node ;
this . connecting _input = input ;
this . connecting _input . slot _index = i ;
this . connecting _pos = node . getConnectionPos ( true , i ) ;
this . connecting _slot = i ;
this . dirty _bgcanvas = true ;
skip _action = true ;
}
}
}
}
} //not resizing
}
//it wasn't clicked on the links boxes
if ( ! skip _action ) {
var block _drag _node = false ;
var pos = [ e . canvasX - node . pos [ 0 ] , e . canvasY - node . pos [ 1 ] ] ;
//widgets
var widget = this . processNodeWidgets ( node , this . graph _mouse , e ) ;
if ( widget ) {
block _drag _node = true ;
this . node _widget = [ node , widget ] ;
}
//double clicking
if ( is _double _click && this . selected _nodes [ node . id ] ) {
//double click node
if ( node . onDblClick ) {
node . onDblClick ( e , pos , this ) ;
}
this . processNodeDblClicked ( node ) ;
block _drag _node = true ;
}
//if do not capture mouse
if ( node . onMouseDown && node . onMouseDown ( e , pos , this ) ) {
block _drag _node = true ;
} else {
//open subgraph button
if ( node . subgraph && ! node . skip _subgraph _button )
{
if ( ! node . flags . collapsed && pos [ 0 ] > node . size [ 0 ] - LiteGraph . NODE _TITLE _HEIGHT && pos [ 1 ] < 0 ) {
var that = this ;
setTimeout ( function ( ) {
that . openSubgraph ( node . subgraph ) ;
} , 10 ) ;
}
}
if ( this . live _mode ) {
clicking _canvas _bg = true ;
block _drag _node = true ;
}
}
if ( ! block _drag _node ) {
if ( this . allow _dragnodes ) {
this . graph . beforeChange ( ) ;
this . node _dragged = node ;
}
2023-04-07 19:11:00 +00:00
this . processNodeSelected ( node , e ) ;
} else { // double-click
/ * *
* Don ' t call the function if the block is already selected .
* Otherwise , it could cause the block to be unselected while its panel is open .
* /
if ( ! node . is _selected ) this . processNodeSelected ( node , e ) ;
2023-01-03 06:53:32 +00:00
}
this . dirty _canvas = true ;
}
} //clicked outside of nodes
else {
if ( ! skip _action ) {
//search for link connector
if ( ! this . read _only ) {
for ( var i = 0 ; i < this . visible _links . length ; ++ i ) {
var link = this . visible _links [ i ] ;
var center = link . _pos ;
if (
! center ||
e . canvasX < center [ 0 ] - 4 ||
e . canvasX > center [ 0 ] + 4 ||
e . canvasY < center [ 1 ] - 4 ||
e . canvasY > center [ 1 ] + 4
) {
continue ;
}
//link clicked
this . showLinkMenu ( link , e ) ;
this . over _link _center = null ; //clear tooltip
break ;
}
}
this . selected _group = this . graph . getGroupOnPos ( e . canvasX , e . canvasY ) ;
this . selected _group _resizing = false ;
if ( this . selected _group && ! this . read _only ) {
if ( e . ctrlKey ) {
this . dragging _rectangle = null ;
}
var dist = distance ( [ e . canvasX , e . canvasY ] , [ this . selected _group . pos [ 0 ] + this . selected _group . size [ 0 ] , this . selected _group . pos [ 1 ] + this . selected _group . size [ 1 ] ] ) ;
if ( dist * this . ds . scale < 10 ) {
this . selected _group _resizing = true ;
} else {
this . selected _group . recomputeInsideNodes ( ) ;
}
}
if ( is _double _click && ! this . read _only && this . allow _searchbox ) {
this . showSearchBox ( e ) ;
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
}
clicking _canvas _bg = true ;
}
}
if ( ! skip _action && clicking _canvas _bg && this . allow _dragcanvas ) {
//console.log("pointerevents: dragging_canvas start");
this . dragging _canvas = true ;
}
} else if ( e . which == 2 ) {
//middle button
if ( LiteGraph . middle _click _slot _add _default _node ) {
if ( node && this . allow _interaction && ! skip _action && ! this . read _only ) {
//not dragging mouse to connect two slots
if (
! this . connecting _node &&
! node . flags . collapsed &&
! this . live _mode
) {
var mClikSlot = false ;
var mClikSlot _index = false ;
var mClikSlot _isOut = false ;
//search for outputs
if ( node . outputs ) {
for ( var i = 0 , l = node . outputs . length ; i < l ; ++ i ) {
var output = node . outputs [ i ] ;
var link _pos = node . getConnectionPos ( false , i ) ;
if ( isInsideRectangle ( e . canvasX , e . canvasY , link _pos [ 0 ] - 15 , link _pos [ 1 ] - 10 , 30 , 20 ) ) {
mClikSlot = output ;
mClikSlot _index = i ;
mClikSlot _isOut = true ;
break ;
}
}
}
//search for inputs
if ( node . inputs ) {
for ( var i = 0 , l = node . inputs . length ; i < l ; ++ i ) {
var input = node . inputs [ i ] ;
var link _pos = node . getConnectionPos ( true , i ) ;
if ( isInsideRectangle ( e . canvasX , e . canvasY , link _pos [ 0 ] - 15 , link _pos [ 1 ] - 10 , 30 , 20 ) ) {
mClikSlot = input ;
mClikSlot _index = i ;
mClikSlot _isOut = false ;
break ;
}
}
}
//console.log("middleClickSlots? "+mClikSlot+" & "+(mClikSlot_index!==false));
if ( mClikSlot && mClikSlot _index !== false ) {
var alphaPosY = 0.5 - ( ( mClikSlot _index + 1 ) / ( ( mClikSlot _isOut ? node . outputs . length : node . inputs . length ) ) ) ;
var node _bounding = node . getBounding ( ) ;
// estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes
var posRef = [ ( ! mClikSlot _isOut ? node _bounding [ 0 ] : node _bounding [ 0 ] + node _bounding [ 2 ] ) // + node_bounding[0]/this.canvas.width*150
, e . canvasY - 80 // + node_bounding[0]/this.canvas.width*66 // vertical "derive"
] ;
var nodeCreated = this . createDefaultNodeForSlot ( { nodeFrom : ! mClikSlot _isOut ? null : node
, slotFrom : ! mClikSlot _isOut ? null : mClikSlot _index
, nodeTo : ! mClikSlot _isOut ? node : null
, slotTo : ! mClikSlot _isOut ? mClikSlot _index : null
, position : posRef //,e: e
, nodeType : "AUTO" //nodeNewType
, posAdd : [ ! mClikSlot _isOut ? - 30 : 30 , - alphaPosY * 130 ] //-alphaPosY*30]
, posSizeFix : [ ! mClikSlot _isOut ? - 1 : 0 , 0 ] //-alphaPosY*2*/
} ) ;
}
}
}
}
} else if ( e . which == 3 || this . pointer _is _double ) {
//right button
if ( this . allow _interaction && ! skip _action && ! this . read _only ) {
// is it hover a node ?
if ( node ) {
if ( Object . keys ( this . selected _nodes ) . length
&& ( this . selected _nodes [ node . id ] || e . shiftKey || e . ctrlKey || e . metaKey )
) {
// is multiselected or using shift to include the now node
if ( ! this . selected _nodes [ node . id ] ) this . selectNodes ( [ node ] , true ) ; // add this if not present
} else {
// update selection
this . selectNodes ( [ node ] ) ;
}
}
// show menu on this node
this . processContextMenu ( node , e ) ;
}
}
//TODO
//if(this.node_selected != prev_selected)
// this.onNodeSelectionChange(this.node_selected);
this . last _mouse [ 0 ] = e . clientX ;
this . last _mouse [ 1 ] = e . clientY ;
this . last _mouseclick = LiteGraph . getTime ( ) ;
this . last _mouse _dragging = true ;
/ *
if ( ( this . dirty _canvas || this . dirty _bgcanvas ) && this . rendering _timer _id == null )
this . draw ( ) ;
* /
this . graph . change ( ) ;
//this is to ensure to defocus(blur) if a text input element is on focus
if (
! ref _window . document . activeElement ||
( ref _window . document . activeElement . nodeName . toLowerCase ( ) !=
"input" &&
ref _window . document . activeElement . nodeName . toLowerCase ( ) !=
"textarea" )
) {
e . preventDefault ( ) ;
}
e . stopPropagation ( ) ;
if ( this . onMouseDown ) {
this . onMouseDown ( e ) ;
}
return false ;
} ;
/ * *
* Called when a mouse move event has to be processed
* @ method processMouseMove
* * /
LGraphCanvas . prototype . processMouseMove = function ( e ) {
if ( this . autoresize ) {
this . resize ( ) ;
}
if ( this . set _canvas _dirty _on _mouse _event )
this . dirty _canvas = true ;
if ( ! this . graph ) {
return ;
}
LGraphCanvas . active _canvas = this ;
this . adjustMouseEvent ( e ) ;
var mouse = [ e . clientX , e . clientY ] ;
this . mouse [ 0 ] = mouse [ 0 ] ;
this . mouse [ 1 ] = mouse [ 1 ] ;
var delta = [
mouse [ 0 ] - this . last _mouse [ 0 ] ,
mouse [ 1 ] - this . last _mouse [ 1 ]
] ;
this . last _mouse = mouse ;
this . graph _mouse [ 0 ] = e . canvasX ;
this . graph _mouse [ 1 ] = e . canvasY ;
//console.log("pointerevents: processMouseMove "+e.pointerId+" "+e.isPrimary);
if ( this . block _click )
{
//console.log("pointerevents: processMouseMove block_click");
e . preventDefault ( ) ;
return false ;
}
e . dragging = this . last _mouse _dragging ;
if ( this . node _widget ) {
this . processNodeWidgets (
this . node _widget [ 0 ] ,
this . graph _mouse ,
e ,
this . node _widget [ 1 ]
) ;
this . dirty _canvas = true ;
}
if ( this . dragging _rectangle )
{
this . dragging _rectangle [ 2 ] = e . canvasX - this . dragging _rectangle [ 0 ] ;
this . dragging _rectangle [ 3 ] = e . canvasY - this . dragging _rectangle [ 1 ] ;
this . dirty _canvas = true ;
}
else if ( this . selected _group && ! this . read _only )
{
//moving/resizing a group
if ( this . selected _group _resizing ) {
this . selected _group . size = [
e . canvasX - this . selected _group . pos [ 0 ] ,
e . canvasY - this . selected _group . pos [ 1 ]
] ;
} else {
var deltax = delta [ 0 ] / this . ds . scale ;
var deltay = delta [ 1 ] / this . ds . scale ;
this . selected _group . move ( deltax , deltay , e . ctrlKey ) ;
if ( this . selected _group . _nodes . length ) {
this . dirty _canvas = true ;
}
}
this . dirty _bgcanvas = true ;
} else if ( this . dragging _canvas ) {
////console.log("pointerevents: processMouseMove is dragging_canvas");
this . ds . offset [ 0 ] += delta [ 0 ] / this . ds . scale ;
this . ds . offset [ 1 ] += delta [ 1 ] / this . ds . scale ;
this . dirty _canvas = true ;
this . dirty _bgcanvas = true ;
} else if ( this . allow _interaction && ! this . read _only ) {
if ( this . connecting _node ) {
this . dirty _canvas = true ;
}
//get node over
var node = this . graph . getNodeOnPos ( e . canvasX , e . canvasY , this . visible _nodes ) ;
//remove mouseover flag
for ( var i = 0 , l = this . graph . _nodes . length ; i < l ; ++ i ) {
if ( this . graph . _nodes [ i ] . mouseOver && node != this . graph . _nodes [ i ] ) {
//mouse leave
this . graph . _nodes [ i ] . mouseOver = false ;
if ( this . node _over && this . node _over . onMouseLeave ) {
this . node _over . onMouseLeave ( e ) ;
}
this . node _over = null ;
this . dirty _canvas = true ;
}
}
//mouse over a node
if ( node ) {
if ( node . redraw _on _mouse )
this . dirty _canvas = true ;
//this.canvas.style.cursor = "move";
if ( ! node . mouseOver ) {
//mouse enter
node . mouseOver = true ;
this . node _over = node ;
this . dirty _canvas = true ;
if ( node . onMouseEnter ) {
node . onMouseEnter ( e ) ;
}
}
//in case the node wants to do something
if ( node . onMouseMove ) {
node . onMouseMove ( e , [ e . canvasX - node . pos [ 0 ] , e . canvasY - node . pos [ 1 ] ] , this ) ;
}
//if dragging a link
if ( this . connecting _node ) {
if ( this . connecting _output ) {
var pos = this . _highlight _input || [ 0 , 0 ] ; //to store the output of isOverNodeInput
//on top of input
if ( this . isOverNodeBox ( node , e . canvasX , e . canvasY ) ) {
//mouse on top of the corner box, don't know what to do
} else {
//check if I have a slot below de mouse
var slot = this . isOverNodeInput ( node , e . canvasX , e . canvasY , pos ) ;
if ( slot != - 1 && node . inputs [ slot ] ) {
var slot _type = node . inputs [ slot ] . type ;
if ( LiteGraph . isValidConnection ( this . connecting _output . type , slot _type ) ) {
this . _highlight _input = pos ;
this . _highlight _input _slot = node . inputs [ slot ] ; // XXX CHECK THIS
}
} else {
this . _highlight _input = null ;
this . _highlight _input _slot = null ; // XXX CHECK THIS
}
}
} else if ( this . connecting _input ) {
var pos = this . _highlight _output || [ 0 , 0 ] ; //to store the output of isOverNodeOutput
//on top of output
if ( this . isOverNodeBox ( node , e . canvasX , e . canvasY ) ) {
//mouse on top of the corner box, don't know what to do
} else {
//check if I have a slot below de mouse
var slot = this . isOverNodeOutput ( node , e . canvasX , e . canvasY , pos ) ;
if ( slot != - 1 && node . outputs [ slot ] ) {
var slot _type = node . outputs [ slot ] . type ;
if ( LiteGraph . isValidConnection ( this . connecting _input . type , slot _type ) ) {
this . _highlight _output = pos ;
}
} else {
this . _highlight _output = null ;
}
}
}
}
//Search for corner
if ( this . canvas ) {
if (
isInsideRectangle (
e . canvasX ,
e . canvasY ,
node . pos [ 0 ] + node . size [ 0 ] - 5 ,
node . pos [ 1 ] + node . size [ 1 ] - 5 ,
5 ,
5
)
) {
this . canvas . style . cursor = "se-resize" ;
} else {
this . canvas . style . cursor = "crosshair" ;
}
}
} else { //not over a node
//search for link connector
var over _link = null ;
for ( var i = 0 ; i < this . visible _links . length ; ++ i ) {
var link = this . visible _links [ i ] ;
var center = link . _pos ;
if (
! center ||
e . canvasX < center [ 0 ] - 4 ||
e . canvasX > center [ 0 ] + 4 ||
e . canvasY < center [ 1 ] - 4 ||
e . canvasY > center [ 1 ] + 4
) {
continue ;
}
over _link = link ;
break ;
}
if ( over _link != this . over _link _center )
{
this . over _link _center = over _link ;
this . dirty _canvas = true ;
}
if ( this . canvas ) {
this . canvas . style . cursor = "" ;
}
} //end
//send event to node if capturing input (used with widgets that allow drag outside of the area of the node)
if ( this . node _capturing _input && this . node _capturing _input != node && this . node _capturing _input . onMouseMove ) {
this . node _capturing _input . onMouseMove ( e , [ e . canvasX - this . node _capturing _input . pos [ 0 ] , e . canvasY - this . node _capturing _input . pos [ 1 ] ] , this ) ;
}
//node being dragged
if ( this . node _dragged && ! this . live _mode ) {
//console.log("draggin!",this.selected_nodes);
for ( var i in this . selected _nodes ) {
var n = this . selected _nodes [ i ] ;
n . pos [ 0 ] += delta [ 0 ] / this . ds . scale ;
n . pos [ 1 ] += delta [ 1 ] / this . ds . scale ;
2023-04-07 19:11:00 +00:00
if ( ! n . is _selected ) this . processNodeSelected ( n , e ) ; / *
* Don ' t call the function if the block is already selected .
* Otherwise , it could cause the block to be unselected while dragging .
* /
2023-01-03 06:53:32 +00:00
}
this . dirty _canvas = true ;
this . dirty _bgcanvas = true ;
}
if ( this . resizing _node && ! this . live _mode ) {
//convert mouse to node space
var desired _size = [ e . canvasX - this . resizing _node . pos [ 0 ] , e . canvasY - this . resizing _node . pos [ 1 ] ] ;
var min _size = this . resizing _node . computeSize ( ) ;
desired _size [ 0 ] = Math . max ( min _size [ 0 ] , desired _size [ 0 ] ) ;
desired _size [ 1 ] = Math . max ( min _size [ 1 ] , desired _size [ 1 ] ) ;
this . resizing _node . setSize ( desired _size ) ;
this . canvas . style . cursor = "se-resize" ;
this . dirty _canvas = true ;
this . dirty _bgcanvas = true ;
}
}
e . preventDefault ( ) ;
return false ;
} ;
/ * *
* Called when a mouse up event has to be processed
* @ method processMouseUp
* * /
LGraphCanvas . prototype . processMouseUp = function ( e ) {
var is _primary = ( e . isPrimary === undefined || e . isPrimary ) ;
//early exit for extra pointer
if ( ! is _primary ) {
/ * e . s t o p P r o p a g a t i o n ( ) ;
e . preventDefault ( ) ; * /
//console.log("pointerevents: processMouseUp pointerN_stop "+e.pointerId+" "+e.isPrimary);
return false ;
}
//console.log("pointerevents: processMouseUp "+e.pointerId+" "+e.isPrimary+" :: "+e.clientX+" "+e.clientY);
if ( this . set _canvas _dirty _on _mouse _event )
this . dirty _canvas = true ;
if ( ! this . graph )
return ;
var window = this . getCanvasWindow ( ) ;
var document = window . document ;
LGraphCanvas . active _canvas = this ;
//restore the mousemove event back to the canvas
if ( ! this . options . skip _events )
{
//console.log("pointerevents: processMouseUp adjustEventListener");
LiteGraph . pointerListenerRemove ( document , "move" , this . _mousemove _callback , true ) ;
LiteGraph . pointerListenerAdd ( this . canvas , "move" , this . _mousemove _callback , true ) ;
LiteGraph . pointerListenerRemove ( document , "up" , this . _mouseup _callback , true ) ;
}
this . adjustMouseEvent ( e ) ;
var now = LiteGraph . getTime ( ) ;
e . click _time = now - this . last _mouseclick ;
this . last _mouse _dragging = false ;
this . last _click _position = null ;
if ( this . block _click )
{
//console.log("pointerevents: processMouseUp block_clicks");
this . block _click = false ; //used to avoid sending twice a click in a immediate button
}
//console.log("pointerevents: processMouseUp which: "+e.which);
if ( e . which == 1 ) {
if ( this . node _widget )
{
this . processNodeWidgets ( this . node _widget [ 0 ] , this . graph _mouse , e ) ;
}
//left button
this . node _widget = null ;
if ( this . selected _group ) {
var diffx =
this . selected _group . pos [ 0 ] -
Math . round ( this . selected _group . pos [ 0 ] ) ;
var diffy =
this . selected _group . pos [ 1 ] -
Math . round ( this . selected _group . pos [ 1 ] ) ;
this . selected _group . move ( diffx , diffy , e . ctrlKey ) ;
this . selected _group . pos [ 0 ] = Math . round (
this . selected _group . pos [ 0 ]
) ;
this . selected _group . pos [ 1 ] = Math . round (
this . selected _group . pos [ 1 ]
) ;
if ( this . selected _group . _nodes . length ) {
this . dirty _canvas = true ;
}
this . selected _group = null ;
}
this . selected _group _resizing = false ;
var node = this . graph . getNodeOnPos (
e . canvasX ,
e . canvasY ,
this . visible _nodes
) ;
if ( this . dragging _rectangle ) {
if ( this . graph ) {
var nodes = this . graph . _nodes ;
var node _bounding = new Float32Array ( 4 ) ;
//compute bounding and flip if left to right
var w = Math . abs ( this . dragging _rectangle [ 2 ] ) ;
var h = Math . abs ( this . dragging _rectangle [ 3 ] ) ;
var startx =
this . dragging _rectangle [ 2 ] < 0
? this . dragging _rectangle [ 0 ] - w
: this . dragging _rectangle [ 0 ] ;
var starty =
this . dragging _rectangle [ 3 ] < 0
? this . dragging _rectangle [ 1 ] - h
: this . dragging _rectangle [ 1 ] ;
this . dragging _rectangle [ 0 ] = startx ;
this . dragging _rectangle [ 1 ] = starty ;
this . dragging _rectangle [ 2 ] = w ;
this . dragging _rectangle [ 3 ] = h ;
// test dragging rect size, if minimun simulate a click
if ( ! node || ( w > 10 && h > 10 ) ) {
//test against all nodes (not visible because the rectangle maybe start outside
var to _select = [ ] ;
for ( var i = 0 ; i < nodes . length ; ++ i ) {
var nodeX = nodes [ i ] ;
nodeX . getBounding ( node _bounding ) ;
if (
! overlapBounding (
this . dragging _rectangle ,
node _bounding
)
) {
continue ;
} //out of the visible area
to _select . push ( nodeX ) ;
}
if ( to _select . length ) {
this . selectNodes ( to _select , e . shiftKey ) ; // add to selection with shift
}
} else {
// will select of update selection
this . selectNodes ( [ node ] , e . shiftKey || e . ctrlKey ) ; // add to selection add to selection with ctrlKey or shiftKey
}
}
this . dragging _rectangle = null ;
} else if ( this . connecting _node ) {
//dragging a connection
this . dirty _canvas = true ;
this . dirty _bgcanvas = true ;
var connInOrOut = this . connecting _output || this . connecting _input ;
var connType = connInOrOut . type ;
//node below mouse
if ( node ) {
/ * n o n e e d t o c o n d i t i o n o n e v e n t t y p e . . j u s t a n o t h e r t y p e
if (
connType == LiteGraph . EVENT &&
this . isOverNodeBox ( node , e . canvasX , e . canvasY )
) {
this . connecting _node . connect (
this . connecting _slot ,
node ,
LiteGraph . EVENT
) ;
} else { * /
//slot below mouse? connect
if ( this . connecting _output ) {
var slot = this . isOverNodeInput (
node ,
e . canvasX ,
e . canvasY
) ;
if ( slot != - 1 ) {
this . connecting _node . connect ( this . connecting _slot , node , slot ) ;
} else {
//not on top of an input
// look for a good slot
this . connecting _node . connectByType ( this . connecting _slot , node , connType ) ;
}
} else if ( this . connecting _input ) {
var slot = this . isOverNodeOutput (
node ,
e . canvasX ,
e . canvasY
) ;
if ( slot != - 1 ) {
node . connect ( slot , this . connecting _node , this . connecting _slot ) ; // this is inverted has output-input nature like
} else {
//not on top of an input
// look for a good slot
this . connecting _node . connectByTypeOutput ( this . connecting _slot , node , connType ) ;
}
}
//}
} else {
// add menu when releasing link in empty space
if ( LiteGraph . release _link _on _empty _shows _menu ) {
if ( e . shiftKey && this . allow _searchbox ) {
if ( this . connecting _output ) {
this . showSearchBox ( e , { node _from : this . connecting _node , slot _from : this . connecting _output , type _filter _in : this . connecting _output . type } ) ;
} else if ( this . connecting _input ) {
this . showSearchBox ( e , { node _to : this . connecting _node , slot _from : this . connecting _input , type _filter _out : this . connecting _input . type } ) ;
}
} else {
if ( this . connecting _output ) {
this . showConnectionMenu ( { nodeFrom : this . connecting _node , slotFrom : this . connecting _output , e : e } ) ;
} else if ( this . connecting _input ) {
this . showConnectionMenu ( { nodeTo : this . connecting _node , slotTo : this . connecting _input , e : e } ) ;
}
}
}
}
this . connecting _output = null ;
this . connecting _input = null ;
this . connecting _pos = null ;
this . connecting _node = null ;
this . connecting _slot = - 1 ;
} //not dragging connection
else if ( this . resizing _node ) {
this . dirty _canvas = true ;
this . dirty _bgcanvas = true ;
this . graph . afterChange ( this . resizing _node ) ;
this . resizing _node = null ;
} else if ( this . node _dragged ) {
//node being dragged?
var node = this . node _dragged ;
if (
node &&
e . click _time < 300 &&
isInsideRectangle ( e . canvasX , e . canvasY , node . pos [ 0 ] , node . pos [ 1 ] - LiteGraph . NODE _TITLE _HEIGHT , LiteGraph . NODE _TITLE _HEIGHT , LiteGraph . NODE _TITLE _HEIGHT )
) {
node . collapse ( ) ;
}
this . dirty _canvas = true ;
this . dirty _bgcanvas = true ;
this . node _dragged . pos [ 0 ] = Math . round ( this . node _dragged . pos [ 0 ] ) ;
this . node _dragged . pos [ 1 ] = Math . round ( this . node _dragged . pos [ 1 ] ) ;
if ( this . graph . config . align _to _grid || this . align _to _grid ) {
this . node _dragged . alignToGrid ( ) ;
}
if ( this . onNodeMoved )
this . onNodeMoved ( this . node _dragged ) ;
this . graph . afterChange ( this . node _dragged ) ;
this . node _dragged = null ;
} //no node being dragged
else {
//get node over
var node = this . graph . getNodeOnPos (
e . canvasX ,
e . canvasY ,
this . visible _nodes
) ;
if ( ! node && e . click _time < 300 ) {
this . deselectAllNodes ( ) ;
}
this . dirty _canvas = true ;
this . dragging _canvas = false ;
if ( this . node _over && this . node _over . onMouseUp ) {
this . node _over . onMouseUp ( e , [ e . canvasX - this . node _over . pos [ 0 ] , e . canvasY - this . node _over . pos [ 1 ] ] , this ) ;
}
if (
this . node _capturing _input &&
this . node _capturing _input . onMouseUp
) {
this . node _capturing _input . onMouseUp ( e , [
e . canvasX - this . node _capturing _input . pos [ 0 ] ,
e . canvasY - this . node _capturing _input . pos [ 1 ]
] ) ;
}
}
} else if ( e . which == 2 ) {
//middle button
//trace("middle");
this . dirty _canvas = true ;
this . dragging _canvas = false ;
} else if ( e . which == 3 ) {
//right button
//trace("right");
this . dirty _canvas = true ;
this . dragging _canvas = false ;
}
/ *
if ( ( this . dirty _canvas || this . dirty _bgcanvas ) && this . rendering _timer _id == null )
this . draw ( ) ;
* /
if ( is _primary )
{
this . pointer _is _down = false ;
this . pointer _is _double = false ;
}
this . graph . change ( ) ;
//console.log("pointerevents: processMouseUp stopPropagation");
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
return false ;
} ;
/ * *
* Called when a mouse wheel event has to be processed
* @ method processMouseWheel
* * /
LGraphCanvas . prototype . processMouseWheel = function ( e ) {
if ( ! this . graph || ! this . allow _dragcanvas ) {
return ;
}
var delta = e . wheelDeltaY != null ? e . wheelDeltaY : e . detail * - 60 ;
this . adjustMouseEvent ( e ) ;
var x = e . clientX ;
var y = e . clientY ;
var is _inside = ! this . viewport || ( this . viewport && x >= this . viewport [ 0 ] && x < ( this . viewport [ 0 ] + this . viewport [ 2 ] ) && y >= this . viewport [ 1 ] && y < ( this . viewport [ 1 ] + this . viewport [ 3 ] ) ) ;
if ( ! is _inside )
return ;
var scale = this . ds . scale ;
if ( delta > 0 ) {
scale *= 1.1 ;
} else if ( delta < 0 ) {
scale *= 1 / 1.1 ;
}
//this.setZoom( scale, [ e.clientX, e.clientY ] );
this . ds . changeScale ( scale , [ e . clientX , e . clientY ] ) ;
this . graph . change ( ) ;
e . preventDefault ( ) ;
return false ; // prevent default
} ;
/ * *
* returns true if a position ( in graph space ) is on top of a node little corner box
* @ method isOverNodeBox
* * /
LGraphCanvas . prototype . isOverNodeBox = function ( node , canvasx , canvasy ) {
var title _height = LiteGraph . NODE _TITLE _HEIGHT ;
if (
isInsideRectangle (
canvasx ,
canvasy ,
node . pos [ 0 ] + 2 ,
node . pos [ 1 ] + 2 - title _height ,
title _height - 4 ,
title _height - 4
)
) {
return true ;
}
return false ;
} ;
/ * *
* returns the INDEX if a position ( in graph space ) is on top of a node input slot
* @ method isOverNodeInput
* * /
LGraphCanvas . prototype . isOverNodeInput = function (
node ,
canvasx ,
canvasy ,
slot _pos
) {
if ( node . inputs ) {
for ( var i = 0 , l = node . inputs . length ; i < l ; ++ i ) {
var input = node . inputs [ i ] ;
var link _pos = node . getConnectionPos ( true , i ) ;
var is _inside = false ;
if ( node . horizontal ) {
is _inside = isInsideRectangle (
canvasx ,
canvasy ,
link _pos [ 0 ] - 5 ,
link _pos [ 1 ] - 10 ,
10 ,
20
) ;
} else {
is _inside = isInsideRectangle (
canvasx ,
canvasy ,
link _pos [ 0 ] - 10 ,
link _pos [ 1 ] - 5 ,
40 ,
10
) ;
}
if ( is _inside ) {
if ( slot _pos ) {
slot _pos [ 0 ] = link _pos [ 0 ] ;
slot _pos [ 1 ] = link _pos [ 1 ] ;
}
return i ;
}
}
}
return - 1 ;
} ;
/ * *
* returns the INDEX if a position ( in graph space ) is on top of a node output slot
* @ method isOverNodeOuput
* * /
LGraphCanvas . prototype . isOverNodeOutput = function (
node ,
canvasx ,
canvasy ,
slot _pos
) {
if ( node . outputs ) {
for ( var i = 0 , l = node . outputs . length ; i < l ; ++ i ) {
var output = node . outputs [ i ] ;
var link _pos = node . getConnectionPos ( false , i ) ;
var is _inside = false ;
if ( node . horizontal ) {
is _inside = isInsideRectangle (
canvasx ,
canvasy ,
link _pos [ 0 ] - 5 ,
link _pos [ 1 ] - 10 ,
10 ,
20
) ;
} else {
is _inside = isInsideRectangle (
canvasx ,
canvasy ,
link _pos [ 0 ] - 10 ,
link _pos [ 1 ] - 5 ,
40 ,
10
) ;
}
if ( is _inside ) {
if ( slot _pos ) {
slot _pos [ 0 ] = link _pos [ 0 ] ;
slot _pos [ 1 ] = link _pos [ 1 ] ;
}
return i ;
}
}
}
return - 1 ;
} ;
/ * *
* process a key event
* @ method processKey
* * /
LGraphCanvas . prototype . processKey = function ( e ) {
if ( ! this . graph ) {
return ;
}
var block _default = false ;
//console.log(e); //debug
if ( e . target . localName == "input" ) {
return ;
}
if ( e . type == "keydown" ) {
if ( e . keyCode == 32 ) {
//space
this . dragging _canvas = true ;
block _default = true ;
}
if ( e . keyCode == 27 ) {
//esc
if ( this . node _panel ) this . node _panel . close ( ) ;
if ( this . options _panel ) this . options _panel . close ( ) ;
block _default = true ;
}
//select all Control A
if ( e . keyCode == 65 && e . ctrlKey ) {
this . selectNodes ( ) ;
block _default = true ;
}
if ( e . code == "KeyC" && ( e . metaKey || e . ctrlKey ) && ! e . shiftKey ) {
//copy
if ( this . selected _nodes ) {
this . copyToClipboard ( ) ;
block _default = true ;
}
}
if ( e . code == "KeyV" && ( e . metaKey || e . ctrlKey ) && ! e . shiftKey ) {
//paste
this . pasteFromClipboard ( ) ;
}
//delete or backspace
if ( e . keyCode == 46 || e . keyCode == 8 ) {
if (
e . target . localName != "input" &&
e . target . localName != "textarea"
) {
this . deleteSelectedNodes ( ) ;
block _default = true ;
}
}
//collapse
//...
//TODO
if ( this . selected _nodes ) {
for ( var i in this . selected _nodes ) {
if ( this . selected _nodes [ i ] . onKeyDown ) {
this . selected _nodes [ i ] . onKeyDown ( e ) ;
}
}
}
} else if ( e . type == "keyup" ) {
if ( e . keyCode == 32 ) {
// space
this . dragging _canvas = false ;
}
if ( this . selected _nodes ) {
for ( var i in this . selected _nodes ) {
if ( this . selected _nodes [ i ] . onKeyUp ) {
this . selected _nodes [ i ] . onKeyUp ( e ) ;
}
}
}
}
this . graph . change ( ) ;
if ( block _default ) {
e . preventDefault ( ) ;
e . stopImmediatePropagation ( ) ;
return false ;
}
} ;
LGraphCanvas . prototype . copyToClipboard = function ( ) {
var clipboard _info = {
nodes : [ ] ,
links : [ ]
} ;
var index = 0 ;
var selected _nodes _array = [ ] ;
for ( var i in this . selected _nodes ) {
var node = this . selected _nodes [ i ] ;
node . _relative _id = index ;
selected _nodes _array . push ( node ) ;
index += 1 ;
}
for ( var i = 0 ; i < selected _nodes _array . length ; ++ i ) {
var node = selected _nodes _array [ i ] ;
var cloned = node . clone ( ) ;
if ( ! cloned )
{
console . warn ( "node type not found: " + node . type ) ;
continue ;
}
clipboard _info . nodes . push ( cloned . serialize ( ) ) ;
if ( node . inputs && node . inputs . length ) {
for ( var j = 0 ; j < node . inputs . length ; ++ j ) {
var input = node . inputs [ j ] ;
if ( ! input || input . link == null ) {
continue ;
}
var link _info = this . graph . links [ input . link ] ;
if ( ! link _info ) {
continue ;
}
var target _node = this . graph . getNodeById (
link _info . origin _id
) ;
if ( ! target _node || ! this . selected _nodes [ target _node . id ] ) {
//improve this by allowing connections to non-selected nodes
continue ;
} //not selected
clipboard _info . links . push ( [
target _node . _relative _id ,
link _info . origin _slot , //j,
node . _relative _id ,
link _info . target _slot
] ) ;
}
}
}
localStorage . setItem (
"litegrapheditor_clipboard" ,
JSON . stringify ( clipboard _info )
) ;
} ;
LGraphCanvas . prototype . pasteFromClipboard = function ( ) {
var data = localStorage . getItem ( "litegrapheditor_clipboard" ) ;
if ( ! data ) {
return ;
}
this . graph . beforeChange ( ) ;
//create nodes
var clipboard _info = JSON . parse ( data ) ;
// calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos
var posMin = false ;
var posMinIndexes = false ;
for ( var i = 0 ; i < clipboard _info . nodes . length ; ++ i ) {
if ( posMin ) {
if ( posMin [ 0 ] > clipboard _info . nodes [ i ] . pos [ 0 ] ) {
posMin [ 0 ] = clipboard _info . nodes [ i ] . pos [ 0 ] ;
posMinIndexes [ 0 ] = i ;
}
if ( posMin [ 1 ] > clipboard _info . nodes [ i ] . pos [ 1 ] ) {
posMin [ 1 ] = clipboard _info . nodes [ i ] . pos [ 1 ] ;
posMinIndexes [ 1 ] = i ;
}
}
else {
posMin = [ clipboard _info . nodes [ i ] . pos [ 0 ] , clipboard _info . nodes [ i ] . pos [ 1 ] ] ;
posMinIndexes = [ i , i ] ;
}
}
var nodes = [ ] ;
for ( var i = 0 ; i < clipboard _info . nodes . length ; ++ i ) {
var node _data = clipboard _info . nodes [ i ] ;
var node = LiteGraph . createNode ( node _data . type ) ;
if ( node ) {
node . configure ( node _data ) ;
//paste in last known mouse position
node . pos [ 0 ] += this . graph _mouse [ 0 ] - posMin [ 0 ] ; //+= 5;
node . pos [ 1 ] += this . graph _mouse [ 1 ] - posMin [ 1 ] ; //+= 5;
this . graph . add ( node , { doProcessChange : false } ) ;
nodes . push ( node ) ;
}
}
//create links
for ( var i = 0 ; i < clipboard _info . links . length ; ++ i ) {
var link _info = clipboard _info . links [ i ] ;
var origin _node = nodes [ link _info [ 0 ] ] ;
var target _node = nodes [ link _info [ 2 ] ] ;
if ( origin _node && target _node )
origin _node . connect ( link _info [ 1 ] , target _node , link _info [ 3 ] ) ;
else
console . warn ( "Warning, nodes missing on pasting" ) ;
}
this . selectNodes ( nodes ) ;
this . graph . afterChange ( ) ;
} ;
/ * *
* process a item drop event on top the canvas
* @ method processDrop
* * /
LGraphCanvas . prototype . processDrop = function ( e ) {
e . preventDefault ( ) ;
this . adjustMouseEvent ( e ) ;
var x = e . clientX ;
var y = e . clientY ;
var is _inside = ! this . viewport || ( this . viewport && x >= this . viewport [ 0 ] && x < ( this . viewport [ 0 ] + this . viewport [ 2 ] ) && y >= this . viewport [ 1 ] && y < ( this . viewport [ 1 ] + this . viewport [ 3 ] ) ) ;
if ( ! is _inside ) {
return ;
// --- BREAK ---
}
var pos = [ e . canvasX , e . canvasY ] ;
var node = this . graph ? this . graph . getNodeOnPos ( pos [ 0 ] , pos [ 1 ] ) : null ;
if ( ! node ) {
var r = null ;
if ( this . onDropItem ) {
r = this . onDropItem ( event ) ;
}
if ( ! r ) {
this . checkDropItem ( e ) ;
}
return ;
}
if ( node . onDropFile || node . onDropData ) {
var files = e . dataTransfer . files ;
if ( files && files . length ) {
for ( var i = 0 ; i < files . length ; i ++ ) {
var file = e . dataTransfer . files [ 0 ] ;
var filename = file . name ;
var ext = LGraphCanvas . getFileExtension ( filename ) ;
//console.log(file);
if ( node . onDropFile ) {
node . onDropFile ( file ) ;
}
if ( node . onDropData ) {
//prepare reader
var reader = new FileReader ( ) ;
reader . onload = function ( event ) {
//console.log(event.target);
var data = event . target . result ;
node . onDropData ( data , filename , file ) ;
} ;
//read data
var type = file . type . split ( "/" ) [ 0 ] ;
if ( type == "text" || type == "" ) {
reader . readAsText ( file ) ;
} else if ( type == "image" ) {
reader . readAsDataURL ( file ) ;
} else {
reader . readAsArrayBuffer ( file ) ;
}
}
}
}
}
if ( node . onDropItem ) {
if ( node . onDropItem ( event ) ) {
return true ;
}
}
if ( this . onDropItem ) {
return this . onDropItem ( event ) ;
}
return false ;
} ;
//called if the graph doesn't have a default drop item behaviour
LGraphCanvas . prototype . checkDropItem = function ( e ) {
if ( e . dataTransfer . files . length ) {
var file = e . dataTransfer . files [ 0 ] ;
var ext = LGraphCanvas . getFileExtension ( file . name ) . toLowerCase ( ) ;
var nodetype = LiteGraph . node _types _by _file _extension [ ext ] ;
if ( nodetype ) {
this . graph . beforeChange ( ) ;
var node = LiteGraph . createNode ( nodetype . type ) ;
node . pos = [ e . canvasX , e . canvasY ] ;
this . graph . add ( node ) ;
if ( node . onDropFile ) {
node . onDropFile ( file ) ;
}
this . graph . afterChange ( ) ;
}
}
} ;
LGraphCanvas . prototype . processNodeDblClicked = function ( n ) {
if ( this . onShowNodePanel ) {
this . onShowNodePanel ( n ) ;
}
else
{
this . showShowNodePanel ( n ) ;
}
if ( this . onNodeDblClicked ) {
this . onNodeDblClicked ( n ) ;
}
this . setDirty ( true ) ;
} ;
LGraphCanvas . prototype . processNodeSelected = function ( node , e ) {
2023-04-07 19:11:00 +00:00
this . selectNode ( node , e && ( e . shiftKey || e . ctrlKey || this . multi _select ) ) ;
2023-01-03 06:53:32 +00:00
if ( this . onNodeSelected ) {
this . onNodeSelected ( node ) ;
}
} ;
/ * *
* selects a given node ( or adds it to the current selection )
* @ method selectNode
* * /
LGraphCanvas . prototype . selectNode = function (
node ,
add _to _current _selection
) {
if ( node == null ) {
this . deselectAllNodes ( ) ;
} else {
this . selectNodes ( [ node ] , add _to _current _selection ) ;
}
} ;
/ * *
* selects several nodes ( or adds them to the current selection )
* @ method selectNodes
* * /
LGraphCanvas . prototype . selectNodes = function ( nodes , add _to _current _selection )
{
if ( ! add _to _current _selection ) {
this . deselectAllNodes ( ) ;
}
nodes = nodes || this . graph . _nodes ;
if ( typeof nodes == "string" ) nodes = [ nodes ] ;
for ( var i in nodes ) {
var node = nodes [ i ] ;
if ( node . is _selected ) {
2023-04-07 19:11:00 +00:00
this . deselectNode ( node ) ;
2023-01-03 06:53:32 +00:00
continue ;
}
if ( ! node . is _selected && node . onSelected ) {
node . onSelected ( ) ;
}
node . is _selected = true ;
this . selected _nodes [ node . id ] = node ;
if ( node . inputs ) {
for ( var j = 0 ; j < node . inputs . length ; ++ j ) {
this . highlighted _links [ node . inputs [ j ] . link ] = true ;
}
}
if ( node . outputs ) {
for ( var j = 0 ; j < node . outputs . length ; ++ j ) {
var out = node . outputs [ j ] ;
if ( out . links ) {
for ( var k = 0 ; k < out . links . length ; ++ k ) {
this . highlighted _links [ out . links [ k ] ] = true ;
}
}
}
}
}
if ( this . onSelectionChange )
this . onSelectionChange ( this . selected _nodes ) ;
this . setDirty ( true ) ;
} ;
/ * *
* removes a node from the current selection
* @ method deselectNode
* * /
LGraphCanvas . prototype . deselectNode = function ( node ) {
if ( ! node . is _selected ) {
return ;
}
if ( node . onDeselected ) {
node . onDeselected ( ) ;
}
node . is _selected = false ;
if ( this . onNodeDeselected ) {
this . onNodeDeselected ( node ) ;
}
//remove highlighted
if ( node . inputs ) {
for ( var i = 0 ; i < node . inputs . length ; ++ i ) {
delete this . highlighted _links [ node . inputs [ i ] . link ] ;
}
}
if ( node . outputs ) {
for ( var i = 0 ; i < node . outputs . length ; ++ i ) {
var out = node . outputs [ i ] ;
if ( out . links ) {
for ( var j = 0 ; j < out . links . length ; ++ j ) {
delete this . highlighted _links [ out . links [ j ] ] ;
}
}
}
}
} ;
/ * *
* removes all nodes from the current selection
* @ method deselectAllNodes
* * /
LGraphCanvas . prototype . deselectAllNodes = function ( ) {
if ( ! this . graph ) {
return ;
}
var nodes = this . graph . _nodes ;
for ( var i = 0 , l = nodes . length ; i < l ; ++ i ) {
var node = nodes [ i ] ;
if ( ! node . is _selected ) {
continue ;
}
if ( node . onDeselected ) {
node . onDeselected ( ) ;
}
node . is _selected = false ;
if ( this . onNodeDeselected ) {
this . onNodeDeselected ( node ) ;
}
}
this . selected _nodes = { } ;
this . current _node = null ;
this . highlighted _links = { } ;
if ( this . onSelectionChange )
this . onSelectionChange ( this . selected _nodes ) ;
this . setDirty ( true ) ;
} ;
/ * *
* deletes all nodes in the current selection from the graph
* @ method deleteSelectedNodes
* * /
LGraphCanvas . prototype . deleteSelectedNodes = function ( ) {
this . graph . beforeChange ( ) ;
for ( var i in this . selected _nodes ) {
var node = this . selected _nodes [ i ] ;
if ( node . block _delete )
continue ;
//autoconnect when possible (very basic, only takes into account first input-output)
if ( node . inputs && node . inputs . length && node . outputs && node . outputs . length && LiteGraph . isValidConnection ( node . inputs [ 0 ] . type , node . outputs [ 0 ] . type ) && node . inputs [ 0 ] . link && node . outputs [ 0 ] . links && node . outputs [ 0 ] . links . length )
{
var input _link = node . graph . links [ node . inputs [ 0 ] . link ] ;
var output _link = node . graph . links [ node . outputs [ 0 ] . links [ 0 ] ] ;
var input _node = node . getInputNode ( 0 ) ;
var output _node = node . getOutputNodes ( 0 ) [ 0 ] ;
if ( input _node && output _node )
input _node . connect ( input _link . origin _slot , output _node , output _link . target _slot ) ;
}
this . graph . remove ( node ) ;
if ( this . onNodeDeselected ) {
this . onNodeDeselected ( node ) ;
}
}
this . selected _nodes = { } ;
this . current _node = null ;
this . highlighted _links = { } ;
this . setDirty ( true ) ;
this . graph . afterChange ( ) ;
} ;
/ * *
* centers the camera on a given node
* @ method centerOnNode
* * /
LGraphCanvas . prototype . centerOnNode = function ( node ) {
this . ds . offset [ 0 ] =
- node . pos [ 0 ] -
node . size [ 0 ] * 0.5 +
( this . canvas . width * 0.5 ) / this . ds . scale ;
this . ds . offset [ 1 ] =
- node . pos [ 1 ] -
node . size [ 1 ] * 0.5 +
( this . canvas . height * 0.5 ) / this . ds . scale ;
this . setDirty ( true , true ) ;
} ;
/ * *
* adds some useful properties to a mouse event , like the position in graph coordinates
* @ method adjustMouseEvent
* * /
LGraphCanvas . prototype . adjustMouseEvent = function ( e ) {
var clientX _rel = 0 ;
var clientY _rel = 0 ;
if ( this . canvas ) {
var b = this . canvas . getBoundingClientRect ( ) ;
clientX _rel = e . clientX - b . left ;
clientY _rel = e . clientY - b . top ;
} else {
clientX _rel = e . clientX ;
clientY _rel = e . clientY ;
}
2023-04-07 19:11:00 +00:00
// e.deltaX = clientX_rel - this.last_mouse_position[0];
// e.deltaY = clientY_rel- this.last_mouse_position[1];
2023-01-03 06:53:32 +00:00
this . last _mouse _position [ 0 ] = clientX _rel ;
this . last _mouse _position [ 1 ] = clientY _rel ;
e . canvasX = clientX _rel / this . ds . scale - this . ds . offset [ 0 ] ;
e . canvasY = clientY _rel / this . ds . scale - this . ds . offset [ 1 ] ;
//console.log("pointerevents: adjustMouseEvent "+e.clientX+":"+e.clientY+" "+clientX_rel+":"+clientY_rel+" "+e.canvasX+":"+e.canvasY);
} ;
/ * *
* changes the zoom level of the graph ( default is 1 ) , you can pass also a place used to pivot the zoom
* @ method setZoom
* * /
LGraphCanvas . prototype . setZoom = function ( value , zooming _center ) {
this . ds . changeScale ( value , zooming _center ) ;
/ *
if ( ! zooming _center && this . canvas )
zooming _center = [ this . canvas . width * 0.5 , this . canvas . height * 0.5 ] ;
var center = this . convertOffsetToCanvas ( zooming _center ) ;
this . ds . scale = value ;
if ( this . scale > this . max _zoom )
this . scale = this . max _zoom ;
else if ( this . scale < this . min _zoom )
this . scale = this . min _zoom ;
var new _center = this . convertOffsetToCanvas ( zooming _center ) ;
var delta _offset = [ new _center [ 0 ] - center [ 0 ] , new _center [ 1 ] - center [ 1 ] ] ;
this . offset [ 0 ] += delta _offset [ 0 ] ;
this . offset [ 1 ] += delta _offset [ 1 ] ;
* /
this . dirty _canvas = true ;
this . dirty _bgcanvas = true ;
} ;
/ * *
* converts a coordinate from graph coordinates to canvas2D coordinates
* @ method convertOffsetToCanvas
* * /
LGraphCanvas . prototype . convertOffsetToCanvas = function ( pos , out ) {
return this . ds . convertOffsetToCanvas ( pos , out ) ;
} ;
/ * *
* converts a coordinate from Canvas2D coordinates to graph space
* @ method convertCanvasToOffset
* * /
LGraphCanvas . prototype . convertCanvasToOffset = function ( pos , out ) {
return this . ds . convertCanvasToOffset ( pos , out ) ;
} ;
//converts event coordinates from canvas2D to graph coordinates
LGraphCanvas . prototype . convertEventToCanvasOffset = function ( e ) {
var rect = this . canvas . getBoundingClientRect ( ) ;
return this . convertCanvasToOffset ( [
e . clientX - rect . left ,
e . clientY - rect . top
] ) ;
} ;
/ * *
* brings a node to front ( above all other nodes )
* @ method bringToFront
* * /
LGraphCanvas . prototype . bringToFront = function ( node ) {
var i = this . graph . _nodes . indexOf ( node ) ;
if ( i == - 1 ) {
return ;
}
this . graph . _nodes . splice ( i , 1 ) ;
this . graph . _nodes . push ( node ) ;
} ;
/ * *
* sends a node to the back ( below all other nodes )
* @ method sendToBack
* * /
LGraphCanvas . prototype . sendToBack = function ( node ) {
var i = this . graph . _nodes . indexOf ( node ) ;
if ( i == - 1 ) {
return ;
}
this . graph . _nodes . splice ( i , 1 ) ;
this . graph . _nodes . unshift ( node ) ;
} ;
/* Interaction */
/* LGraphCanvas render */
var temp = new Float32Array ( 4 ) ;
/ * *
* checks which nodes are visible ( inside the camera area )
* @ method computeVisibleNodes
* * /
LGraphCanvas . prototype . computeVisibleNodes = function ( nodes , out ) {
var visible _nodes = out || [ ] ;
visible _nodes . length = 0 ;
nodes = nodes || this . graph . _nodes ;
for ( var i = 0 , l = nodes . length ; i < l ; ++ i ) {
var n = nodes [ i ] ;
//skip rendering nodes in live mode
if ( this . live _mode && ! n . onDrawBackground && ! n . onDrawForeground ) {
continue ;
}
if ( ! overlapBounding ( this . visible _area , n . getBounding ( temp ) ) ) {
continue ;
} //out of the visible area
visible _nodes . push ( n ) ;
}
return visible _nodes ;
} ;
/ * *
* renders the whole canvas content , by rendering in two separated canvas , one containing the background grid and the connections , and one containing the nodes )
* @ method draw
* * /
LGraphCanvas . prototype . draw = function ( force _canvas , force _bgcanvas ) {
if ( ! this . canvas || this . canvas . width == 0 || this . canvas . height == 0 ) {
return ;
}
//fps counting
var now = LiteGraph . getTime ( ) ;
this . render _time = ( now - this . last _draw _time ) * 0.001 ;
this . last _draw _time = now ;
if ( this . graph ) {
this . ds . computeVisibleArea ( this . viewport ) ;
}
if (
this . dirty _bgcanvas ||
force _bgcanvas ||
this . always _render _background ||
( this . graph &&
this . graph . _last _trigger _time &&
now - this . graph . _last _trigger _time < 1000 )
) {
this . drawBackCanvas ( ) ;
}
if ( this . dirty _canvas || force _canvas ) {
this . drawFrontCanvas ( ) ;
}
this . fps = this . render _time ? 1.0 / this . render _time : 0 ;
this . frame += 1 ;
} ;
/ * *
* draws the front canvas ( the one containing all the nodes )
* @ method drawFrontCanvas
* * /
LGraphCanvas . prototype . drawFrontCanvas = function ( ) {
this . dirty _canvas = false ;
if ( ! this . ctx ) {
this . ctx = this . bgcanvas . getContext ( "2d" ) ;
}
var ctx = this . ctx ;
if ( ! ctx ) {
//maybe is using webgl...
return ;
}
var canvas = this . canvas ;
if ( ctx . start2D && ! this . viewport ) {
ctx . start2D ( ) ;
ctx . restore ( ) ;
ctx . setTransform ( 1 , 0 , 0 , 1 , 0 , 0 ) ;
}
//clip dirty area if there is one, otherwise work in full canvas
var area = this . viewport || this . dirty _area ;
if ( area ) {
ctx . save ( ) ;
ctx . beginPath ( ) ;
ctx . rect ( area [ 0 ] , area [ 1 ] , area [ 2 ] , area [ 3 ] ) ;
ctx . clip ( ) ;
}
//clear
//canvas.width = canvas.width;
if ( this . clear _background ) {
if ( area )
ctx . clearRect ( area [ 0 ] , area [ 1 ] , area [ 2 ] , area [ 3 ] ) ;
else
ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
}
//draw bg canvas
if ( this . bgcanvas == this . canvas ) {
this . drawBackCanvas ( ) ;
} else {
ctx . drawImage ( this . bgcanvas , 0 , 0 ) ;
}
//rendering
if ( this . onRender ) {
this . onRender ( canvas , ctx ) ;
}
//info widget
if ( this . show _info ) {
this . renderInfo ( ctx , area ? area [ 0 ] : 0 , area ? area [ 1 ] : 0 ) ;
}
if ( this . graph ) {
//apply transformations
ctx . save ( ) ;
this . ds . toCanvasContext ( ctx ) ;
//draw nodes
var drawn _nodes = 0 ;
var visible _nodes = this . computeVisibleNodes (
null ,
this . visible _nodes
) ;
for ( var i = 0 ; i < visible _nodes . length ; ++ i ) {
var node = visible _nodes [ i ] ;
//transform coords system
ctx . save ( ) ;
ctx . translate ( node . pos [ 0 ] , node . pos [ 1 ] ) ;
//Draw
this . drawNode ( node , ctx ) ;
drawn _nodes += 1 ;
//Restore
ctx . restore ( ) ;
}
//on top (debug)
if ( this . render _execution _order ) {
this . drawExecutionOrder ( ctx ) ;
}
//connections ontop?
if ( this . graph . config . links _ontop ) {
if ( ! this . live _mode ) {
this . drawConnections ( ctx ) ;
}
}
//current connection (the one being dragged by the mouse)
if ( this . connecting _pos != null ) {
ctx . lineWidth = this . connections _width ;
var link _color = null ;
var connInOrOut = this . connecting _output || this . connecting _input ;
var connType = connInOrOut . type ;
var connDir = connInOrOut . dir ;
if ( connDir == null )
{
if ( this . connecting _output )
connDir = this . connecting _node . horizontal ? LiteGraph . DOWN : LiteGraph . RIGHT ;
else
connDir = this . connecting _node . horizontal ? LiteGraph . UP : LiteGraph . LEFT ;
}
var connShape = connInOrOut . shape ;
switch ( connType ) {
case LiteGraph . EVENT :
link _color = LiteGraph . EVENT _LINK _COLOR ;
break ;
default :
link _color = LiteGraph . CONNECTING _LINK _COLOR ;
}
//the connection being dragged by the mouse
this . renderLink (
ctx ,
this . connecting _pos ,
[ this . graph _mouse [ 0 ] , this . graph _mouse [ 1 ] ] ,
null ,
false ,
null ,
link _color ,
connDir ,
LiteGraph . CENTER
) ;
ctx . beginPath ( ) ;
if (
connType === LiteGraph . EVENT ||
connShape === LiteGraph . BOX _SHAPE
) {
ctx . rect (
this . connecting _pos [ 0 ] - 6 + 0.5 ,
this . connecting _pos [ 1 ] - 5 + 0.5 ,
14 ,
10
) ;
ctx . fill ( ) ;
ctx . beginPath ( ) ;
ctx . rect (
this . graph _mouse [ 0 ] - 6 + 0.5 ,
this . graph _mouse [ 1 ] - 5 + 0.5 ,
14 ,
10
) ;
} else if ( connShape === LiteGraph . ARROW _SHAPE ) {
ctx . moveTo ( this . connecting _pos [ 0 ] + 8 , this . connecting _pos [ 1 ] + 0.5 ) ;
ctx . lineTo ( this . connecting _pos [ 0 ] - 4 , this . connecting _pos [ 1 ] + 6 + 0.5 ) ;
ctx . lineTo ( this . connecting _pos [ 0 ] - 4 , this . connecting _pos [ 1 ] - 6 + 0.5 ) ;
ctx . closePath ( ) ;
}
else {
ctx . arc (
this . connecting _pos [ 0 ] ,
this . connecting _pos [ 1 ] ,
4 ,
0 ,
Math . PI * 2
) ;
ctx . fill ( ) ;
ctx . beginPath ( ) ;
ctx . arc (
this . graph _mouse [ 0 ] ,
this . graph _mouse [ 1 ] ,
4 ,
0 ,
Math . PI * 2
) ;
}
ctx . fill ( ) ;
ctx . fillStyle = "#ffcc00" ;
if ( this . _highlight _input ) {
ctx . beginPath ( ) ;
var shape = this . _highlight _input _slot . shape ;
if ( shape === LiteGraph . ARROW _SHAPE ) {
ctx . moveTo ( this . _highlight _input [ 0 ] + 8 , this . _highlight _input [ 1 ] + 0.5 ) ;
ctx . lineTo ( this . _highlight _input [ 0 ] - 4 , this . _highlight _input [ 1 ] + 6 + 0.5 ) ;
ctx . lineTo ( this . _highlight _input [ 0 ] - 4 , this . _highlight _input [ 1 ] - 6 + 0.5 ) ;
ctx . closePath ( ) ;
} else {
ctx . arc (
this . _highlight _input [ 0 ] ,
this . _highlight _input [ 1 ] ,
6 ,
0 ,
Math . PI * 2
) ;
}
ctx . fill ( ) ;
}
if ( this . _highlight _output ) {
ctx . beginPath ( ) ;
if ( shape === LiteGraph . ARROW _SHAPE ) {
ctx . moveTo ( this . _highlight _output [ 0 ] + 8 , this . _highlight _output [ 1 ] + 0.5 ) ;
ctx . lineTo ( this . _highlight _output [ 0 ] - 4 , this . _highlight _output [ 1 ] + 6 + 0.5 ) ;
ctx . lineTo ( this . _highlight _output [ 0 ] - 4 , this . _highlight _output [ 1 ] - 6 + 0.5 ) ;
ctx . closePath ( ) ;
} else {
ctx . arc (
this . _highlight _output [ 0 ] ,
this . _highlight _output [ 1 ] ,
6 ,
0 ,
Math . PI * 2
) ;
}
ctx . fill ( ) ;
}
}
//the selection rectangle
if ( this . dragging _rectangle ) {
ctx . strokeStyle = "#FFF" ;
ctx . strokeRect (
this . dragging _rectangle [ 0 ] ,
this . dragging _rectangle [ 1 ] ,
this . dragging _rectangle [ 2 ] ,
this . dragging _rectangle [ 3 ]
) ;
}
//on top of link center
if ( this . over _link _center && this . render _link _tooltip )
this . drawLinkTooltip ( ctx , this . over _link _center ) ;
else
if ( this . onDrawLinkTooltip ) //to remove
this . onDrawLinkTooltip ( ctx , null ) ;
//custom info
if ( this . onDrawForeground ) {
this . onDrawForeground ( ctx , this . visible _rect ) ;
}
ctx . restore ( ) ;
}
//draws panel in the corner
if ( this . _graph _stack && this . _graph _stack . length ) {
this . drawSubgraphPanel ( ctx ) ;
}
if ( this . onDrawOverlay ) {
this . onDrawOverlay ( ctx ) ;
}
if ( area ) {
ctx . restore ( ) ;
}
if ( ctx . finish2D ) {
//this is a function I use in webgl renderer
ctx . finish2D ( ) ;
}
} ;
/ * *
* draws the panel in the corner that shows subgraph properties
* @ method drawSubgraphPanel
* * /
LGraphCanvas . prototype . drawSubgraphPanel = function ( ctx ) {
var subgraph = this . graph ;
var subnode = subgraph . _subgraph _node ;
if ( ! subnode ) {
console . warn ( "subgraph without subnode" ) ;
return ;
}
this . drawSubgraphPanelLeft ( subgraph , subnode , ctx )
this . drawSubgraphPanelRight ( subgraph , subnode , ctx )
}
LGraphCanvas . prototype . drawSubgraphPanelLeft = function ( subgraph , subnode , ctx ) {
var num = subnode . inputs ? subnode . inputs . length : 0 ;
var w = 200 ;
var h = Math . floor ( LiteGraph . NODE _SLOT _HEIGHT * 1.6 ) ;
ctx . fillStyle = "#111" ;
ctx . globalAlpha = 0.8 ;
ctx . beginPath ( ) ;
ctx . roundRect ( 10 , 10 , w , ( num + 1 ) * h + 50 , [ 8 ] ) ;
ctx . fill ( ) ;
ctx . globalAlpha = 1 ;
ctx . fillStyle = "#888" ;
ctx . font = "14px Arial" ;
ctx . textAlign = "left" ;
ctx . fillText ( "Graph Inputs" , 20 , 34 ) ;
// var pos = this.mouse;
if ( this . drawButton ( w - 20 , 20 , 20 , 20 , "X" , "#151515" ) ) {
this . closeSubgraph ( ) ;
return ;
}
var y = 50 ;
ctx . font = "14px Arial" ;
if ( subnode . inputs )
for ( var i = 0 ; i < subnode . inputs . length ; ++ i ) {
var input = subnode . inputs [ i ] ;
if ( input . not _subgraph _input )
continue ;
//input button clicked
if ( this . drawButton ( 20 , y + 2 , w - 20 , h - 2 ) ) {
var type = subnode . constructor . input _node _type || "graph/input" ;
this . graph . beforeChange ( ) ;
var newnode = LiteGraph . createNode ( type ) ;
if ( newnode ) {
subgraph . add ( newnode ) ;
this . block _click = false ;
this . last _click _position = null ;
this . selectNodes ( [ newnode ] ) ;
this . node _dragged = newnode ;
this . dragging _canvas = false ;
newnode . setProperty ( "name" , input . name ) ;
newnode . setProperty ( "type" , input . type ) ;
this . node _dragged . pos [ 0 ] = this . graph _mouse [ 0 ] - 5 ;
this . node _dragged . pos [ 1 ] = this . graph _mouse [ 1 ] - 5 ;
this . graph . afterChange ( ) ;
}
else
console . error ( "graph input node not found:" , type ) ;
}
ctx . fillStyle = "#9C9" ;
ctx . beginPath ( ) ;
ctx . arc ( w - 16 , y + h * 0.5 , 5 , 0 , 2 * Math . PI ) ;
ctx . fill ( ) ;
ctx . fillStyle = "#AAA" ;
ctx . fillText ( input . name , 30 , y + h * 0.75 ) ;
// var tw = ctx.measureText(input.name);
ctx . fillStyle = "#777" ;
ctx . fillText ( input . type , 130 , y + h * 0.75 ) ;
y += h ;
}
//add + button
if ( this . drawButton ( 20 , y + 2 , w - 20 , h - 2 , "+" , "#151515" , "#222" ) ) {
this . showSubgraphPropertiesDialog ( subnode ) ;
}
}
LGraphCanvas . prototype . drawSubgraphPanelRight = function ( subgraph , subnode , ctx ) {
var num = subnode . outputs ? subnode . outputs . length : 0 ;
var canvas _w = this . bgcanvas . width
var w = 200 ;
var h = Math . floor ( LiteGraph . NODE _SLOT _HEIGHT * 1.6 ) ;
ctx . fillStyle = "#111" ;
ctx . globalAlpha = 0.8 ;
ctx . beginPath ( ) ;
ctx . roundRect ( canvas _w - w - 10 , 10 , w , ( num + 1 ) * h + 50 , [ 8 ] ) ;
ctx . fill ( ) ;
ctx . globalAlpha = 1 ;
ctx . fillStyle = "#888" ;
ctx . font = "14px Arial" ;
ctx . textAlign = "left" ;
var title _text = "Graph Outputs"
var tw = ctx . measureText ( title _text ) . width
ctx . fillText ( title _text , ( canvas _w - tw ) - 20 , 34 ) ;
// var pos = this.mouse;
if ( this . drawButton ( canvas _w - w , 20 , 20 , 20 , "X" , "#151515" ) ) {
this . closeSubgraph ( ) ;
return ;
}
var y = 50 ;
ctx . font = "14px Arial" ;
if ( subnode . outputs )
for ( var i = 0 ; i < subnode . outputs . length ; ++ i ) {
var output = subnode . outputs [ i ] ;
if ( output . not _subgraph _input )
continue ;
//output button clicked
if ( this . drawButton ( canvas _w - w , y + 2 , w - 20 , h - 2 ) ) {
var type = subnode . constructor . output _node _type || "graph/output" ;
this . graph . beforeChange ( ) ;
var newnode = LiteGraph . createNode ( type ) ;
if ( newnode ) {
subgraph . add ( newnode ) ;
this . block _click = false ;
this . last _click _position = null ;
this . selectNodes ( [ newnode ] ) ;
this . node _dragged = newnode ;
this . dragging _canvas = false ;
newnode . setProperty ( "name" , output . name ) ;
newnode . setProperty ( "type" , output . type ) ;
this . node _dragged . pos [ 0 ] = this . graph _mouse [ 0 ] - 5 ;
this . node _dragged . pos [ 1 ] = this . graph _mouse [ 1 ] - 5 ;
this . graph . afterChange ( ) ;
}
else
console . error ( "graph input node not found:" , type ) ;
}
ctx . fillStyle = "#9C9" ;
ctx . beginPath ( ) ;
ctx . arc ( canvas _w - w + 16 , y + h * 0.5 , 5 , 0 , 2 * Math . PI ) ;
ctx . fill ( ) ;
ctx . fillStyle = "#AAA" ;
ctx . fillText ( output . name , canvas _w - w + 30 , y + h * 0.75 ) ;
// var tw = ctx.measureText(input.name);
ctx . fillStyle = "#777" ;
ctx . fillText ( output . type , canvas _w - w + 130 , y + h * 0.75 ) ;
y += h ;
}
//add + button
if ( this . drawButton ( canvas _w - w , y + 2 , w - 20 , h - 2 , "+" , "#151515" , "#222" ) ) {
this . showSubgraphPropertiesDialogRight ( subnode ) ;
}
}
//Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm
LGraphCanvas . prototype . drawButton = function ( x , y , w , h , text , bgcolor , hovercolor , textcolor )
{
var ctx = this . ctx ;
bgcolor = bgcolor || LiteGraph . NODE _DEFAULT _COLOR ;
hovercolor = hovercolor || "#555" ;
textcolor = textcolor || LiteGraph . NODE _TEXT _COLOR ;
var yFix = y + LiteGraph . NODE _TITLE _HEIGHT + 2 ; // fix the height with the title
var pos = this . mouse ;
var hover = LiteGraph . isInsideRectangle ( pos [ 0 ] , pos [ 1 ] , x , yFix , w , h ) ;
pos = this . last _click _position ;
var clicked = pos && LiteGraph . isInsideRectangle ( pos [ 0 ] , pos [ 1 ] , x , yFix , w , h ) ;
ctx . fillStyle = hover ? hovercolor : bgcolor ;
if ( clicked )
ctx . fillStyle = "#AAA" ;
ctx . beginPath ( ) ;
ctx . roundRect ( x , y , w , h , [ 4 ] ) ;
ctx . fill ( ) ;
if ( text != null )
{
if ( text . constructor == String )
{
ctx . fillStyle = textcolor ;
ctx . textAlign = "center" ;
ctx . font = ( ( h * 0.65 ) | 0 ) + "px Arial" ;
ctx . fillText ( text , x + w * 0.5 , y + h * 0.75 ) ;
ctx . textAlign = "left" ;
}
}
var was _clicked = clicked && ! this . block _click ;
if ( clicked )
this . blockClick ( ) ;
return was _clicked ;
}
LGraphCanvas . prototype . isAreaClicked = function ( x , y , w , h , hold _click )
{
var pos = this . mouse ;
var hover = LiteGraph . isInsideRectangle ( pos [ 0 ] , pos [ 1 ] , x , y , w , h ) ;
pos = this . last _click _position ;
var clicked = pos && LiteGraph . isInsideRectangle ( pos [ 0 ] , pos [ 1 ] , x , y , w , h ) ;
var was _clicked = clicked && ! this . block _click ;
if ( clicked && hold _click )
this . blockClick ( ) ;
return was _clicked ;
}
/ * *
* draws some useful stats in the corner of the canvas
* @ method renderInfo
* * /
LGraphCanvas . prototype . renderInfo = function ( ctx , x , y ) {
x = x || 10 ;
y = y || this . canvas . height - 80 ;
ctx . save ( ) ;
ctx . translate ( x , y ) ;
ctx . font = "10px Arial" ;
ctx . fillStyle = "#888" ;
ctx . textAlign = "left" ;
if ( this . graph ) {
ctx . fillText ( "T: " + this . graph . globaltime . toFixed ( 2 ) + "s" , 5 , 13 * 1 ) ;
ctx . fillText ( "I: " + this . graph . iteration , 5 , 13 * 2 ) ;
ctx . fillText ( "N: " + this . graph . _nodes . length + " [" + this . visible _nodes . length + "]" , 5 , 13 * 3 ) ;
ctx . fillText ( "V: " + this . graph . _version , 5 , 13 * 4 ) ;
ctx . fillText ( "FPS:" + this . fps . toFixed ( 2 ) , 5 , 13 * 5 ) ;
} else {
ctx . fillText ( "No graph selected" , 5 , 13 * 1 ) ;
}
ctx . restore ( ) ;
} ;
/ * *
* draws the back canvas ( the one containing the background and the connections )
* @ method drawBackCanvas
* * /
LGraphCanvas . prototype . drawBackCanvas = function ( ) {
var canvas = this . bgcanvas ;
if (
canvas . width != this . canvas . width ||
canvas . height != this . canvas . height
) {
canvas . width = this . canvas . width ;
canvas . height = this . canvas . height ;
}
if ( ! this . bgctx ) {
this . bgctx = this . bgcanvas . getContext ( "2d" ) ;
}
var ctx = this . bgctx ;
if ( ctx . start ) {
ctx . start ( ) ;
}
var viewport = this . viewport || [ 0 , 0 , ctx . canvas . width , ctx . canvas . height ] ;
//clear
if ( this . clear _background ) {
ctx . clearRect ( viewport [ 0 ] , viewport [ 1 ] , viewport [ 2 ] , viewport [ 3 ] ) ;
}
//show subgraph stack header
if ( this . _graph _stack && this . _graph _stack . length ) {
ctx . save ( ) ;
var parent _graph = this . _graph _stack [ this . _graph _stack . length - 1 ] ;
var subgraph _node = this . graph . _subgraph _node ;
ctx . strokeStyle = subgraph _node . bgcolor ;
ctx . lineWidth = 10 ;
ctx . strokeRect ( 1 , 1 , canvas . width - 2 , canvas . height - 2 ) ;
ctx . lineWidth = 1 ;
ctx . font = "40px Arial" ;
ctx . textAlign = "center" ;
ctx . fillStyle = subgraph _node . bgcolor || "#AAA" ;
var title = "" ;
for ( var i = 1 ; i < this . _graph _stack . length ; ++ i ) {
title +=
this . _graph _stack [ i ] . _subgraph _node . getTitle ( ) + " >> " ;
}
ctx . fillText (
title + subgraph _node . getTitle ( ) ,
canvas . width * 0.5 ,
40
) ;
ctx . restore ( ) ;
}
var bg _already _painted = false ;
if ( this . onRenderBackground ) {
bg _already _painted = this . onRenderBackground ( canvas , ctx ) ;
}
//reset in case of error
if ( ! this . viewport )
{
ctx . restore ( ) ;
ctx . setTransform ( 1 , 0 , 0 , 1 , 0 , 0 ) ;
}
this . visible _links . length = 0 ;
if ( this . graph ) {
//apply transformations
ctx . save ( ) ;
this . ds . toCanvasContext ( ctx ) ;
//render BG
if (
this . background _image &&
this . ds . scale > 0.5 &&
! bg _already _painted
) {
if ( this . zoom _modify _alpha ) {
ctx . globalAlpha =
( 1.0 - 0.5 / this . ds . scale ) * this . editor _alpha ;
} else {
ctx . globalAlpha = this . editor _alpha ;
}
ctx . imageSmoothingEnabled = ctx . imageSmoothingEnabled = false ; // ctx.mozImageSmoothingEnabled =
if (
! this . _bg _img ||
this . _bg _img . name != this . background _image
) {
this . _bg _img = new Image ( ) ;
this . _bg _img . name = this . background _image ;
this . _bg _img . src = this . background _image ;
var that = this ;
this . _bg _img . onload = function ( ) {
that . draw ( true , true ) ;
} ;
}
var pattern = null ;
if ( this . _pattern == null && this . _bg _img . width > 0 ) {
pattern = ctx . createPattern ( this . _bg _img , "repeat" ) ;
this . _pattern _img = this . _bg _img ;
this . _pattern = pattern ;
} else {
pattern = this . _pattern ;
}
if ( pattern ) {
ctx . fillStyle = pattern ;
ctx . fillRect (
this . visible _area [ 0 ] ,
this . visible _area [ 1 ] ,
this . visible _area [ 2 ] ,
this . visible _area [ 3 ]
) ;
ctx . fillStyle = "transparent" ;
}
ctx . globalAlpha = 1.0 ;
ctx . imageSmoothingEnabled = ctx . imageSmoothingEnabled = true ; //= ctx.mozImageSmoothingEnabled
}
//groups
if ( this . graph . _groups . length && ! this . live _mode ) {
this . drawGroups ( canvas , ctx ) ;
}
if ( this . onDrawBackground ) {
this . onDrawBackground ( ctx , this . visible _area ) ;
}
if ( this . onBackgroundRender ) {
//LEGACY
console . error (
"WARNING! onBackgroundRender deprecated, now is named onDrawBackground "
) ;
this . onBackgroundRender = null ;
}
//DEBUG: show clipping area
//ctx.fillStyle = "red";
//ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20);
//bg
if ( this . render _canvas _border ) {
ctx . strokeStyle = "#235" ;
ctx . strokeRect ( 0 , 0 , canvas . width , canvas . height ) ;
}
if ( this . render _connections _shadows ) {
ctx . shadowColor = "#000" ;
ctx . shadowOffsetX = 0 ;
ctx . shadowOffsetY = 0 ;
ctx . shadowBlur = 6 ;
} else {
ctx . shadowColor = "rgba(0,0,0,0)" ;
}
//draw connections
if ( ! this . live _mode ) {
this . drawConnections ( ctx ) ;
}
ctx . shadowColor = "rgba(0,0,0,0)" ;
//restore state
ctx . restore ( ) ;
}
if ( ctx . finish ) {
ctx . finish ( ) ;
}
this . dirty _bgcanvas = false ;
this . dirty _canvas = true ; //to force to repaint the front canvas with the bgcanvas
} ;
var temp _vec2 = new Float32Array ( 2 ) ;
/ * *
* draws the given node inside the canvas
* @ method drawNode
* * /
LGraphCanvas . prototype . drawNode = function ( node , ctx ) {
var glow = false ;
this . current _node = node ;
var color = node . color || node . constructor . color || LiteGraph . NODE _DEFAULT _COLOR ;
var bgcolor = node . bgcolor || node . constructor . bgcolor || LiteGraph . NODE _DEFAULT _BGCOLOR ;
//shadow and glow
if ( node . mouseOver ) {
glow = true ;
}
var low _quality = this . ds . scale < 0.6 ; //zoomed out
//only render if it forces it to do it
if ( this . live _mode ) {
if ( ! node . flags . collapsed ) {
ctx . shadowColor = "transparent" ;
if ( node . onDrawForeground ) {
node . onDrawForeground ( ctx , this , this . canvas ) ;
}
}
return ;
}
var editor _alpha = this . editor _alpha ;
ctx . globalAlpha = editor _alpha ;
if ( this . render _shadows && ! low _quality ) {
ctx . shadowColor = LiteGraph . DEFAULT _SHADOW _COLOR ;
ctx . shadowOffsetX = 2 * this . ds . scale ;
ctx . shadowOffsetY = 2 * this . ds . scale ;
ctx . shadowBlur = 3 * this . ds . scale ;
} else {
ctx . shadowColor = "transparent" ;
}
//custom draw collapsed method (draw after shadows because they are affected)
if (
node . flags . collapsed &&
node . onDrawCollapsed &&
node . onDrawCollapsed ( ctx , this ) == true
) {
return ;
}
//clip if required (mask)
var shape = node . _shape || LiteGraph . BOX _SHAPE ;
var size = temp _vec2 ;
temp _vec2 . set ( node . size ) ;
var horizontal = node . horizontal ; // || node.flags.horizontal;
if ( node . flags . collapsed ) {
ctx . font = this . inner _text _font ;
var title = node . getTitle ? node . getTitle ( ) : node . title ;
if ( title != null ) {
node . _collapsed _width = Math . min (
node . size [ 0 ] ,
ctx . measureText ( title ) . width +
LiteGraph . NODE _TITLE _HEIGHT * 2
) ; //LiteGraph.NODE_COLLAPSED_WIDTH;
size [ 0 ] = node . _collapsed _width ;
size [ 1 ] = 0 ;
}
}
if ( node . clip _area ) {
//Start clipping
ctx . save ( ) ;
ctx . beginPath ( ) ;
if ( shape == LiteGraph . BOX _SHAPE ) {
ctx . rect ( 0 , 0 , size [ 0 ] , size [ 1 ] ) ;
} else if ( shape == LiteGraph . ROUND _SHAPE ) {
ctx . roundRect ( 0 , 0 , size [ 0 ] , size [ 1 ] , [ 10 ] ) ;
} else if ( shape == LiteGraph . CIRCLE _SHAPE ) {
ctx . arc (
size [ 0 ] * 0.5 ,
size [ 1 ] * 0.5 ,
size [ 0 ] * 0.5 ,
0 ,
Math . PI * 2
) ;
}
ctx . clip ( ) ;
}
//draw shape
if ( node . has _errors ) {
bgcolor = "red" ;
}
this . drawNodeShape (
node ,
ctx ,
size ,
color ,
bgcolor ,
node . is _selected ,
node . mouseOver
) ;
ctx . shadowColor = "transparent" ;
//draw foreground
if ( node . onDrawForeground ) {
node . onDrawForeground ( ctx , this , this . canvas ) ;
}
//connection slots
ctx . textAlign = horizontal ? "center" : "left" ;
ctx . font = this . inner _text _font ;
var render _text = ! low _quality ;
var out _slot = this . connecting _output ;
var in _slot = this . connecting _input ;
ctx . lineWidth = 1 ;
var max _y = 0 ;
var slot _pos = new Float32Array ( 2 ) ; //to reuse
//render inputs and outputs
if ( ! node . flags . collapsed ) {
//input connection slots
if ( node . inputs ) {
for ( var i = 0 ; i < node . inputs . length ; i ++ ) {
var slot = node . inputs [ i ] ;
var slot _type = slot . type ;
var slot _shape = slot . shape ;
ctx . globalAlpha = editor _alpha ;
//change opacity of incompatible slots when dragging a connection
if ( this . connecting _output && ! LiteGraph . isValidConnection ( slot . type , out _slot . type ) ) {
ctx . globalAlpha = 0.4 * editor _alpha ;
}
ctx . fillStyle =
slot . link != null
? slot . color _on ||
this . default _connection _color _byType [ slot _type ] ||
this . default _connection _color . input _on
: slot . color _off ||
this . default _connection _color _byTypeOff [ slot _type ] ||
this . default _connection _color _byType [ slot _type ] ||
this . default _connection _color . input _off ;
var pos = node . getConnectionPos ( true , i , slot _pos ) ;
pos [ 0 ] -= node . pos [ 0 ] ;
pos [ 1 ] -= node . pos [ 1 ] ;
if ( max _y < pos [ 1 ] + LiteGraph . NODE _SLOT _HEIGHT * 0.5 ) {
max _y = pos [ 1 ] + LiteGraph . NODE _SLOT _HEIGHT * 0.5 ;
}
ctx . beginPath ( ) ;
if ( slot _type == "array" ) {
slot _shape = LiteGraph . GRID _SHAPE ; // place in addInput? addOutput instead?
}
var doStroke = true ;
if (
slot . type === LiteGraph . EVENT ||
slot . shape === LiteGraph . BOX _SHAPE
) {
if ( horizontal ) {
ctx . rect (
pos [ 0 ] - 5 + 0.5 ,
pos [ 1 ] - 8 + 0.5 ,
10 ,
14
) ;
} else {
ctx . rect (
pos [ 0 ] - 6 + 0.5 ,
pos [ 1 ] - 5 + 0.5 ,
14 ,
10
) ;
}
} else if ( slot _shape === LiteGraph . ARROW _SHAPE ) {
ctx . moveTo ( pos [ 0 ] + 8 , pos [ 1 ] + 0.5 ) ;
ctx . lineTo ( pos [ 0 ] - 4 , pos [ 1 ] + 6 + 0.5 ) ;
ctx . lineTo ( pos [ 0 ] - 4 , pos [ 1 ] - 6 + 0.5 ) ;
ctx . closePath ( ) ;
} else if ( slot _shape === LiteGraph . GRID _SHAPE ) {
ctx . rect ( pos [ 0 ] - 4 , pos [ 1 ] - 4 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] - 1 , pos [ 1 ] - 4 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] + 2 , pos [ 1 ] - 4 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] - 4 , pos [ 1 ] - 1 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] - 1 , pos [ 1 ] - 1 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] + 2 , pos [ 1 ] - 1 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] - 4 , pos [ 1 ] + 2 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] - 1 , pos [ 1 ] + 2 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] + 2 , pos [ 1 ] + 2 , 2 , 2 ) ;
doStroke = false ;
} else {
if ( low _quality )
ctx . rect ( pos [ 0 ] - 4 , pos [ 1 ] - 4 , 8 , 8 ) ; //faster
else
ctx . arc ( pos [ 0 ] , pos [ 1 ] , 4 , 0 , Math . PI * 2 ) ;
}
ctx . fill ( ) ;
//render name
if ( render _text ) {
var text = slot . label != null ? slot . label : slot . name ;
if ( text ) {
ctx . fillStyle = LiteGraph . NODE _TEXT _COLOR ;
if ( horizontal || slot . dir == LiteGraph . UP ) {
ctx . fillText ( text , pos [ 0 ] , pos [ 1 ] - 10 ) ;
} else {
ctx . fillText ( text , pos [ 0 ] + 10 , pos [ 1 ] + 5 ) ;
}
}
}
}
}
//output connection slots
ctx . textAlign = horizontal ? "center" : "right" ;
ctx . strokeStyle = "black" ;
if ( node . outputs ) {
for ( var i = 0 ; i < node . outputs . length ; i ++ ) {
var slot = node . outputs [ i ] ;
var slot _type = slot . type ;
var slot _shape = slot . shape ;
//change opacity of incompatible slots when dragging a connection
if ( this . connecting _input && ! LiteGraph . isValidConnection ( slot _type , in _slot . type ) ) {
ctx . globalAlpha = 0.4 * editor _alpha ;
}
var pos = node . getConnectionPos ( false , i , slot _pos ) ;
pos [ 0 ] -= node . pos [ 0 ] ;
pos [ 1 ] -= node . pos [ 1 ] ;
if ( max _y < pos [ 1 ] + LiteGraph . NODE _SLOT _HEIGHT * 0.5 ) {
max _y = pos [ 1 ] + LiteGraph . NODE _SLOT _HEIGHT * 0.5 ;
}
ctx . fillStyle =
slot . links && slot . links . length
? slot . color _on ||
this . default _connection _color _byType [ slot _type ] ||
this . default _connection _color . output _on
: slot . color _off ||
this . default _connection _color _byTypeOff [ slot _type ] ||
this . default _connection _color _byType [ slot _type ] ||
this . default _connection _color . output _off ;
ctx . beginPath ( ) ;
//ctx.rect( node.size[0] - 14,i*14,10,10);
if ( slot _type == "array" ) {
slot _shape = LiteGraph . GRID _SHAPE ;
}
var doStroke = true ;
if (
slot _type === LiteGraph . EVENT ||
slot _shape === LiteGraph . BOX _SHAPE
) {
if ( horizontal ) {
ctx . rect (
pos [ 0 ] - 5 + 0.5 ,
pos [ 1 ] - 8 + 0.5 ,
10 ,
14
) ;
} else {
ctx . rect (
pos [ 0 ] - 6 + 0.5 ,
pos [ 1 ] - 5 + 0.5 ,
14 ,
10
) ;
}
} else if ( slot _shape === LiteGraph . ARROW _SHAPE ) {
ctx . moveTo ( pos [ 0 ] + 8 , pos [ 1 ] + 0.5 ) ;
ctx . lineTo ( pos [ 0 ] - 4 , pos [ 1 ] + 6 + 0.5 ) ;
ctx . lineTo ( pos [ 0 ] - 4 , pos [ 1 ] - 6 + 0.5 ) ;
ctx . closePath ( ) ;
} else if ( slot _shape === LiteGraph . GRID _SHAPE ) {
ctx . rect ( pos [ 0 ] - 4 , pos [ 1 ] - 4 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] - 1 , pos [ 1 ] - 4 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] + 2 , pos [ 1 ] - 4 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] - 4 , pos [ 1 ] - 1 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] - 1 , pos [ 1 ] - 1 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] + 2 , pos [ 1 ] - 1 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] - 4 , pos [ 1 ] + 2 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] - 1 , pos [ 1 ] + 2 , 2 , 2 ) ;
ctx . rect ( pos [ 0 ] + 2 , pos [ 1 ] + 2 , 2 , 2 ) ;
doStroke = false ;
} else {
if ( low _quality )
ctx . rect ( pos [ 0 ] - 4 , pos [ 1 ] - 4 , 8 , 8 ) ;
else
ctx . arc ( pos [ 0 ] , pos [ 1 ] , 4 , 0 , Math . PI * 2 ) ;
}
//trigger
//if(slot.node_id != null && slot.slot == -1)
// ctx.fillStyle = "#F85";
//if(slot.links != null && slot.links.length)
ctx . fill ( ) ;
if ( ! low _quality && doStroke )
ctx . stroke ( ) ;
//render output name
if ( render _text ) {
var text = slot . label != null ? slot . label : slot . name ;
if ( text ) {
ctx . fillStyle = LiteGraph . NODE _TEXT _COLOR ;
if ( horizontal || slot . dir == LiteGraph . DOWN ) {
ctx . fillText ( text , pos [ 0 ] , pos [ 1 ] - 8 ) ;
} else {
ctx . fillText ( text , pos [ 0 ] - 10 , pos [ 1 ] + 5 ) ;
}
}
}
}
}
ctx . textAlign = "left" ;
ctx . globalAlpha = 1 ;
if ( node . widgets ) {
var widgets _y = max _y ;
if ( horizontal || node . widgets _up ) {
widgets _y = 2 ;
}
if ( node . widgets _start _y != null )
widgets _y = node . widgets _start _y ;
this . drawNodeWidgets (
node ,
widgets _y ,
ctx ,
this . node _widget && this . node _widget [ 0 ] == node
? this . node _widget [ 1 ]
: null
) ;
}
} else if ( this . render _collapsed _slots ) {
//if collapsed
var input _slot = null ;
var output _slot = null ;
//get first connected slot to render
if ( node . inputs ) {
for ( var i = 0 ; i < node . inputs . length ; i ++ ) {
var slot = node . inputs [ i ] ;
if ( slot . link == null ) {
continue ;
}
input _slot = slot ;
break ;
}
}
if ( node . outputs ) {
for ( var i = 0 ; i < node . outputs . length ; i ++ ) {
var slot = node . outputs [ i ] ;
if ( ! slot . links || ! slot . links . length ) {
continue ;
}
output _slot = slot ;
}
}
if ( input _slot ) {
var x = 0 ;
var y = LiteGraph . NODE _TITLE _HEIGHT * - 0.5 ; //center
if ( horizontal ) {
x = node . _collapsed _width * 0.5 ;
y = - LiteGraph . NODE _TITLE _HEIGHT ;
}
ctx . fillStyle = "#686" ;
ctx . beginPath ( ) ;
if (
slot . type === LiteGraph . EVENT ||
slot . shape === LiteGraph . BOX _SHAPE
) {
ctx . rect ( x - 7 + 0.5 , y - 4 , 14 , 8 ) ;
} else if ( slot . shape === LiteGraph . ARROW _SHAPE ) {
ctx . moveTo ( x + 8 , y ) ;
ctx . lineTo ( x + - 4 , y - 4 ) ;
ctx . lineTo ( x + - 4 , y + 4 ) ;
ctx . closePath ( ) ;
} else {
ctx . arc ( x , y , 4 , 0 , Math . PI * 2 ) ;
}
ctx . fill ( ) ;
}
if ( output _slot ) {
var x = node . _collapsed _width ;
var y = LiteGraph . NODE _TITLE _HEIGHT * - 0.5 ; //center
if ( horizontal ) {
x = node . _collapsed _width * 0.5 ;
y = 0 ;
}
ctx . fillStyle = "#686" ;
ctx . strokeStyle = "black" ;
ctx . beginPath ( ) ;
if (
slot . type === LiteGraph . EVENT ||
slot . shape === LiteGraph . BOX _SHAPE
) {
ctx . rect ( x - 7 + 0.5 , y - 4 , 14 , 8 ) ;
} else if ( slot . shape === LiteGraph . ARROW _SHAPE ) {
ctx . moveTo ( x + 6 , y ) ;
ctx . lineTo ( x - 6 , y - 4 ) ;
ctx . lineTo ( x - 6 , y + 4 ) ;
ctx . closePath ( ) ;
} else {
ctx . arc ( x , y , 4 , 0 , Math . PI * 2 ) ;
}
ctx . fill ( ) ;
//ctx.stroke();
}
}
if ( node . clip _area ) {
ctx . restore ( ) ;
}
ctx . globalAlpha = 1.0 ;
} ;
//used by this.over_link_center
LGraphCanvas . prototype . drawLinkTooltip = function ( ctx , link )
{
var pos = link . _pos ;
ctx . fillStyle = "black" ;
ctx . beginPath ( ) ;
ctx . arc ( pos [ 0 ] , pos [ 1 ] , 3 , 0 , Math . PI * 2 ) ;
ctx . fill ( ) ;
if ( link . data == null )
return ;
if ( this . onDrawLinkTooltip )
if ( this . onDrawLinkTooltip ( ctx , link , this ) == true )
return ;
var data = link . data ;
var text = null ;
if ( data . constructor === Number )
text = data . toFixed ( 2 ) ;
else if ( data . constructor === String )
text = "\"" + data + "\"" ;
else if ( data . constructor === Boolean )
text = String ( data ) ;
else if ( data . toToolTip )
text = data . toToolTip ( ) ;
else
text = "[" + data . constructor . name + "]" ;
if ( text == null )
return ;
text = text . substr ( 0 , 30 ) ; //avoid weird
ctx . font = "14px Courier New" ;
var info = ctx . measureText ( text ) ;
var w = info . width + 20 ;
var h = 24 ;
ctx . shadowColor = "black" ;
ctx . shadowOffsetX = 2 ;
ctx . shadowOffsetY = 2 ;
ctx . shadowBlur = 3 ;
ctx . fillStyle = "#454" ;
ctx . beginPath ( ) ;
ctx . roundRect ( pos [ 0 ] - w * 0.5 , pos [ 1 ] - 15 - h , w , h , [ 3 ] ) ;
ctx . moveTo ( pos [ 0 ] - 10 , pos [ 1 ] - 15 ) ;
ctx . lineTo ( pos [ 0 ] + 10 , pos [ 1 ] - 15 ) ;
ctx . lineTo ( pos [ 0 ] , pos [ 1 ] - 5 ) ;
ctx . fill ( ) ;
ctx . shadowColor = "transparent" ;
ctx . textAlign = "center" ;
ctx . fillStyle = "#CEC" ;
ctx . fillText ( text , pos [ 0 ] , pos [ 1 ] - 15 - h * 0.3 ) ;
}
/ * *
* draws the shape of the given node in the canvas
* @ method drawNodeShape
* * /
var tmp _area = new Float32Array ( 4 ) ;
LGraphCanvas . prototype . drawNodeShape = function (
node ,
ctx ,
size ,
fgcolor ,
bgcolor ,
selected ,
mouse _over
) {
//bg rect
ctx . strokeStyle = fgcolor ;
ctx . fillStyle = bgcolor ;
var title _height = LiteGraph . NODE _TITLE _HEIGHT ;
var low _quality = this . ds . scale < 0.5 ;
//render node area depending on shape
var shape =
node . _shape || node . constructor . shape || LiteGraph . ROUND _SHAPE ;
var title _mode = node . constructor . title _mode ;
var render _title = true ;
if ( title _mode == LiteGraph . TRANSPARENT _TITLE || title _mode == LiteGraph . NO _TITLE ) {
render _title = false ;
} else if ( title _mode == LiteGraph . AUTOHIDE _TITLE && mouse _over ) {
render _title = true ;
}
var area = tmp _area ;
area [ 0 ] = 0 ; //x
area [ 1 ] = render _title ? - title _height : 0 ; //y
area [ 2 ] = size [ 0 ] + 1 ; //w
area [ 3 ] = render _title ? size [ 1 ] + title _height : size [ 1 ] ; //h
var old _alpha = ctx . globalAlpha ;
//full node shape
//if(node.flags.collapsed)
{
ctx . beginPath ( ) ;
if ( shape == LiteGraph . BOX _SHAPE || low _quality ) {
ctx . fillRect ( area [ 0 ] , area [ 1 ] , area [ 2 ] , area [ 3 ] ) ;
} else if (
shape == LiteGraph . ROUND _SHAPE ||
shape == LiteGraph . CARD _SHAPE
) {
ctx . roundRect (
area [ 0 ] ,
area [ 1 ] ,
area [ 2 ] ,
area [ 3 ] ,
shape == LiteGraph . CARD _SHAPE ? [ this . round _radius , this . round _radius , 0 , 0 ] : [ this . round _radius ]
) ;
} else if ( shape == LiteGraph . CIRCLE _SHAPE ) {
ctx . arc (
size [ 0 ] * 0.5 ,
size [ 1 ] * 0.5 ,
size [ 0 ] * 0.5 ,
0 ,
Math . PI * 2
) ;
}
ctx . fill ( ) ;
//separator
if ( ! node . flags . collapsed && render _title )
{
ctx . shadowColor = "transparent" ;
ctx . fillStyle = "rgba(0,0,0,0.2)" ;
ctx . fillRect ( 0 , - 1 , area [ 2 ] , 2 ) ;
}
}
ctx . shadowColor = "transparent" ;
if ( node . onDrawBackground ) {
node . onDrawBackground ( ctx , this , this . canvas , this . graph _mouse ) ;
}
//title bg (remember, it is rendered ABOVE the node)
if ( render _title || title _mode == LiteGraph . TRANSPARENT _TITLE ) {
//title bar
if ( node . onDrawTitleBar ) {
node . onDrawTitleBar ( ctx , title _height , size , this . ds . scale , fgcolor ) ;
} else if (
title _mode != LiteGraph . TRANSPARENT _TITLE &&
( node . constructor . title _color || this . render _title _colored )
) {
var title _color = node . constructor . title _color || fgcolor ;
if ( node . flags . collapsed ) {
ctx . shadowColor = LiteGraph . DEFAULT _SHADOW _COLOR ;
}
//* gradient test
if ( this . use _gradients ) {
var grad = LGraphCanvas . gradients [ title _color ] ;
if ( ! grad ) {
grad = LGraphCanvas . gradients [ title _color ] = ctx . createLinearGradient ( 0 , 0 , 400 , 0 ) ;
grad . addColorStop ( 0 , title _color ) ; // TODO refactor: validate color !! prevent DOMException
grad . addColorStop ( 1 , "#000" ) ;
}
ctx . fillStyle = grad ;
} else {
ctx . fillStyle = title _color ;
}
//ctx.globalAlpha = 0.5 * old_alpha;
ctx . beginPath ( ) ;
if ( shape == LiteGraph . BOX _SHAPE || low _quality ) {
ctx . rect ( 0 , - title _height , size [ 0 ] + 1 , title _height ) ;
} else if ( shape == LiteGraph . ROUND _SHAPE || shape == LiteGraph . CARD _SHAPE ) {
ctx . roundRect (
0 ,
- title _height ,
size [ 0 ] + 1 ,
title _height ,
node . flags . collapsed ? [ this . round _radius ] : [ this . round _radius , this . round _radius , 0 , 0 ]
) ;
}
ctx . fill ( ) ;
ctx . shadowColor = "transparent" ;
}
var colState = false ;
if ( LiteGraph . node _box _coloured _by _mode ) {
if ( LiteGraph . NODE _MODES _COLORS [ node . mode ] ) {
colState = LiteGraph . NODE _MODES _COLORS [ node . mode ] ;
}
}
if ( LiteGraph . node _box _coloured _when _on ) {
colState = node . action _triggered ? "#FFF" : ( node . execute _triggered ? "#AAA" : colState ) ;
}
//title box
var box _size = 10 ;
if ( node . onDrawTitleBox ) {
node . onDrawTitleBox ( ctx , title _height , size , this . ds . scale ) ;
} else if (
shape == LiteGraph . ROUND _SHAPE ||
shape == LiteGraph . CIRCLE _SHAPE ||
shape == LiteGraph . CARD _SHAPE
) {
if ( low _quality ) {
ctx . fillStyle = "black" ;
ctx . beginPath ( ) ;
ctx . arc (
title _height * 0.5 ,
title _height * - 0.5 ,
box _size * 0.5 + 1 ,
0 ,
Math . PI * 2
) ;
ctx . fill ( ) ;
}
ctx . fillStyle = node . boxcolor || colState || LiteGraph . NODE _DEFAULT _BOXCOLOR ;
if ( low _quality )
ctx . fillRect ( title _height * 0.5 - box _size * 0.5 , title _height * - 0.5 - box _size * 0.5 , box _size , box _size ) ;
else
{
ctx . beginPath ( ) ;
ctx . arc (
title _height * 0.5 ,
title _height * - 0.5 ,
box _size * 0.5 ,
0 ,
Math . PI * 2
) ;
ctx . fill ( ) ;
}
} else {
if ( low _quality ) {
ctx . fillStyle = "black" ;
ctx . fillRect (
( title _height - box _size ) * 0.5 - 1 ,
( title _height + box _size ) * - 0.5 - 1 ,
box _size + 2 ,
box _size + 2
) ;
}
ctx . fillStyle = node . boxcolor || colState || LiteGraph . NODE _DEFAULT _BOXCOLOR ;
ctx . fillRect (
( title _height - box _size ) * 0.5 ,
( title _height + box _size ) * - 0.5 ,
box _size ,
box _size
) ;
}
ctx . globalAlpha = old _alpha ;
//title text
if ( node . onDrawTitleText ) {
node . onDrawTitleText (
ctx ,
title _height ,
size ,
this . ds . scale ,
this . title _text _font ,
selected
) ;
}
if ( ! low _quality ) {
ctx . font = this . title _text _font ;
var title = String ( node . getTitle ( ) ) ;
if ( title ) {
if ( selected ) {
ctx . fillStyle = LiteGraph . NODE _SELECTED _TITLE _COLOR ;
} else {
ctx . fillStyle =
node . constructor . title _text _color ||
this . node _title _color ;
}
if ( node . flags . collapsed ) {
ctx . textAlign = "left" ;
var measure = ctx . measureText ( title ) ;
ctx . fillText (
title . substr ( 0 , 20 ) , //avoid urls too long
title _height , // + measure.width * 0.5,
LiteGraph . NODE _TITLE _TEXT _Y - title _height
) ;
ctx . textAlign = "left" ;
} else {
ctx . textAlign = "left" ;
ctx . fillText (
title ,
title _height ,
LiteGraph . NODE _TITLE _TEXT _Y - title _height
) ;
}
}
}
//subgraph box
if ( ! node . flags . collapsed && node . subgraph && ! node . skip _subgraph _button ) {
var w = LiteGraph . NODE _TITLE _HEIGHT ;
var x = node . size [ 0 ] - w ;
var over = LiteGraph . isInsideRectangle ( this . graph _mouse [ 0 ] - node . pos [ 0 ] , this . graph _mouse [ 1 ] - node . pos [ 1 ] , x + 2 , - w + 2 , w - 4 , w - 4 ) ;
ctx . fillStyle = over ? "#888" : "#555" ;
if ( shape == LiteGraph . BOX _SHAPE || low _quality )
ctx . fillRect ( x + 2 , - w + 2 , w - 4 , w - 4 ) ;
else
{
ctx . beginPath ( ) ;
ctx . roundRect ( x + 2 , - w + 2 , w - 4 , w - 4 , [ 4 ] ) ;
ctx . fill ( ) ;
}
ctx . fillStyle = "#333" ;
ctx . beginPath ( ) ;
ctx . moveTo ( x + w * 0.2 , - w * 0.6 ) ;
ctx . lineTo ( x + w * 0.8 , - w * 0.6 ) ;
ctx . lineTo ( x + w * 0.5 , - w * 0.3 ) ;
ctx . fill ( ) ;
}
//custom title render
if ( node . onDrawTitle ) {
node . onDrawTitle ( ctx ) ;
}
}
//render selection marker
if ( selected ) {
if ( node . onBounding ) {
node . onBounding ( area ) ;
}
if ( title _mode == LiteGraph . TRANSPARENT _TITLE ) {
area [ 1 ] -= title _height ;
area [ 3 ] += title _height ;
}
ctx . lineWidth = 1 ;
ctx . globalAlpha = 0.8 ;
ctx . beginPath ( ) ;
if ( shape == LiteGraph . BOX _SHAPE ) {
ctx . rect (
- 6 + area [ 0 ] ,
- 6 + area [ 1 ] ,
12 + area [ 2 ] ,
12 + area [ 3 ]
) ;
} else if (
shape == LiteGraph . ROUND _SHAPE ||
( shape == LiteGraph . CARD _SHAPE && node . flags . collapsed )
) {
ctx . roundRect (
- 6 + area [ 0 ] ,
- 6 + area [ 1 ] ,
12 + area [ 2 ] ,
12 + area [ 3 ] ,
[ this . round _radius * 2 ]
) ;
} else if ( shape == LiteGraph . CARD _SHAPE ) {
ctx . roundRect (
- 6 + area [ 0 ] ,
- 6 + area [ 1 ] ,
12 + area [ 2 ] ,
12 + area [ 3 ] ,
[ this . round _radius * 2 , 2 , this . round _radius * 2 , 2 ]
) ;
} else if ( shape == LiteGraph . CIRCLE _SHAPE ) {
ctx . arc (
size [ 0 ] * 0.5 ,
size [ 1 ] * 0.5 ,
size [ 0 ] * 0.5 + 6 ,
0 ,
Math . PI * 2
) ;
}
ctx . strokeStyle = LiteGraph . NODE _BOX _OUTLINE _COLOR ;
ctx . stroke ( ) ;
ctx . strokeStyle = fgcolor ;
ctx . globalAlpha = 1 ;
}
// these counter helps in conditioning drawing based on if the node has been executed or an action occurred
if ( node . execute _triggered > 0 ) node . execute _triggered -- ;
if ( node . action _triggered > 0 ) node . action _triggered -- ;
} ;
var margin _area = new Float32Array ( 4 ) ;
var link _bounding = new Float32Array ( 4 ) ;
var tempA = new Float32Array ( 2 ) ;
var tempB = new Float32Array ( 2 ) ;
/ * *
* draws every connection visible in the canvas
* OPTIMIZE THIS : pre - catch connections position instead of recomputing them every time
* @ method drawConnections
* * /
LGraphCanvas . prototype . drawConnections = function ( ctx ) {
var now = LiteGraph . getTime ( ) ;
var visible _area = this . visible _area ;
margin _area [ 0 ] = visible _area [ 0 ] - 20 ;
margin _area [ 1 ] = visible _area [ 1 ] - 20 ;
margin _area [ 2 ] = visible _area [ 2 ] + 40 ;
margin _area [ 3 ] = visible _area [ 3 ] + 40 ;
//draw connections
ctx . lineWidth = this . connections _width ;
ctx . fillStyle = "#AAA" ;
ctx . strokeStyle = "#AAA" ;
ctx . globalAlpha = this . editor _alpha ;
//for every node
var nodes = this . graph . _nodes ;
for ( var n = 0 , l = nodes . length ; n < l ; ++ n ) {
var node = nodes [ n ] ;
//for every input (we render just inputs because it is easier as every slot can only have one input)
if ( ! node . inputs || ! node . inputs . length ) {
continue ;
}
for ( var i = 0 ; i < node . inputs . length ; ++ i ) {
var input = node . inputs [ i ] ;
if ( ! input || input . link == null ) {
continue ;
}
var link _id = input . link ;
var link = this . graph . links [ link _id ] ;
if ( ! link ) {
continue ;
}
//find link info
var start _node = this . graph . getNodeById ( link . origin _id ) ;
if ( start _node == null ) {
continue ;
}
var start _node _slot = link . origin _slot ;
var start _node _slotpos = null ;
if ( start _node _slot == - 1 ) {
start _node _slotpos = [
start _node . pos [ 0 ] + 10 ,
start _node . pos [ 1 ] + 10
] ;
} else {
start _node _slotpos = start _node . getConnectionPos (
false ,
start _node _slot ,
tempA
) ;
}
var end _node _slotpos = node . getConnectionPos ( true , i , tempB ) ;
//compute link bounding
link _bounding [ 0 ] = start _node _slotpos [ 0 ] ;
link _bounding [ 1 ] = start _node _slotpos [ 1 ] ;
link _bounding [ 2 ] = end _node _slotpos [ 0 ] - start _node _slotpos [ 0 ] ;
link _bounding [ 3 ] = end _node _slotpos [ 1 ] - start _node _slotpos [ 1 ] ;
if ( link _bounding [ 2 ] < 0 ) {
link _bounding [ 0 ] += link _bounding [ 2 ] ;
link _bounding [ 2 ] = Math . abs ( link _bounding [ 2 ] ) ;
}
if ( link _bounding [ 3 ] < 0 ) {
link _bounding [ 1 ] += link _bounding [ 3 ] ;
link _bounding [ 3 ] = Math . abs ( link _bounding [ 3 ] ) ;
}
//skip links outside of the visible area of the canvas
if ( ! overlapBounding ( link _bounding , margin _area ) ) {
continue ;
}
var start _slot = start _node . outputs [ start _node _slot ] ;
var end _slot = node . inputs [ i ] ;
if ( ! start _slot || ! end _slot ) {
continue ;
}
var start _dir =
start _slot . dir ||
( start _node . horizontal ? LiteGraph . DOWN : LiteGraph . RIGHT ) ;
var end _dir =
end _slot . dir ||
( node . horizontal ? LiteGraph . UP : LiteGraph . LEFT ) ;
this . renderLink (
ctx ,
start _node _slotpos ,
end _node _slotpos ,
link ,
false ,
0 ,
null ,
start _dir ,
end _dir
) ;
//event triggered rendered on top
if ( link && link . _last _time && now - link . _last _time < 1000 ) {
var f = 2.0 - ( now - link . _last _time ) * 0.002 ;
var tmp = ctx . globalAlpha ;
ctx . globalAlpha = tmp * f ;
this . renderLink (
ctx ,
start _node _slotpos ,
end _node _slotpos ,
link ,
true ,
f ,
"white" ,
start _dir ,
end _dir
) ;
ctx . globalAlpha = tmp ;
}
}
}
ctx . globalAlpha = 1 ;
} ;
/ * *
* draws a link between two points
* @ method renderLink
* @ param { vec2 } a start pos
* @ param { vec2 } b end pos
* @ param { Object } link the link object with all the link info
* @ param { boolean } skip _border ignore the shadow of the link
* @ param { boolean } flow show flow animation ( for events )
* @ param { string } color the color for the link
* @ param { number } start _dir the direction enum
* @ param { number } end _dir the direction enum
* @ param { number } num _sublines number of sublines ( useful to represent vec3 or rgb )
* * /
LGraphCanvas . prototype . renderLink = function (
ctx ,
a ,
b ,
link ,
skip _border ,
flow ,
color ,
start _dir ,
end _dir ,
num _sublines
) {
if ( link ) {
this . visible _links . push ( link ) ;
}
//choose color
if ( ! color && link ) {
color = link . color || LGraphCanvas . link _type _colors [ link . type ] ;
}
if ( ! color ) {
color = this . default _link _color ;
}
if ( link != null && this . highlighted _links [ link . id ] ) {
color = "#FFF" ;
}
start _dir = start _dir || LiteGraph . RIGHT ;
end _dir = end _dir || LiteGraph . LEFT ;
var dist = distance ( a , b ) ;
if ( this . render _connections _border && this . ds . scale > 0.6 ) {
ctx . lineWidth = this . connections _width + 4 ;
}
ctx . lineJoin = "round" ;
num _sublines = num _sublines || 1 ;
if ( num _sublines > 1 ) {
ctx . lineWidth = 0.5 ;
}
//begin line shape
ctx . beginPath ( ) ;
for ( var i = 0 ; i < num _sublines ; i += 1 ) {
var offsety = ( i - ( num _sublines - 1 ) * 0.5 ) * 5 ;
if ( this . links _render _mode == LiteGraph . SPLINE _LINK ) {
ctx . moveTo ( a [ 0 ] , a [ 1 ] + offsety ) ;
var start _offset _x = 0 ;
var start _offset _y = 0 ;
var end _offset _x = 0 ;
var end _offset _y = 0 ;
switch ( start _dir ) {
case LiteGraph . LEFT :
start _offset _x = dist * - 0.25 ;
break ;
case LiteGraph . RIGHT :
start _offset _x = dist * 0.25 ;
break ;
case LiteGraph . UP :
start _offset _y = dist * - 0.25 ;
break ;
case LiteGraph . DOWN :
start _offset _y = dist * 0.25 ;
break ;
}
switch ( end _dir ) {
case LiteGraph . LEFT :
end _offset _x = dist * - 0.25 ;
break ;
case LiteGraph . RIGHT :
end _offset _x = dist * 0.25 ;
break ;
case LiteGraph . UP :
end _offset _y = dist * - 0.25 ;
break ;
case LiteGraph . DOWN :
end _offset _y = dist * 0.25 ;
break ;
}
ctx . bezierCurveTo (
a [ 0 ] + start _offset _x ,
a [ 1 ] + start _offset _y + offsety ,
b [ 0 ] + end _offset _x ,
b [ 1 ] + end _offset _y + offsety ,
b [ 0 ] ,
b [ 1 ] + offsety
) ;
} else if ( this . links _render _mode == LiteGraph . LINEAR _LINK ) {
ctx . moveTo ( a [ 0 ] , a [ 1 ] + offsety ) ;
var start _offset _x = 0 ;
var start _offset _y = 0 ;
var end _offset _x = 0 ;
var end _offset _y = 0 ;
switch ( start _dir ) {
case LiteGraph . LEFT :
start _offset _x = - 1 ;
break ;
case LiteGraph . RIGHT :
start _offset _x = 1 ;
break ;
case LiteGraph . UP :
start _offset _y = - 1 ;
break ;
case LiteGraph . DOWN :
start _offset _y = 1 ;
break ;
}
switch ( end _dir ) {
case LiteGraph . LEFT :
end _offset _x = - 1 ;
break ;
case LiteGraph . RIGHT :
end _offset _x = 1 ;
break ;
case LiteGraph . UP :
end _offset _y = - 1 ;
break ;
case LiteGraph . DOWN :
end _offset _y = 1 ;
break ;
}
var l = 15 ;
ctx . lineTo (
a [ 0 ] + start _offset _x * l ,
a [ 1 ] + start _offset _y * l + offsety
) ;
ctx . lineTo (
b [ 0 ] + end _offset _x * l ,
b [ 1 ] + end _offset _y * l + offsety
) ;
ctx . lineTo ( b [ 0 ] , b [ 1 ] + offsety ) ;
} else if ( this . links _render _mode == LiteGraph . STRAIGHT _LINK ) {
ctx . moveTo ( a [ 0 ] , a [ 1 ] ) ;
var start _x = a [ 0 ] ;
var start _y = a [ 1 ] ;
var end _x = b [ 0 ] ;
var end _y = b [ 1 ] ;
if ( start _dir == LiteGraph . RIGHT ) {
start _x += 10 ;
} else {
start _y += 10 ;
}
if ( end _dir == LiteGraph . LEFT ) {
end _x -= 10 ;
} else {
end _y -= 10 ;
}
ctx . lineTo ( start _x , start _y ) ;
ctx . lineTo ( ( start _x + end _x ) * 0.5 , start _y ) ;
ctx . lineTo ( ( start _x + end _x ) * 0.5 , end _y ) ;
ctx . lineTo ( end _x , end _y ) ;
ctx . lineTo ( b [ 0 ] , b [ 1 ] ) ;
} else {
return ;
} //unknown
}
//rendering the outline of the connection can be a little bit slow
if (
this . render _connections _border &&
this . ds . scale > 0.6 &&
! skip _border
) {
ctx . strokeStyle = "rgba(0,0,0,0.5)" ;
ctx . stroke ( ) ;
}
ctx . lineWidth = this . connections _width ;
ctx . fillStyle = ctx . strokeStyle = color ;
ctx . stroke ( ) ;
//end line shape
var pos = this . computeConnectionPoint ( a , b , 0.5 , start _dir , end _dir ) ;
if ( link && link . _pos ) {
link . _pos [ 0 ] = pos [ 0 ] ;
link . _pos [ 1 ] = pos [ 1 ] ;
}
//render arrow in the middle
if (
this . ds . scale >= 0.6 &&
this . highquality _render &&
end _dir != LiteGraph . CENTER
) {
//render arrow
if ( this . render _connection _arrows ) {
//compute two points in the connection
var posA = this . computeConnectionPoint (
a ,
b ,
0.25 ,
start _dir ,
end _dir
) ;
var posB = this . computeConnectionPoint (
a ,
b ,
0.26 ,
start _dir ,
end _dir
) ;
var posC = this . computeConnectionPoint (
a ,
b ,
0.75 ,
start _dir ,
end _dir
) ;
var posD = this . computeConnectionPoint (
a ,
b ,
0.76 ,
start _dir ,
end _dir
) ;
//compute the angle between them so the arrow points in the right direction
var angleA = 0 ;
var angleB = 0 ;
if ( this . render _curved _connections ) {
angleA = - Math . atan2 ( posB [ 0 ] - posA [ 0 ] , posB [ 1 ] - posA [ 1 ] ) ;
angleB = - Math . atan2 ( posD [ 0 ] - posC [ 0 ] , posD [ 1 ] - posC [ 1 ] ) ;
} else {
angleB = angleA = b [ 1 ] > a [ 1 ] ? 0 : Math . PI ;
}
//render arrow
ctx . save ( ) ;
ctx . translate ( posA [ 0 ] , posA [ 1 ] ) ;
ctx . rotate ( angleA ) ;
ctx . beginPath ( ) ;
ctx . moveTo ( - 5 , - 3 ) ;
ctx . lineTo ( 0 , + 7 ) ;
ctx . lineTo ( + 5 , - 3 ) ;
ctx . fill ( ) ;
ctx . restore ( ) ;
ctx . save ( ) ;
ctx . translate ( posC [ 0 ] , posC [ 1 ] ) ;
ctx . rotate ( angleB ) ;
ctx . beginPath ( ) ;
ctx . moveTo ( - 5 , - 3 ) ;
ctx . lineTo ( 0 , + 7 ) ;
ctx . lineTo ( + 5 , - 3 ) ;
ctx . fill ( ) ;
ctx . restore ( ) ;
}
//circle
ctx . beginPath ( ) ;
ctx . arc ( pos [ 0 ] , pos [ 1 ] , 5 , 0 , Math . PI * 2 ) ;
ctx . fill ( ) ;
}
//render flowing points
if ( flow ) {
ctx . fillStyle = color ;
for ( var i = 0 ; i < 5 ; ++ i ) {
var f = ( LiteGraph . getTime ( ) * 0.001 + i * 0.2 ) % 1 ;
var pos = this . computeConnectionPoint (
a ,
b ,
f ,
start _dir ,
end _dir
) ;
ctx . beginPath ( ) ;
ctx . arc ( pos [ 0 ] , pos [ 1 ] , 5 , 0 , 2 * Math . PI ) ;
ctx . fill ( ) ;
}
}
} ;
//returns the link center point based on curvature
LGraphCanvas . prototype . computeConnectionPoint = function (
a ,
b ,
t ,
start _dir ,
end _dir
) {
start _dir = start _dir || LiteGraph . RIGHT ;
end _dir = end _dir || LiteGraph . LEFT ;
var dist = distance ( a , b ) ;
var p0 = a ;
var p1 = [ a [ 0 ] , a [ 1 ] ] ;
var p2 = [ b [ 0 ] , b [ 1 ] ] ;
var p3 = b ;
switch ( start _dir ) {
case LiteGraph . LEFT :
p1 [ 0 ] += dist * - 0.25 ;
break ;
case LiteGraph . RIGHT :
p1 [ 0 ] += dist * 0.25 ;
break ;
case LiteGraph . UP :
p1 [ 1 ] += dist * - 0.25 ;
break ;
case LiteGraph . DOWN :
p1 [ 1 ] += dist * 0.25 ;
break ;
}
switch ( end _dir ) {
case LiteGraph . LEFT :
p2 [ 0 ] += dist * - 0.25 ;
break ;
case LiteGraph . RIGHT :
p2 [ 0 ] += dist * 0.25 ;
break ;
case LiteGraph . UP :
p2 [ 1 ] += dist * - 0.25 ;
break ;
case LiteGraph . DOWN :
p2 [ 1 ] += dist * 0.25 ;
break ;
}
var c1 = ( 1 - t ) * ( 1 - t ) * ( 1 - t ) ;
var c2 = 3 * ( ( 1 - t ) * ( 1 - t ) ) * t ;
var c3 = 3 * ( 1 - t ) * ( t * t ) ;
var c4 = t * t * t ;
var x = c1 * p0 [ 0 ] + c2 * p1 [ 0 ] + c3 * p2 [ 0 ] + c4 * p3 [ 0 ] ;
var y = c1 * p0 [ 1 ] + c2 * p1 [ 1 ] + c3 * p2 [ 1 ] + c4 * p3 [ 1 ] ;
return [ x , y ] ;
} ;
LGraphCanvas . prototype . drawExecutionOrder = function ( ctx ) {
ctx . shadowColor = "transparent" ;
ctx . globalAlpha = 0.25 ;
ctx . textAlign = "center" ;
ctx . strokeStyle = "white" ;
ctx . globalAlpha = 0.75 ;
var visible _nodes = this . visible _nodes ;
for ( var i = 0 ; i < visible _nodes . length ; ++ i ) {
var node = visible _nodes [ i ] ;
ctx . fillStyle = "black" ;
ctx . fillRect (
node . pos [ 0 ] - LiteGraph . NODE _TITLE _HEIGHT ,
node . pos [ 1 ] - LiteGraph . NODE _TITLE _HEIGHT ,
LiteGraph . NODE _TITLE _HEIGHT ,
LiteGraph . NODE _TITLE _HEIGHT
) ;
if ( node . order == 0 ) {
ctx . strokeRect (
node . pos [ 0 ] - LiteGraph . NODE _TITLE _HEIGHT + 0.5 ,
node . pos [ 1 ] - LiteGraph . NODE _TITLE _HEIGHT + 0.5 ,
LiteGraph . NODE _TITLE _HEIGHT ,
LiteGraph . NODE _TITLE _HEIGHT
) ;
}
ctx . fillStyle = "#FFF" ;
ctx . fillText (
node . order ,
node . pos [ 0 ] + LiteGraph . NODE _TITLE _HEIGHT * - 0.5 ,
node . pos [ 1 ] - 6
) ;
}
ctx . globalAlpha = 1 ;
} ;
/ * *
* draws the widgets stored inside a node
* @ method drawNodeWidgets
* * /
LGraphCanvas . prototype . drawNodeWidgets = function (
node ,
posY ,
ctx ,
active _widget
) {
if ( ! node . widgets || ! node . widgets . length ) {
return 0 ;
}
var width = node . size [ 0 ] ;
var widgets = node . widgets ;
posY += 2 ;
var H = LiteGraph . NODE _WIDGET _HEIGHT ;
var show _text = this . ds . scale > 0.5 ;
ctx . save ( ) ;
ctx . globalAlpha = this . editor _alpha ;
var outline _color = LiteGraph . WIDGET _OUTLINE _COLOR ;
var background _color = LiteGraph . WIDGET _BGCOLOR ;
var text _color = LiteGraph . WIDGET _TEXT _COLOR ;
var secondary _text _color = LiteGraph . WIDGET _SECONDARY _TEXT _COLOR ;
var margin = 15 ;
for ( var i = 0 ; i < widgets . length ; ++ i ) {
var w = widgets [ i ] ;
var y = posY ;
if ( w . y ) {
y = w . y ;
}
w . last _y = y ;
ctx . strokeStyle = outline _color ;
ctx . fillStyle = "#222" ;
ctx . textAlign = "left" ;
//ctx.lineWidth = 2;
if ( w . disabled )
ctx . globalAlpha *= 0.5 ;
var widget _width = w . width || width ;
switch ( w . type ) {
case "button" :
if ( w . clicked ) {
ctx . fillStyle = "#AAA" ;
w . clicked = false ;
this . dirty _canvas = true ;
}
ctx . fillRect ( margin , y , widget _width - margin * 2 , H ) ;
if ( show _text && ! w . disabled )
ctx . strokeRect ( margin , y , widget _width - margin * 2 , H ) ;
if ( show _text ) {
ctx . textAlign = "center" ;
ctx . fillStyle = text _color ;
ctx . fillText ( w . name , widget _width * 0.5 , y + H * 0.7 ) ;
}
break ;
case "toggle" :
ctx . textAlign = "left" ;
ctx . strokeStyle = outline _color ;
ctx . fillStyle = background _color ;
ctx . beginPath ( ) ;
if ( show _text )
ctx . roundRect ( margin , y , widget _width - margin * 2 , H , [ H * 0.5 ] ) ;
else
ctx . rect ( margin , y , widget _width - margin * 2 , H ) ;
ctx . fill ( ) ;
if ( show _text && ! w . disabled )
ctx . stroke ( ) ;
ctx . fillStyle = w . value ? "#89A" : "#333" ;
ctx . beginPath ( ) ;
ctx . arc ( widget _width - margin * 2 , y + H * 0.5 , H * 0.36 , 0 , Math . PI * 2 ) ;
ctx . fill ( ) ;
if ( show _text ) {
ctx . fillStyle = secondary _text _color ;
if ( w . name != null ) {
ctx . fillText ( w . name , margin * 2 , y + H * 0.7 ) ;
}
ctx . fillStyle = w . value ? text _color : secondary _text _color ;
ctx . textAlign = "right" ;
ctx . fillText (
w . value
? w . options . on || "true"
: w . options . off || "false" ,
widget _width - 40 ,
y + H * 0.7
) ;
}
break ;
case "slider" :
ctx . fillStyle = background _color ;
ctx . fillRect ( margin , y , widget _width - margin * 2 , H ) ;
var range = w . options . max - w . options . min ;
var nvalue = ( w . value - w . options . min ) / range ;
2023-04-07 19:11:00 +00:00
if ( nvalue < 0.0 ) nvalue = 0.0 ;
if ( nvalue > 1.0 ) nvalue = 1.0 ;
ctx . fillStyle = w . options . hasOwnProperty ( "slider_color" ) ? w . options . slider _color : ( active _widget == w ? "#89A" : "#678" ) ;
2023-01-03 06:53:32 +00:00
ctx . fillRect ( margin , y , nvalue * ( widget _width - margin * 2 ) , H ) ;
if ( show _text && ! w . disabled )
ctx . strokeRect ( margin , y , widget _width - margin * 2 , H ) ;
if ( w . marker ) {
var marker _nvalue = ( w . marker - w . options . min ) / range ;
2023-04-07 19:11:00 +00:00
if ( marker _nvalue < 0.0 ) marker _nvalue = 0.0 ;
if ( marker _nvalue > 1.0 ) marker _nvalue = 1.0 ;
ctx . fillStyle = w . options . hasOwnProperty ( "marker_color" ) ? w . options . marker _color : "#AA9" ;
2023-01-03 06:53:32 +00:00
ctx . fillRect ( margin + marker _nvalue * ( widget _width - margin * 2 ) , y , 2 , H ) ;
}
if ( show _text ) {
ctx . textAlign = "center" ;
ctx . fillStyle = text _color ;
ctx . fillText (
w . name + " " + Number ( w . value ) . toFixed ( 3 ) ,
widget _width * 0.5 ,
y + H * 0.7
) ;
}
break ;
case "number" :
case "combo" :
ctx . textAlign = "left" ;
ctx . strokeStyle = outline _color ;
ctx . fillStyle = background _color ;
ctx . beginPath ( ) ;
if ( show _text )
ctx . roundRect ( margin , y , widget _width - margin * 2 , H , [ H * 0.5 ] ) ;
else
ctx . rect ( margin , y , widget _width - margin * 2 , H ) ;
ctx . fill ( ) ;
if ( show _text ) {
if ( ! w . disabled )
ctx . stroke ( ) ;
ctx . fillStyle = text _color ;
if ( ! w . disabled )
{
ctx . beginPath ( ) ;
ctx . moveTo ( margin + 16 , y + 5 ) ;
ctx . lineTo ( margin + 6 , y + H * 0.5 ) ;
ctx . lineTo ( margin + 16 , y + H - 5 ) ;
ctx . fill ( ) ;
ctx . beginPath ( ) ;
ctx . moveTo ( widget _width - margin - 16 , y + 5 ) ;
ctx . lineTo ( widget _width - margin - 6 , y + H * 0.5 ) ;
ctx . lineTo ( widget _width - margin - 16 , y + H - 5 ) ;
ctx . fill ( ) ;
}
ctx . fillStyle = secondary _text _color ;
ctx . fillText ( w . name , margin * 2 + 5 , y + H * 0.7 ) ;
ctx . fillStyle = text _color ;
ctx . textAlign = "right" ;
if ( w . type == "number" ) {
ctx . fillText (
Number ( w . value ) . toFixed (
w . options . precision !== undefined
? w . options . precision
: 3
) ,
widget _width - margin * 2 - 20 ,
y + H * 0.7
) ;
} else {
var v = w . value ;
if ( w . options . values )
{
var values = w . options . values ;
if ( values . constructor === Function )
values = values ( ) ;
if ( values && values . constructor !== Array )
v = values [ w . value ] ;
}
ctx . fillText (
v ,
widget _width - margin * 2 - 20 ,
y + H * 0.7
) ;
}
}
break ;
case "string" :
case "text" :
ctx . textAlign = "left" ;
ctx . strokeStyle = outline _color ;
ctx . fillStyle = background _color ;
ctx . beginPath ( ) ;
if ( show _text )
ctx . roundRect ( margin , y , widget _width - margin * 2 , H , [ H * 0.5 ] ) ;
else
ctx . rect ( margin , y , widget _width - margin * 2 , H ) ;
ctx . fill ( ) ;
if ( show _text ) {
if ( ! w . disabled )
ctx . stroke ( ) ;
ctx . save ( ) ;
ctx . beginPath ( ) ;
ctx . rect ( margin , y , widget _width - margin * 2 , H ) ;
ctx . clip ( ) ;
//ctx.stroke();
ctx . fillStyle = secondary _text _color ;
if ( w . name != null ) {
ctx . fillText ( w . name , margin * 2 , y + H * 0.7 ) ;
}
ctx . fillStyle = text _color ;
ctx . textAlign = "right" ;
ctx . fillText ( String ( w . value ) . substr ( 0 , 30 ) , widget _width - margin * 2 , y + H * 0.7 ) ; //30 chars max
ctx . restore ( ) ;
}
break ;
default :
if ( w . draw ) {
w . draw ( ctx , node , widget _width , y , H ) ;
}
break ;
}
posY += ( w . computeSize ? w . computeSize ( widget _width ) [ 1 ] : H ) + 4 ;
ctx . globalAlpha = this . editor _alpha ;
}
ctx . restore ( ) ;
ctx . textAlign = "left" ;
} ;
/ * *
* process an event on widgets
* @ method processNodeWidgets
* * /
LGraphCanvas . prototype . processNodeWidgets = function (
node ,
pos ,
event ,
active _widget
) {
if ( ! node . widgets || ! node . widgets . length ) {
return null ;
}
var x = pos [ 0 ] - node . pos [ 0 ] ;
var y = pos [ 1 ] - node . pos [ 1 ] ;
var width = node . size [ 0 ] ;
var that = this ;
var ref _window = this . getCanvasWindow ( ) ;
for ( var i = 0 ; i < node . widgets . length ; ++ i ) {
var w = node . widgets [ i ] ;
if ( ! w || w . disabled )
continue ;
var widget _height = w . computeSize ? w . computeSize ( width ) [ 1 ] : LiteGraph . NODE _WIDGET _HEIGHT ;
var widget _width = w . width || width ;
//outside
if ( w != active _widget &&
( x < 6 || x > widget _width - 12 || y < w . last _y || y > w . last _y + widget _height || w . last _y === undefined ) )
continue ;
var old _value = w . value ;
//if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) {
//inside widget
switch ( w . type ) {
case "button" :
if ( event . type === LiteGraph . pointerevents _method + "down" ) {
if ( w . callback ) {
setTimeout ( function ( ) {
w . callback ( w , that , node , pos , event ) ;
} , 20 ) ;
}
w . clicked = true ;
this . dirty _canvas = true ;
}
break ;
case "slider" :
var range = w . options . max - w . options . min ;
var nvalue = Math . clamp ( ( x - 15 ) / ( widget _width - 30 ) , 0 , 1 ) ;
2023-04-07 19:11:00 +00:00
if ( w . options . read _only ) break ;
2023-01-03 06:53:32 +00:00
w . value = w . options . min + ( w . options . max - w . options . min ) * nvalue ;
if ( w . callback ) {
setTimeout ( function ( ) {
inner _value _change ( w , w . value ) ;
} , 20 ) ;
}
this . dirty _canvas = true ;
break ;
case "number" :
case "combo" :
var old _value = w . value ;
if ( event . type == LiteGraph . pointerevents _method + "move" && w . type == "number" ) {
2023-04-07 19:11:00 +00:00
if ( event . deltaX )
w . value += event . deltaX * 0.1 * ( w . options . step || 1 ) ;
2023-01-03 06:53:32 +00:00
if ( w . options . min != null && w . value < w . options . min ) {
w . value = w . options . min ;
}
if ( w . options . max != null && w . value > w . options . max ) {
w . value = w . options . max ;
}
} else if ( event . type == LiteGraph . pointerevents _method + "down" ) {
var values = w . options . values ;
if ( values && values . constructor === Function ) {
values = w . options . values ( w , node ) ;
}
var values _list = null ;
if ( w . type != "number" )
values _list = values . constructor === Array ? values : Object . keys ( values ) ;
var delta = x < 40 ? - 1 : x > widget _width - 40 ? 1 : 0 ;
if ( w . type == "number" ) {
w . value += delta * 0.1 * ( w . options . step || 1 ) ;
if ( w . options . min != null && w . value < w . options . min ) {
w . value = w . options . min ;
}
if ( w . options . max != null && w . value > w . options . max ) {
w . value = w . options . max ;
}
} else if ( delta ) { //clicked in arrow, used for combos
var index = - 1 ;
this . last _mouseclick = 0 ; //avoids dobl click event
if ( values . constructor === Object )
index = values _list . indexOf ( String ( w . value ) ) + delta ;
else
index = values _list . indexOf ( w . value ) + delta ;
if ( index >= values _list . length ) {
index = values _list . length - 1 ;
}
if ( index < 0 ) {
index = 0 ;
}
if ( values . constructor === Array )
w . value = values [ index ] ;
else
w . value = index ;
} else { //combo clicked
var text _values = values != values _list ? Object . values ( values ) : values ;
var menu = new LiteGraph . ContextMenu ( text _values , {
scale : Math . max ( 1 , this . ds . scale ) ,
event : event ,
className : "dark" ,
callback : inner _clicked . bind ( w )
} ,
ref _window ) ;
function inner _clicked ( v , option , event ) {
if ( values != values _list )
v = text _values . indexOf ( v ) ;
this . value = v ;
inner _value _change ( this , v ) ;
that . dirty _canvas = true ;
return false ;
}
}
} //end mousedown
else if ( event . type == LiteGraph . pointerevents _method + "up" && w . type == "number" )
{
var delta = x < 40 ? - 1 : x > widget _width - 40 ? 1 : 0 ;
if ( event . click _time < 200 && delta == 0 ) {
this . prompt ( "Value" , w . value , function ( v ) {
2023-04-07 19:11:00 +00:00
// check if v is a valid equation or a number
if ( /^[0-9+\-*/()\s]+$/ . test ( v ) ) {
try { //solve the equation if possible
v = eval ( v ) ;
} catch ( e ) { }
}
2023-01-03 06:53:32 +00:00
this . value = Number ( v ) ;
inner _value _change ( this , this . value ) ;
} . bind ( w ) ,
event ) ;
}
}
if ( old _value != w . value )
setTimeout (
function ( ) {
inner _value _change ( this , this . value ) ;
} . bind ( w ) ,
20
) ;
this . dirty _canvas = true ;
break ;
case "toggle" :
if ( event . type == LiteGraph . pointerevents _method + "down" ) {
w . value = ! w . value ;
setTimeout ( function ( ) {
inner _value _change ( w , w . value ) ;
} , 20 ) ;
}
break ;
case "string" :
case "text" :
if ( event . type == LiteGraph . pointerevents _method + "down" ) {
this . prompt ( "Value" , w . value , function ( v ) {
inner _value _change ( this , v ) ;
} . bind ( w ) ,
event , w . options ? w . options . multiline : false ) ;
}
break ;
default :
if ( w . mouse ) {
this . dirty _canvas = w . mouse ( event , [ x , y ] , node ) ;
}
break ;
} //end switch
//value changed
if ( old _value != w . value )
{
if ( node . onWidgetChanged )
node . onWidgetChanged ( w . name , w . value , old _value , w ) ;
node . graph . _version ++ ;
}
return w ;
} //end for
function inner _value _change ( widget , value ) {
2023-04-07 19:11:00 +00:00
if ( widget . type == "number" ) {
value = Number ( value ) ;
}
2023-01-03 06:53:32 +00:00
widget . value = value ;
if ( widget . options && widget . options . property && node . properties [ widget . options . property ] !== undefined ) {
node . setProperty ( widget . options . property , value ) ;
}
if ( widget . callback ) {
widget . callback ( widget . value , that , node , pos , event ) ;
}
}
return null ;
} ;
/ * *
* draws every group area in the background
* @ method drawGroups
* * /
LGraphCanvas . prototype . drawGroups = function ( canvas , ctx ) {
if ( ! this . graph ) {
return ;
}
var groups = this . graph . _groups ;
ctx . save ( ) ;
ctx . globalAlpha = 0.5 * this . editor _alpha ;
for ( var i = 0 ; i < groups . length ; ++ i ) {
var group = groups [ i ] ;
if ( ! overlapBounding ( this . visible _area , group . _bounding ) ) {
continue ;
} //out of the visible area
ctx . fillStyle = group . color || "#335" ;
ctx . strokeStyle = group . color || "#335" ;
var pos = group . _pos ;
var size = group . _size ;
ctx . globalAlpha = 0.25 * this . editor _alpha ;
ctx . beginPath ( ) ;
ctx . rect ( pos [ 0 ] + 0.5 , pos [ 1 ] + 0.5 , size [ 0 ] , size [ 1 ] ) ;
ctx . fill ( ) ;
ctx . globalAlpha = this . editor _alpha ;
ctx . stroke ( ) ;
ctx . beginPath ( ) ;
ctx . moveTo ( pos [ 0 ] + size [ 0 ] , pos [ 1 ] + size [ 1 ] ) ;
ctx . lineTo ( pos [ 0 ] + size [ 0 ] - 10 , pos [ 1 ] + size [ 1 ] ) ;
ctx . lineTo ( pos [ 0 ] + size [ 0 ] , pos [ 1 ] + size [ 1 ] - 10 ) ;
ctx . fill ( ) ;
var font _size =
group . font _size || LiteGraph . DEFAULT _GROUP _FONT _SIZE ;
ctx . font = font _size + "px Arial" ;
ctx . textAlign = "left" ;
ctx . fillText ( group . title , pos [ 0 ] + 4 , pos [ 1 ] + font _size ) ;
}
ctx . restore ( ) ;
} ;
LGraphCanvas . prototype . adjustNodesSize = function ( ) {
var nodes = this . graph . _nodes ;
for ( var i = 0 ; i < nodes . length ; ++ i ) {
nodes [ i ] . size = nodes [ i ] . computeSize ( ) ;
}
this . setDirty ( true , true ) ;
} ;
/ * *
* resizes the canvas to a given size , if no size is passed , then it tries to fill the parentNode
* @ method resize
* * /
LGraphCanvas . prototype . resize = function ( width , height ) {
if ( ! width && ! height ) {
var parent = this . canvas . parentNode ;
width = parent . offsetWidth ;
height = parent . offsetHeight ;
}
if ( this . canvas . width == width && this . canvas . height == height ) {
return ;
}
this . canvas . width = width ;
this . canvas . height = height ;
this . bgcanvas . width = this . canvas . width ;
this . bgcanvas . height = this . canvas . height ;
this . setDirty ( true , true ) ;
} ;
/ * *
* switches to live mode ( node shapes are not rendered , only the content )
* this feature was designed when graphs where meant to create user interfaces
* @ method switchLiveMode
* * /
LGraphCanvas . prototype . switchLiveMode = function ( transition ) {
if ( ! transition ) {
this . live _mode = ! this . live _mode ;
this . dirty _canvas = true ;
this . dirty _bgcanvas = true ;
return ;
}
var self = this ;
var delta = this . live _mode ? 1.1 : 0.9 ;
if ( this . live _mode ) {
this . live _mode = false ;
this . editor _alpha = 0.1 ;
}
var t = setInterval ( function ( ) {
self . editor _alpha *= delta ;
self . dirty _canvas = true ;
self . dirty _bgcanvas = true ;
if ( delta < 1 && self . editor _alpha < 0.01 ) {
clearInterval ( t ) ;
if ( delta < 1 ) {
self . live _mode = true ;
}
}
if ( delta > 1 && self . editor _alpha > 0.99 ) {
clearInterval ( t ) ;
self . editor _alpha = 1 ;
}
} , 1 ) ;
} ;
LGraphCanvas . prototype . onNodeSelectionChange = function ( node ) {
return ; //disabled
} ;
/ * t h i s i s a n i m p l e m e n t a t i o n f o r t o u c h n o t i n p r o d u c t i o n a n d n o t r e a d y
* /
/ * L G r a p h C a n v a s . p r o t o t y p e . t o u c h H a n d l e r = f u n c t i o n ( e v e n t ) {
//alert("foo");
var touches = event . changedTouches ,
first = touches [ 0 ] ,
type = "" ;
switch ( event . type ) {
case "touchstart" :
type = "mousedown" ;
break ;
case "touchmove" :
type = "mousemove" ;
break ;
case "touchend" :
type = "mouseup" ;
break ;
default :
return ;
}
//initMouseEvent(type, canBubble, cancelable, view, clickCount,
// screenX, screenY, clientX, clientY, ctrlKey,
// altKey, shiftKey, metaKey, button, relatedTarget);
// this is eventually a Dom object, get the LGraphCanvas back
if ( typeof this . getCanvasWindow == "undefined" ) {
var window = this . lgraphcanvas . getCanvasWindow ( ) ;
} else {
var window = this . getCanvasWindow ( ) ;
}
var document = window . document ;
var simulatedEvent = document . createEvent ( "MouseEvent" ) ;
simulatedEvent . initMouseEvent (
type ,
true ,
true ,
window ,
1 ,
first . screenX ,
first . screenY ,
first . clientX ,
first . clientY ,
false ,
false ,
false ,
false ,
0 , //left
null
) ;
first . target . dispatchEvent ( simulatedEvent ) ;
event . preventDefault ( ) ;
} ; * /
/* CONTEXT MENU ********************/
LGraphCanvas . onGroupAdd = function ( info , entry , mouse _event ) {
var canvas = LGraphCanvas . active _canvas ;
var ref _window = canvas . getCanvasWindow ( ) ;
var group = new LiteGraph . LGraphGroup ( ) ;
group . pos = canvas . convertEventToCanvasOffset ( mouse _event ) ;
canvas . graph . add ( group ) ;
} ;
LGraphCanvas . onMenuAdd = function ( node , options , e , prev _menu , callback ) {
var canvas = LGraphCanvas . active _canvas ;
var ref _window = canvas . getCanvasWindow ( ) ;
var graph = canvas . graph ;
if ( ! graph )
return ;
function inner _onMenuAdded ( base _category , prev _menu ) {
var categories = LiteGraph . getNodeTypesCategories ( canvas . filter || graph . filter ) . filter ( function ( category ) { return category . startsWith ( base _category ) } ) ;
var entries = [ ] ;
categories . map ( function ( category ) {
if ( ! category )
return ;
var base _category _regex = new RegExp ( '^(' + base _category + ')' ) ;
var category _name = category . replace ( base _category _regex , "" ) . split ( '/' ) [ 0 ] ;
var category _path = base _category === '' ? category _name + '/' : base _category + category _name + '/' ;
var name = category _name ;
if ( name . indexOf ( "::" ) != - 1 ) //in case it has a namespace like "shader::math/rand" it hides the namespace
name = name . split ( "::" ) [ 1 ] ;
var index = entries . findIndex ( function ( entry ) { return entry . value === category _path } ) ;
if ( index === - 1 ) {
entries . push ( { value : category _path , content : name , has _submenu : true , callback : function ( value , event , mouseEvent , contextMenu ) {
inner _onMenuAdded ( value . value , contextMenu )
} } ) ;
}
} ) ;
var nodes = LiteGraph . getNodeTypesInCategory ( base _category . slice ( 0 , - 1 ) , canvas . filter || graph . filter ) ;
nodes . map ( function ( node ) {
if ( node . skip _list )
return ;
var entry = { value : node . type , content : node . title , has _submenu : false , callback : function ( value , event , mouseEvent , contextMenu ) {
var first _event = contextMenu . getFirstEvent ( ) ;
canvas . graph . beforeChange ( ) ;
var node = LiteGraph . createNode ( value . value ) ;
if ( node ) {
node . pos = canvas . convertEventToCanvasOffset ( first _event ) ;
canvas . graph . add ( node ) ;
}
if ( callback )
callback ( node ) ;
canvas . graph . afterChange ( ) ;
}
}
entries . push ( entry ) ;
} ) ;
new LiteGraph . ContextMenu ( entries , { event : e , parentMenu : prev _menu } , ref _window ) ;
}
inner _onMenuAdded ( '' , prev _menu ) ;
return false ;
} ;
LGraphCanvas . onMenuCollapseAll = function ( ) { } ;
LGraphCanvas . onMenuNodeEdit = function ( ) { } ;
LGraphCanvas . showMenuNodeOptionalInputs = function (
v ,
options ,
e ,
prev _menu ,
node
) {
if ( ! node ) {
return ;
}
var that = this ;
var canvas = LGraphCanvas . active _canvas ;
var ref _window = canvas . getCanvasWindow ( ) ;
var options = node . optional _inputs ;
if ( node . onGetInputs ) {
options = node . onGetInputs ( ) ;
}
var entries = [ ] ;
if ( options ) {
for ( var i = 0 ; i < options . length ; i ++ ) {
var entry = options [ i ] ;
if ( ! entry ) {
entries . push ( null ) ;
continue ;
}
var label = entry [ 0 ] ;
if ( ! entry [ 2 ] )
entry [ 2 ] = { } ;
if ( entry [ 2 ] . label ) {
label = entry [ 2 ] . label ;
}
entry [ 2 ] . removable = true ;
var data = { content : label , value : entry } ;
if ( entry [ 1 ] == LiteGraph . ACTION ) {
data . className = "event" ;
}
entries . push ( data ) ;
}
}
if ( node . onMenuNodeInputs ) {
var retEntries = node . onMenuNodeInputs ( entries ) ;
if ( retEntries ) entries = retEntries ;
}
if ( ! entries . length ) {
console . log ( "no input entries" ) ;
return ;
}
var menu = new LiteGraph . ContextMenu (
entries ,
{
event : e ,
callback : inner _clicked ,
parentMenu : prev _menu ,
node : node
} ,
ref _window
) ;
function inner _clicked ( v , e , prev ) {
if ( ! node ) {
return ;
}
if ( v . callback ) {
v . callback . call ( that , node , v , e , prev ) ;
}
if ( v . value ) {
node . graph . beforeChange ( ) ;
node . addInput ( v . value [ 0 ] , v . value [ 1 ] , v . value [ 2 ] ) ;
if ( node . onNodeInputAdd ) { // callback to the node when adding a slot
node . onNodeInputAdd ( v . value ) ;
}
node . setDirtyCanvas ( true , true ) ;
node . graph . afterChange ( ) ;
}
}
return false ;
} ;
LGraphCanvas . showMenuNodeOptionalOutputs = function (
v ,
options ,
e ,
prev _menu ,
node
) {
if ( ! node ) {
return ;
}
var that = this ;
var canvas = LGraphCanvas . active _canvas ;
var ref _window = canvas . getCanvasWindow ( ) ;
var options = node . optional _outputs ;
if ( node . onGetOutputs ) {
options = node . onGetOutputs ( ) ;
}
var entries = [ ] ;
if ( options ) {
for ( var i = 0 ; i < options . length ; i ++ ) {
var entry = options [ i ] ;
if ( ! entry ) {
//separator?
entries . push ( null ) ;
continue ;
}
if (
node . flags &&
node . flags . skip _repeated _outputs &&
node . findOutputSlot ( entry [ 0 ] ) != - 1
) {
continue ;
} //skip the ones already on
var label = entry [ 0 ] ;
if ( ! entry [ 2 ] )
entry [ 2 ] = { } ;
if ( entry [ 2 ] . label ) {
label = entry [ 2 ] . label ;
}
entry [ 2 ] . removable = true ;
var data = { content : label , value : entry } ;
if ( entry [ 1 ] == LiteGraph . EVENT ) {
data . className = "event" ;
}
entries . push ( data ) ;
}
}
if ( this . onMenuNodeOutputs ) {
entries = this . onMenuNodeOutputs ( entries ) ;
}
if ( LiteGraph . do _add _triggers _slots ) { //canvas.allow_addOutSlot_onExecuted
if ( node . findOutputSlot ( "onExecuted" ) == - 1 ) {
entries . push ( { content : "On Executed" , value : [ "onExecuted" , LiteGraph . EVENT , { nameLocked : true } ] , className : "event" } ) ; //, opts: {}
}
}
// add callback for modifing the menu elements onMenuNodeOutputs
if ( node . onMenuNodeOutputs ) {
var retEntries = node . onMenuNodeOutputs ( entries ) ;
if ( retEntries ) entries = retEntries ;
}
if ( ! entries . length ) {
return ;
}
var menu = new LiteGraph . ContextMenu (
entries ,
{
event : e ,
callback : inner _clicked ,
parentMenu : prev _menu ,
node : node
} ,
ref _window
) ;
function inner _clicked ( v , e , prev ) {
if ( ! node ) {
return ;
}
if ( v . callback ) {
v . callback . call ( that , node , v , e , prev ) ;
}
if ( ! v . value ) {
return ;
}
var value = v . value [ 1 ] ;
if (
value &&
( value . constructor === Object || value . constructor === Array )
) {
//submenu why?
var entries = [ ] ;
for ( var i in value ) {
entries . push ( { content : i , value : value [ i ] } ) ;
}
new LiteGraph . ContextMenu ( entries , {
event : e ,
callback : inner _clicked ,
parentMenu : prev _menu ,
node : node
} ) ;
return false ;
} else {
node . graph . beforeChange ( ) ;
node . addOutput ( v . value [ 0 ] , v . value [ 1 ] , v . value [ 2 ] ) ;
if ( node . onNodeOutputAdd ) { // a callback to the node when adding a slot
node . onNodeOutputAdd ( v . value ) ;
}
node . setDirtyCanvas ( true , true ) ;
node . graph . afterChange ( ) ;
}
}
return false ;
} ;
LGraphCanvas . onShowMenuNodeProperties = function (
value ,
options ,
e ,
prev _menu ,
node
) {
if ( ! node || ! node . properties ) {
return ;
}
var that = this ;
var canvas = LGraphCanvas . active _canvas ;
var ref _window = canvas . getCanvasWindow ( ) ;
var entries = [ ] ;
for ( var i in node . properties ) {
var value = node . properties [ i ] !== undefined ? node . properties [ i ] : " " ;
if ( typeof value == "object" )
value = JSON . stringify ( value ) ;
var info = node . getPropertyInfo ( i ) ;
if ( info . type == "enum" || info . type == "combo" )
value = LGraphCanvas . getPropertyPrintableValue ( value , info . values ) ;
//value could contain invalid html characters, clean that
value = LGraphCanvas . decodeHTML ( value ) ;
entries . push ( {
content :
"<span class='property_name'>" +
( info . label ? info . label : i ) +
"</span>" +
"<span class='property_value'>" +
value +
"</span>" ,
value : i
} ) ;
}
if ( ! entries . length ) {
return ;
}
var menu = new LiteGraph . ContextMenu (
entries ,
{
event : e ,
callback : inner _clicked ,
parentMenu : prev _menu ,
allow _html : true ,
node : node
} ,
ref _window
) ;
function inner _clicked ( v , options , e , prev ) {
if ( ! node ) {
return ;
}
var rect = this . getBoundingClientRect ( ) ;
canvas . showEditPropertyValue ( node , v . value , {
position : [ rect . left , rect . top ]
} ) ;
}
return false ;
} ;
LGraphCanvas . decodeHTML = function ( str ) {
var e = document . createElement ( "div" ) ;
e . innerText = str ;
return e . innerHTML ;
} ;
LGraphCanvas . onMenuResizeNode = function ( value , options , e , menu , node ) {
if ( ! node ) {
return ;
}
var fApplyMultiNode = function ( node ) {
node . size = node . computeSize ( ) ;
if ( node . onResize )
node . onResize ( node . size ) ;
}
var graphcanvas = LGraphCanvas . active _canvas ;
if ( ! graphcanvas . selected _nodes || Object . keys ( graphcanvas . selected _nodes ) . length <= 1 ) {
fApplyMultiNode ( node ) ;
} else {
for ( var i in graphcanvas . selected _nodes ) {
fApplyMultiNode ( graphcanvas . selected _nodes [ i ] ) ;
}
}
node . setDirtyCanvas ( true , true ) ;
} ;
LGraphCanvas . prototype . showLinkMenu = function ( link , e ) {
var that = this ;
// console.log(link);
var node _left = that . graph . getNodeById ( link . origin _id ) ;
var node _right = that . graph . getNodeById ( link . target _id ) ;
var fromType = false ;
if ( node _left && node _left . outputs && node _left . outputs [ link . origin _slot ] ) fromType = node _left . outputs [ link . origin _slot ] . type ;
var destType = false ;
if ( node _right && node _right . outputs && node _right . outputs [ link . target _slot ] ) destType = node _right . inputs [ link . target _slot ] . type ;
var options = [ "Add Node" , null , "Delete" , null ] ;
var menu = new LiteGraph . ContextMenu ( options , {
event : e ,
title : link . data != null ? link . data . constructor . name : null ,
callback : inner _clicked
} ) ;
function inner _clicked ( v , options , e ) {
switch ( v ) {
case "Add Node" :
LGraphCanvas . onMenuAdd ( null , null , e , menu , function ( node ) {
// console.debug("node autoconnect");
if ( ! node . inputs || ! node . inputs . length || ! node . outputs || ! node . outputs . length ) {
return ;
}
// leave the connection type checking inside connectByType
if ( node _left . connectByType ( link . origin _slot , node , fromType ) ) {
node . connectByType ( link . target _slot , node _right , destType ) ;
node . pos [ 0 ] -= node . size [ 0 ] * 0.5 ;
}
} ) ;
break ;
case "Delete" :
that . graph . removeLink ( link . id ) ;
break ;
default :
/ * v a r n o d e C r e a t e d = c r e a t e D e f a u l t N o d e F o r S l o t ( { n o d e F r o m : n o d e _ l e f t
, slotFrom : link . origin _slot
, nodeTo : node
, slotTo : link . target _slot
, e : e
, nodeType : "AUTO"
} ) ;
if ( nodeCreated ) console . log ( "new node in beetween " + v + " created" ) ; * /
}
}
return false ;
} ;
LGraphCanvas . prototype . createDefaultNodeForSlot = function ( optPass ) { // addNodeMenu for connection
var optPass = optPass || { } ;
var opts = Object . assign ( { nodeFrom : null // input
, slotFrom : null // input
, nodeTo : null // output
, slotTo : null // output
, position : [ ] // pass the event coords
, nodeType : null // choose a nodetype to add, AUTO to set at first good
, posAdd : [ 0 , 0 ] // adjust x,y
, posSizeFix : [ 0 , 0 ] // alpha, adjust the position x,y based on the new node size w,h
}
, optPass
) ;
var that = this ;
var isFrom = opts . nodeFrom && opts . slotFrom !== null ;
var isTo = ! isFrom && opts . nodeTo && opts . slotTo !== null ;
if ( ! isFrom && ! isTo ) {
console . warn ( "No data passed to createDefaultNodeForSlot " + opts . nodeFrom + " " + opts . slotFrom + " " + opts . nodeTo + " " + opts . slotTo ) ;
return false ;
}
if ( ! opts . nodeType ) {
console . warn ( "No type to createDefaultNodeForSlot" ) ;
return false ;
}
var nodeX = isFrom ? opts . nodeFrom : opts . nodeTo ;
var slotX = isFrom ? opts . slotFrom : opts . slotTo ;
var iSlotConn = false ;
switch ( typeof slotX ) {
case "string" :
iSlotConn = isFrom ? nodeX . findOutputSlot ( slotX , false ) : nodeX . findInputSlot ( slotX , false ) ;
slotX = isFrom ? nodeX . outputs [ slotX ] : nodeX . inputs [ slotX ] ;
break ;
case "object" :
// ok slotX
iSlotConn = isFrom ? nodeX . findOutputSlot ( slotX . name ) : nodeX . findInputSlot ( slotX . name ) ;
break ;
case "number" :
iSlotConn = slotX ;
slotX = isFrom ? nodeX . outputs [ slotX ] : nodeX . inputs [ slotX ] ;
break ;
case "undefined" :
default :
// bad ?
//iSlotConn = 0;
console . warn ( "Cant get slot information " + slotX ) ;
return false ;
}
if ( slotX === false || iSlotConn === false ) {
console . warn ( "createDefaultNodeForSlot bad slotX " + slotX + " " + iSlotConn ) ;
}
// check for defaults nodes for this slottype
var fromSlotType = slotX . type == LiteGraph . EVENT ? "_event_" : slotX . type ;
var slotTypesDefault = isFrom ? LiteGraph . slot _types _default _out : LiteGraph . slot _types _default _in ;
if ( slotTypesDefault && slotTypesDefault [ fromSlotType ] ) {
if ( slotX . link !== null ) {
// is connected
} else {
// is not not connected
}
nodeNewType = false ;
if ( typeof slotTypesDefault [ fromSlotType ] == "object" || typeof slotTypesDefault [ fromSlotType ] == "array" ) {
for ( var typeX in slotTypesDefault [ fromSlotType ] ) {
if ( opts . nodeType == slotTypesDefault [ fromSlotType ] [ typeX ] || opts . nodeType == "AUTO" ) {
nodeNewType = slotTypesDefault [ fromSlotType ] [ typeX ] ;
// console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType);
break ; // --------
}
}
} else {
if ( opts . nodeType == slotTypesDefault [ fromSlotType ] || opts . nodeType == "AUTO" ) nodeNewType = slotTypesDefault [ fromSlotType ] ;
}
if ( nodeNewType ) {
var nodeNewOpts = false ;
if ( typeof nodeNewType == "object" && nodeNewType . node ) {
nodeNewOpts = nodeNewType ;
nodeNewType = nodeNewType . node ;
}
//that.graph.beforeChange();
var newNode = LiteGraph . createNode ( nodeNewType ) ;
if ( newNode ) {
// if is object pass options
if ( nodeNewOpts ) {
if ( nodeNewOpts . properties ) {
for ( var i in nodeNewOpts . properties ) {
newNode . addProperty ( i , nodeNewOpts . properties [ i ] ) ;
}
}
if ( nodeNewOpts . inputs ) {
newNode . inputs = [ ] ;
for ( var i in nodeNewOpts . inputs ) {
newNode . addOutput (
nodeNewOpts . inputs [ i ] [ 0 ] ,
nodeNewOpts . inputs [ i ] [ 1 ]
) ;
}
}
if ( nodeNewOpts . outputs ) {
newNode . outputs = [ ] ;
for ( var i in nodeNewOpts . outputs ) {
newNode . addOutput (
nodeNewOpts . outputs [ i ] [ 0 ] ,
nodeNewOpts . outputs [ i ] [ 1 ]
) ;
}
}
if ( nodeNewOpts . title ) {
newNode . title = nodeNewOpts . title ;
}
if ( nodeNewOpts . json ) {
newNode . configure ( nodeNewOpts . json ) ;
}
}
// add the node
that . graph . add ( newNode ) ;
newNode . pos = [ opts . position [ 0 ] + opts . posAdd [ 0 ] + ( opts . posSizeFix [ 0 ] ? opts . posSizeFix [ 0 ] * newNode . size [ 0 ] : 0 )
, opts . position [ 1 ] + opts . posAdd [ 1 ] + ( opts . posSizeFix [ 1 ] ? opts . posSizeFix [ 1 ] * newNode . size [ 1 ] : 0 ) ] ; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/
//that.graph.afterChange();
// connect the two!
if ( isFrom ) {
opts . nodeFrom . connectByType ( iSlotConn , newNode , fromSlotType ) ;
} else {
opts . nodeTo . connectByTypeOutput ( iSlotConn , newNode , fromSlotType ) ;
}
// if connecting in between
if ( isFrom && isTo ) {
// TODO
}
return true ;
} else {
console . log ( "failed creating " + nodeNewType ) ;
}
}
}
return false ;
}
LGraphCanvas . prototype . showConnectionMenu = function ( optPass ) { // addNodeMenu for connection
var optPass = optPass || { } ;
var opts = Object . assign ( { nodeFrom : null // input
, slotFrom : null // input
, nodeTo : null // output
, slotTo : null // output
, e : null
}
, optPass
) ;
var that = this ;
var isFrom = opts . nodeFrom && opts . slotFrom ;
var isTo = ! isFrom && opts . nodeTo && opts . slotTo ;
if ( ! isFrom && ! isTo ) {
console . warn ( "No data passed to showConnectionMenu" ) ;
return false ;
}
var nodeX = isFrom ? opts . nodeFrom : opts . nodeTo ;
var slotX = isFrom ? opts . slotFrom : opts . slotTo ;
var iSlotConn = false ;
switch ( typeof slotX ) {
case "string" :
iSlotConn = isFrom ? nodeX . findOutputSlot ( slotX , false ) : nodeX . findInputSlot ( slotX , false ) ;
slotX = isFrom ? nodeX . outputs [ slotX ] : nodeX . inputs [ slotX ] ;
break ;
case "object" :
// ok slotX
iSlotConn = isFrom ? nodeX . findOutputSlot ( slotX . name ) : nodeX . findInputSlot ( slotX . name ) ;
break ;
case "number" :
iSlotConn = slotX ;
slotX = isFrom ? nodeX . outputs [ slotX ] : nodeX . inputs [ slotX ] ;
break ;
default :
// bad ?
//iSlotConn = 0;
console . warn ( "Cant get slot information " + slotX ) ;
return false ;
}
var options = [ "Add Node" , null ] ;
if ( that . allow _searchbox ) {
options . push ( "Search" ) ;
options . push ( null ) ;
}
// get defaults nodes for this slottype
var fromSlotType = slotX . type == LiteGraph . EVENT ? "_event_" : slotX . type ;
var slotTypesDefault = isFrom ? LiteGraph . slot _types _default _out : LiteGraph . slot _types _default _in ;
if ( slotTypesDefault && slotTypesDefault [ fromSlotType ] ) {
if ( typeof slotTypesDefault [ fromSlotType ] == "object" || typeof slotTypesDefault [ fromSlotType ] == "array" ) {
for ( var typeX in slotTypesDefault [ fromSlotType ] ) {
options . push ( slotTypesDefault [ fromSlotType ] [ typeX ] ) ;
}
} else {
options . push ( slotTypesDefault [ fromSlotType ] ) ;
}
}
// build menu
var menu = new LiteGraph . ContextMenu ( options , {
event : opts . e ,
title : ( slotX && slotX . name != "" ? ( slotX . name + ( fromSlotType ? " | " : "" ) ) : "" ) + ( slotX && fromSlotType ? fromSlotType : "" ) ,
callback : inner _clicked
} ) ;
// callback
function inner _clicked ( v , options , e ) {
//console.log("Process showConnectionMenu selection");
switch ( v ) {
case "Add Node" :
LGraphCanvas . onMenuAdd ( null , null , e , menu , function ( node ) {
if ( isFrom ) {
opts . nodeFrom . connectByType ( iSlotConn , node , fromSlotType ) ;
} else {
opts . nodeTo . connectByTypeOutput ( iSlotConn , node , fromSlotType ) ;
}
} ) ;
break ;
case "Search" :
if ( isFrom ) {
that . showSearchBox ( e , { node _from : opts . nodeFrom , slot _from : slotX , type _filter _in : fromSlotType } ) ;
} else {
that . showSearchBox ( e , { node _to : opts . nodeTo , slot _from : slotX , type _filter _out : fromSlotType } ) ;
}
break ;
default :
// check for defaults nodes for this slottype
var nodeCreated = that . createDefaultNodeForSlot ( Object . assign ( opts , { position : [ opts . e . canvasX , opts . e . canvasY ]
, nodeType : v
} ) ) ;
if ( nodeCreated ) {
// new node created
//console.log("node "+v+" created")
} else {
// failed or v is not in defaults
}
break ;
}
}
return false ;
} ;
// TODO refactor :: this is used fot title but not for properties!
LGraphCanvas . onShowPropertyEditor = function ( item , options , e , menu , node ) {
var input _html = "" ;
var property = item . property || "title" ;
var value = node [ property ] ;
// TODO refactor :: use createDialog ?
var dialog = document . createElement ( "div" ) ;
dialog . is _modified = false ;
dialog . className = "graphdialog" ;
dialog . innerHTML =
"<span class='name'></span><input autofocus type='text' class='value'/><button>OK</button>" ;
dialog . close = function ( ) {
if ( dialog . parentNode ) {
dialog . parentNode . removeChild ( dialog ) ;
}
} ;
var title = dialog . querySelector ( ".name" ) ;
title . innerText = property ;
var input = dialog . querySelector ( ".value" ) ;
if ( input ) {
input . value = value ;
input . addEventListener ( "blur" , function ( e ) {
this . focus ( ) ;
} ) ;
input . addEventListener ( "keydown" , function ( e ) {
dialog . is _modified = true ;
if ( e . keyCode == 27 ) {
//ESC
dialog . close ( ) ;
} else if ( e . keyCode == 13 ) {
inner ( ) ; // save
} else if ( e . keyCode != 13 && e . target . localName != "textarea" ) {
return ;
}
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
} ) ;
}
var graphcanvas = LGraphCanvas . active _canvas ;
var canvas = graphcanvas . canvas ;
var rect = canvas . getBoundingClientRect ( ) ;
var offsetx = - 20 ;
var offsety = - 20 ;
if ( rect ) {
offsetx -= rect . left ;
offsety -= rect . top ;
}
if ( event ) {
dialog . style . left = event . clientX + offsetx + "px" ;
dialog . style . top = event . clientY + offsety + "px" ;
} else {
dialog . style . left = canvas . width * 0.5 + offsetx + "px" ;
dialog . style . top = canvas . height * 0.5 + offsety + "px" ;
}
var button = dialog . querySelector ( "button" ) ;
button . addEventListener ( "click" , inner ) ;
canvas . parentNode . appendChild ( dialog ) ;
if ( input ) input . focus ( ) ;
var dialogCloseTimer = null ;
dialog . addEventListener ( "mouseleave" , function ( e ) {
if ( LiteGraph . dialog _close _on _mouse _leave )
if ( ! dialog . is _modified && LiteGraph . dialog _close _on _mouse _leave )
dialogCloseTimer = setTimeout ( dialog . close , LiteGraph . dialog _close _on _mouse _leave _delay ) ; //dialog.close();
} ) ;
dialog . addEventListener ( "mouseenter" , function ( e ) {
if ( LiteGraph . dialog _close _on _mouse _leave )
if ( dialogCloseTimer ) clearTimeout ( dialogCloseTimer ) ;
} ) ;
function inner ( ) {
if ( input ) setValue ( input . value ) ;
}
function setValue ( value ) {
if ( item . type == "Number" ) {
value = Number ( value ) ;
} else if ( item . type == "Boolean" ) {
value = Boolean ( value ) ;
}
node [ property ] = value ;
if ( dialog . parentNode ) {
dialog . parentNode . removeChild ( dialog ) ;
}
node . setDirtyCanvas ( true , true ) ;
}
} ;
// refactor: there are different dialogs, some uses createDialog some dont
LGraphCanvas . prototype . prompt = function ( title , value , callback , event , multiline ) {
var that = this ;
var input _html = "" ;
title = title || "" ;
var dialog = document . createElement ( "div" ) ;
dialog . is _modified = false ;
dialog . className = "graphdialog rounded" ;
if ( multiline )
dialog . innerHTML = "<span class='name'></span> <textarea autofocus class='value'></textarea><button class='rounded'>OK</button>" ;
else
dialog . innerHTML = "<span class='name'></span> <input autofocus type='text' class='value'/><button class='rounded'>OK</button>" ;
dialog . close = function ( ) {
that . prompt _box = null ;
if ( dialog . parentNode ) {
dialog . parentNode . removeChild ( dialog ) ;
}
} ;
var graphcanvas = LGraphCanvas . active _canvas ;
var canvas = graphcanvas . canvas ;
canvas . parentNode . appendChild ( dialog ) ;
if ( this . ds . scale > 1 ) {
dialog . style . transform = "scale(" + this . ds . scale + ")" ;
}
var dialogCloseTimer = null ;
var prevent _timeout = false ;
LiteGraph . pointerListenerAdd ( dialog , "leave" , function ( e ) {
if ( prevent _timeout )
return ;
if ( LiteGraph . dialog _close _on _mouse _leave )
if ( ! dialog . is _modified && LiteGraph . dialog _close _on _mouse _leave )
dialogCloseTimer = setTimeout ( dialog . close , LiteGraph . dialog _close _on _mouse _leave _delay ) ; //dialog.close();
} ) ;
LiteGraph . pointerListenerAdd ( dialog , "enter" , function ( e ) {
if ( LiteGraph . dialog _close _on _mouse _leave )
if ( dialogCloseTimer ) clearTimeout ( dialogCloseTimer ) ;
} ) ;
var selInDia = dialog . querySelectorAll ( "select" ) ;
if ( selInDia ) {
// if filtering, check focus changed to comboboxes and prevent closing
selInDia . forEach ( function ( selIn ) {
selIn . addEventListener ( "click" , function ( e ) {
prevent _timeout ++ ;
} ) ;
selIn . addEventListener ( "blur" , function ( e ) {
prevent _timeout = 0 ;
} ) ;
selIn . addEventListener ( "change" , function ( e ) {
prevent _timeout = - 1 ;
} ) ;
} ) ;
}
if ( that . prompt _box ) {
that . prompt _box . close ( ) ;
}
that . prompt _box = dialog ;
var first = null ;
var timeout = null ;
var selected = null ;
var name _element = dialog . querySelector ( ".name" ) ;
name _element . innerText = title ;
var value _element = dialog . querySelector ( ".value" ) ;
value _element . value = value ;
var input = value _element ;
input . addEventListener ( "keydown" , function ( e ) {
dialog . is _modified = true ;
if ( e . keyCode == 27 ) {
//ESC
dialog . close ( ) ;
} else if ( e . keyCode == 13 && e . target . localName != "textarea" ) {
if ( callback ) {
callback ( this . value ) ;
}
dialog . close ( ) ;
} else {
return ;
}
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
} ) ;
var button = dialog . querySelector ( "button" ) ;
button . addEventListener ( "click" , function ( e ) {
if ( callback ) {
callback ( input . value ) ;
}
that . setDirty ( true ) ;
dialog . close ( ) ;
} ) ;
var rect = canvas . getBoundingClientRect ( ) ;
var offsetx = - 20 ;
var offsety = - 20 ;
if ( rect ) {
offsetx -= rect . left ;
offsety -= rect . top ;
}
if ( event ) {
dialog . style . left = event . clientX + offsetx + "px" ;
dialog . style . top = event . clientY + offsety + "px" ;
} else {
dialog . style . left = canvas . width * 0.5 + offsetx + "px" ;
dialog . style . top = canvas . height * 0.5 + offsety + "px" ;
}
setTimeout ( function ( ) {
input . focus ( ) ;
} , 10 ) ;
return dialog ;
} ;
LGraphCanvas . search _limit = - 1 ;
LGraphCanvas . prototype . showSearchBox = function ( event , options ) {
// proposed defaults
2023-04-07 19:11:00 +00:00
var def _options = { slot _from : null
2023-01-03 06:53:32 +00:00
, node _from : null
, node _to : null
, do _type _filter : LiteGraph . search _filter _enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out
, type _filter _in : false // these are default: pass to set initially set values
, type _filter _out : false
, show _general _if _none _on _typefilter : true
, show _general _after _typefiltered : true
, hide _on _mouse _leave : LiteGraph . search _hide _on _mouse _leave
, show _all _if _empty : true
, show _all _on _open : LiteGraph . search _show _all _on _open
} ;
options = Object . assign ( def _options , options || { } ) ;
//console.log(options);
var that = this ;
var input _html = "" ;
var graphcanvas = LGraphCanvas . active _canvas ;
var canvas = graphcanvas . canvas ;
var root _document = canvas . ownerDocument || document ;
var dialog = document . createElement ( "div" ) ;
dialog . className = "litegraph litesearchbox graphdialog rounded" ;
dialog . innerHTML = "<span class='name'>Search</span> <input autofocus type='text' class='value rounded'/>" ;
if ( options . do _type _filter ) {
dialog . innerHTML += "<select class='slot_in_type_filter'><option value=''></option></select>" ;
dialog . innerHTML += "<select class='slot_out_type_filter'><option value=''></option></select>" ;
}
dialog . innerHTML += "<div class='helper'></div>" ;
if ( root _document . fullscreenElement )
root _document . fullscreenElement . appendChild ( dialog ) ;
else
{
root _document . body . appendChild ( dialog ) ;
root _document . body . style . overflow = "hidden" ;
}
// dialog element has been appended
if ( options . do _type _filter ) {
var selIn = dialog . querySelector ( ".slot_in_type_filter" ) ;
var selOut = dialog . querySelector ( ".slot_out_type_filter" ) ;
}
dialog . close = function ( ) {
that . search _box = null ;
this . blur ( ) ;
canvas . focus ( ) ;
root _document . body . style . overflow = "" ;
setTimeout ( function ( ) {
that . canvas . focus ( ) ;
} , 20 ) ; //important, if canvas loses focus keys wont be captured
if ( dialog . parentNode ) {
dialog . parentNode . removeChild ( dialog ) ;
}
} ;
if ( this . ds . scale > 1 ) {
dialog . style . transform = "scale(" + this . ds . scale + ")" ;
}
// hide on mouse leave
if ( options . hide _on _mouse _leave ) {
var prevent _timeout = false ;
var timeout _close = null ;
LiteGraph . pointerListenerAdd ( dialog , "enter" , function ( e ) {
if ( timeout _close ) {
clearTimeout ( timeout _close ) ;
timeout _close = null ;
}
} ) ;
LiteGraph . pointerListenerAdd ( dialog , "leave" , function ( e ) {
if ( prevent _timeout ) {
return ;
}
timeout _close = setTimeout ( function ( ) {
dialog . close ( ) ;
} , 500 ) ;
} ) ;
// if filtering, check focus changed to comboboxes and prevent closing
if ( options . do _type _filter ) {
selIn . addEventListener ( "click" , function ( e ) {
prevent _timeout ++ ;
} ) ;
selIn . addEventListener ( "blur" , function ( e ) {
prevent _timeout = 0 ;
} ) ;
selIn . addEventListener ( "change" , function ( e ) {
prevent _timeout = - 1 ;
} ) ;
selOut . addEventListener ( "click" , function ( e ) {
prevent _timeout ++ ;
} ) ;
selOut . addEventListener ( "blur" , function ( e ) {
prevent _timeout = 0 ;
} ) ;
selOut . addEventListener ( "change" , function ( e ) {
prevent _timeout = - 1 ;
} ) ;
}
}
if ( that . search _box ) {
that . search _box . close ( ) ;
}
that . search _box = dialog ;
var helper = dialog . querySelector ( ".helper" ) ;
var first = null ;
var timeout = null ;
var selected = null ;
var input = dialog . querySelector ( "input" ) ;
if ( input ) {
input . addEventListener ( "blur" , function ( e ) {
this . focus ( ) ;
} ) ;
input . addEventListener ( "keydown" , function ( e ) {
if ( e . keyCode == 38 ) {
//UP
changeSelection ( false ) ;
} else if ( e . keyCode == 40 ) {
//DOWN
changeSelection ( true ) ;
} else if ( e . keyCode == 27 ) {
//ESC
dialog . close ( ) ;
} else if ( e . keyCode == 13 ) {
if ( selected ) {
select ( selected . innerHTML ) ;
} else if ( first ) {
select ( first ) ;
} else {
dialog . close ( ) ;
}
} else {
if ( timeout ) {
clearInterval ( timeout ) ;
}
timeout = setTimeout ( refreshHelper , 250 ) ;
return ;
}
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
e . stopImmediatePropagation ( ) ;
return true ;
} ) ;
}
// if should filter on type, load and fill selected and choose elements if passed
if ( options . do _type _filter ) {
if ( selIn ) {
var aSlots = LiteGraph . slot _types _in ;
var nSlots = aSlots . length ; // this for object :: Object.keys(aSlots).length;
if ( options . type _filter _in == LiteGraph . EVENT || options . type _filter _in == LiteGraph . ACTION )
options . type _filter _in = "_event_" ;
/ * t h i s w i l l f i l t e r o n * . . b u t b e t t e r d o i t m a n u a l l y i n c a s e
else if ( options . type _filter _in === "" || options . type _filter _in === 0 )
options . type _filter _in = "*" ; * /
for ( var iK = 0 ; iK < nSlots ; iK ++ ) {
var opt = document . createElement ( 'option' ) ;
opt . value = aSlots [ iK ] ;
opt . innerHTML = aSlots [ iK ] ;
selIn . appendChild ( opt ) ;
if ( options . type _filter _in !== false && ( options . type _filter _in + "" ) . toLowerCase ( ) == ( aSlots [ iK ] + "" ) . toLowerCase ( ) ) {
//selIn.selectedIndex ..
opt . selected = true ;
//console.log("comparing IN "+options.type_filter_in+" :: "+aSlots[iK]);
} else {
//console.log("comparing OUT "+options.type_filter_in+" :: "+aSlots[iK]);
}
}
selIn . addEventListener ( "change" , function ( ) {
refreshHelper ( ) ;
} ) ;
}
if ( selOut ) {
var aSlots = LiteGraph . slot _types _out ;
var nSlots = aSlots . length ; // this for object :: Object.keys(aSlots).length;
if ( options . type _filter _out == LiteGraph . EVENT || options . type _filter _out == LiteGraph . ACTION )
options . type _filter _out = "_event_" ;
/ * t h i s w i l l f i l t e r o n * . . b u t b e t t e r d o i t m a n u a l l y i n c a s e
else if ( options . type _filter _out === "" || options . type _filter _out === 0 )
options . type _filter _out = "*" ; * /
for ( var iK = 0 ; iK < nSlots ; iK ++ ) {
var opt = document . createElement ( 'option' ) ;
opt . value = aSlots [ iK ] ;
opt . innerHTML = aSlots [ iK ] ;
selOut . appendChild ( opt ) ;
if ( options . type _filter _out !== false && ( options . type _filter _out + "" ) . toLowerCase ( ) == ( aSlots [ iK ] + "" ) . toLowerCase ( ) ) {
//selOut.selectedIndex ..
opt . selected = true ;
}
}
selOut . addEventListener ( "change" , function ( ) {
refreshHelper ( ) ;
} ) ;
}
}
//compute best position
var rect = canvas . getBoundingClientRect ( ) ;
var left = ( event ? event . clientX : ( rect . left + rect . width * 0.5 ) ) - 80 ;
var top = ( event ? event . clientY : ( rect . top + rect . height * 0.5 ) ) - 20 ;
dialog . style . left = left + "px" ;
dialog . style . top = top + "px" ;
//To avoid out of screen problems
if ( event . layerY > ( rect . height - 200 ) )
helper . style . maxHeight = ( rect . height - event . layerY - 20 ) + "px" ;
/ *
var offsetx = - 20 ;
var offsety = - 20 ;
if ( rect ) {
offsetx -= rect . left ;
offsety -= rect . top ;
}
if ( event ) {
dialog . style . left = event . clientX + offsetx + "px" ;
dialog . style . top = event . clientY + offsety + "px" ;
} else {
dialog . style . left = canvas . width * 0.5 + offsetx + "px" ;
dialog . style . top = canvas . height * 0.5 + offsety + "px" ;
}
canvas . parentNode . appendChild ( dialog ) ;
* /
input . focus ( ) ;
if ( options . show _all _on _open ) refreshHelper ( ) ;
function select ( name ) {
if ( name ) {
if ( that . onSearchBoxSelection ) {
that . onSearchBoxSelection ( name , event , graphcanvas ) ;
} else {
var extra = LiteGraph . searchbox _extras [ name . toLowerCase ( ) ] ;
if ( extra ) {
name = extra . type ;
}
graphcanvas . graph . beforeChange ( ) ;
var node = LiteGraph . createNode ( name ) ;
if ( node ) {
node . pos = graphcanvas . convertEventToCanvasOffset (
event
) ;
graphcanvas . graph . add ( node , false ) ;
}
if ( extra && extra . data ) {
if ( extra . data . properties ) {
for ( var i in extra . data . properties ) {
node . addProperty ( i , extra . data . properties [ i ] ) ;
}
}
if ( extra . data . inputs ) {
node . inputs = [ ] ;
for ( var i in extra . data . inputs ) {
node . addOutput (
extra . data . inputs [ i ] [ 0 ] ,
extra . data . inputs [ i ] [ 1 ]
) ;
}
}
if ( extra . data . outputs ) {
node . outputs = [ ] ;
for ( var i in extra . data . outputs ) {
node . addOutput (
extra . data . outputs [ i ] [ 0 ] ,
extra . data . outputs [ i ] [ 1 ]
) ;
}
}
if ( extra . data . title ) {
node . title = extra . data . title ;
}
if ( extra . data . json ) {
node . configure ( extra . data . json ) ;
}
}
// join node after inserting
if ( options . node _from ) {
var iS = false ;
switch ( typeof options . slot _from ) {
case "string" :
iS = options . node _from . findOutputSlot ( options . slot _from ) ;
break ;
case "object" :
if ( options . slot _from . name ) {
iS = options . node _from . findOutputSlot ( options . slot _from . name ) ;
} else {
iS = - 1 ;
}
if ( iS == - 1 && typeof options . slot _from . slot _index !== "undefined" ) iS = options . slot _from . slot _index ;
break ;
case "number" :
iS = options . slot _from ;
break ;
default :
iS = 0 ; // try with first if no name set
}
if ( typeof options . node _from . outputs [ iS ] !== undefined ) {
if ( iS !== false && iS > - 1 ) {
options . node _from . connectByType ( iS , node , options . node _from . outputs [ iS ] . type ) ;
}
} else {
// console.warn("cant find slot " + options.slot_from);
}
}
if ( options . node _to ) {
var iS = false ;
switch ( typeof options . slot _from ) {
case "string" :
iS = options . node _to . findInputSlot ( options . slot _from ) ;
break ;
case "object" :
if ( options . slot _from . name ) {
iS = options . node _to . findInputSlot ( options . slot _from . name ) ;
} else {
iS = - 1 ;
}
if ( iS == - 1 && typeof options . slot _from . slot _index !== "undefined" ) iS = options . slot _from . slot _index ;
break ;
case "number" :
iS = options . slot _from ;
break ;
default :
iS = 0 ; // try with first if no name set
}
if ( typeof options . node _to . inputs [ iS ] !== undefined ) {
if ( iS !== false && iS > - 1 ) {
// try connection
options . node _to . connectByTypeOutput ( iS , node , options . node _to . inputs [ iS ] . type ) ;
}
} else {
// console.warn("cant find slot_nodeTO " + options.slot_from);
}
}
graphcanvas . graph . afterChange ( ) ;
}
}
dialog . close ( ) ;
}
function changeSelection ( forward ) {
var prev = selected ;
if ( selected ) {
selected . classList . remove ( "selected" ) ;
}
if ( ! selected ) {
selected = forward
? helper . childNodes [ 0 ]
: helper . childNodes [ helper . childNodes . length ] ;
} else {
selected = forward
? selected . nextSibling
: selected . previousSibling ;
if ( ! selected ) {
selected = prev ;
}
}
if ( ! selected ) {
return ;
}
selected . classList . add ( "selected" ) ;
selected . scrollIntoView ( { block : "end" , behavior : "smooth" } ) ;
}
function refreshHelper ( ) {
timeout = null ;
var str = input . value ;
first = null ;
helper . innerHTML = "" ;
if ( ! str && ! options . show _all _if _empty ) {
return ;
}
if ( that . onSearchBox ) {
var list = that . onSearchBox ( helper , str , graphcanvas ) ;
if ( list ) {
for ( var i = 0 ; i < list . length ; ++ i ) {
addResult ( list [ i ] ) ;
}
}
} else {
var c = 0 ;
str = str . toLowerCase ( ) ;
var filter = graphcanvas . filter || graphcanvas . graph . filter ;
// filter by type preprocess
if ( options . do _type _filter && that . search _box ) {
var sIn = that . search _box . querySelector ( ".slot_in_type_filter" ) ;
var sOut = that . search _box . querySelector ( ".slot_out_type_filter" ) ;
} else {
var sIn = false ;
var sOut = false ;
}
//extras
for ( var i in LiteGraph . searchbox _extras ) {
var extra = LiteGraph . searchbox _extras [ i ] ;
if ( ( ! options . show _all _if _empty || str ) && extra . desc . toLowerCase ( ) . indexOf ( str ) === - 1 ) {
continue ;
}
var ctor = LiteGraph . registered _node _types [ extra . type ] ;
if ( ctor && ctor . filter != filter )
continue ;
if ( ! inner _test _filter ( extra . type ) )
continue ;
addResult ( extra . desc , "searchbox_extra" ) ;
if ( LGraphCanvas . search _limit !== - 1 && c ++ > LGraphCanvas . search _limit ) {
break ;
}
}
var filtered = null ;
if ( Array . prototype . filter ) { //filter supported
var keys = Object . keys ( LiteGraph . registered _node _types ) ; //types
var filtered = keys . filter ( inner _test _filter ) ;
} else {
filtered = [ ] ;
for ( var i in LiteGraph . registered _node _types ) {
if ( inner _test _filter ( i ) )
filtered . push ( i ) ;
}
}
for ( var i = 0 ; i < filtered . length ; i ++ ) {
addResult ( filtered [ i ] ) ;
if ( LGraphCanvas . search _limit !== - 1 && c ++ > LGraphCanvas . search _limit ) {
break ;
}
}
// add general type if filtering
if ( options . show _general _after _typefiltered
&& ( sIn . value || sOut . value )
) {
filtered _extra = [ ] ;
for ( var i in LiteGraph . registered _node _types ) {
if ( inner _test _filter ( i , { inTypeOverride : sIn && sIn . value ? "*" : false , outTypeOverride : sOut && sOut . value ? "*" : false } ) )
filtered _extra . push ( i ) ;
}
for ( var i = 0 ; i < filtered _extra . length ; i ++ ) {
addResult ( filtered _extra [ i ] , "generic_type" ) ;
if ( LGraphCanvas . search _limit !== - 1 && c ++ > LGraphCanvas . search _limit ) {
break ;
}
}
}
// check il filtering gave no results
if ( ( sIn . value || sOut . value ) &&
( ( helper . childNodes . length == 0 && options . show _general _if _none _on _typefilter ) )
) {
filtered _extra = [ ] ;
for ( var i in LiteGraph . registered _node _types ) {
if ( inner _test _filter ( i , { skipFilter : true } ) )
filtered _extra . push ( i ) ;
}
for ( var i = 0 ; i < filtered _extra . length ; i ++ ) {
addResult ( filtered _extra [ i ] , "not_in_filter" ) ;
if ( LGraphCanvas . search _limit !== - 1 && c ++ > LGraphCanvas . search _limit ) {
break ;
}
}
}
function inner _test _filter ( type , optsIn )
{
var optsIn = optsIn || { } ;
var optsDef = { skipFilter : false
, inTypeOverride : false
, outTypeOverride : false
} ;
var opts = Object . assign ( optsDef , optsIn ) ;
var ctor = LiteGraph . registered _node _types [ type ] ;
if ( filter && ctor . filter != filter )
return false ;
if ( ( ! options . show _all _if _empty || str ) && type . toLowerCase ( ) . indexOf ( str ) === - 1 )
return false ;
// filter by slot IN, OUT types
if ( options . do _type _filter && ! opts . skipFilter ) {
var sType = type ;
var sV = sIn . value ;
if ( opts . inTypeOverride !== false ) sV = opts . inTypeOverride ;
//if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1
if ( sIn && sV ) {
//console.log("will check filter against "+sV);
if ( LiteGraph . registered _slot _in _types [ sV ] && LiteGraph . registered _slot _in _types [ sV ] . nodes ) { // type is stored
//console.debug("check "+sType+" in "+LiteGraph.registered_slot_in_types[sV].nodes);
var doesInc = LiteGraph . registered _slot _in _types [ sV ] . nodes . includes ( sType ) ;
if ( doesInc !== false ) {
//console.log(sType+" HAS "+sV);
} else {
/ * c o n s o l e . d e b u g ( L i t e G r a p h . r e g i s t e r e d _ s l o t _ i n _ t y p e s [ s V ] ) ;
console . log ( + " DONT includes " + type ) ; * /
return false ;
}
}
}
var sV = sOut . value ;
if ( opts . outTypeOverride !== false ) sV = opts . outTypeOverride ;
//if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1
if ( sOut && sV ) {
//console.log("search will check filter against "+sV);
if ( LiteGraph . registered _slot _out _types [ sV ] && LiteGraph . registered _slot _out _types [ sV ] . nodes ) { // type is stored
//console.debug("check "+sType+" in "+LiteGraph.registered_slot_out_types[sV].nodes);
var doesInc = LiteGraph . registered _slot _out _types [ sV ] . nodes . includes ( sType ) ;
if ( doesInc !== false ) {
//console.log(sType+" HAS "+sV);
} else {
/ * c o n s o l e . d e b u g ( L i t e G r a p h . r e g i s t e r e d _ s l o t _ o u t _ t y p e s [ s V ] ) ;
console . log ( + " DONT includes " + type ) ; * /
return false ;
}
}
}
}
return true ;
}
}
function addResult ( type , className ) {
var help = document . createElement ( "div" ) ;
if ( ! first ) {
first = type ;
}
help . innerText = type ;
help . dataset [ "type" ] = escape ( type ) ;
help . className = "litegraph lite-search-item" ;
if ( className ) {
help . className += " " + className ;
}
help . addEventListener ( "click" , function ( e ) {
select ( unescape ( this . dataset [ "type" ] ) ) ;
} ) ;
helper . appendChild ( help ) ;
}
}
return dialog ;
} ;
LGraphCanvas . prototype . showEditPropertyValue = function ( node , property , options ) {
if ( ! node || node . properties [ property ] === undefined ) {
return ;
}
options = options || { } ;
var that = this ;
var info = node . getPropertyInfo ( property ) ;
var type = info . type ;
var input _html = "" ;
if ( type == "string" || type == "number" || type == "array" || type == "object" ) {
input _html = "<input autofocus type='text' class='value'/>" ;
} else if ( ( type == "enum" || type == "combo" ) && info . values ) {
input _html = "<select autofocus type='text' class='value'>" ;
for ( var i in info . values ) {
var v = i ;
if ( info . values . constructor === Array )
v = info . values [ i ] ;
input _html +=
"<option value='" +
v +
"' " +
( v == node . properties [ property ] ? "selected" : "" ) +
">" +
info . values [ i ] +
"</option>" ;
}
input _html += "</select>" ;
} else if ( type == "boolean" || type == "toggle" ) {
input _html =
"<input autofocus type='checkbox' class='value' " +
( node . properties [ property ] ? "checked" : "" ) +
"/>" ;
} else {
console . warn ( "unknown type: " + type ) ;
return ;
}
var dialog = this . createDialog (
"<span class='name'>" +
( info . label ? info . label : property ) +
"</span>" +
input _html +
"<button>OK</button>" ,
options
) ;
var input = false ;
if ( ( type == "enum" || type == "combo" ) && info . values ) {
input = dialog . querySelector ( "select" ) ;
input . addEventListener ( "change" , function ( e ) {
dialog . modified ( ) ;
setValue ( e . target . value ) ;
//var index = e.target.value;
//setValue( e.options[e.selectedIndex].value );
} ) ;
} else if ( type == "boolean" || type == "toggle" ) {
input = dialog . querySelector ( "input" ) ;
if ( input ) {
input . addEventListener ( "click" , function ( e ) {
dialog . modified ( ) ;
setValue ( ! ! input . checked ) ;
} ) ;
}
} else {
input = dialog . querySelector ( "input" ) ;
if ( input ) {
input . addEventListener ( "blur" , function ( e ) {
this . focus ( ) ;
} ) ;
var v = node . properties [ property ] !== undefined ? node . properties [ property ] : "" ;
if ( type !== 'string' ) {
v = JSON . stringify ( v ) ;
}
input . value = v ;
input . addEventListener ( "keydown" , function ( e ) {
if ( e . keyCode == 27 ) {
//ESC
dialog . close ( ) ;
} else if ( e . keyCode == 13 ) {
// ENTER
inner ( ) ; // save
} else if ( e . keyCode != 13 ) {
dialog . modified ( ) ;
return ;
}
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
} ) ;
}
}
if ( input ) input . focus ( ) ;
var button = dialog . querySelector ( "button" ) ;
button . addEventListener ( "click" , inner ) ;
function inner ( ) {
setValue ( input . value ) ;
}
function setValue ( value ) {
if ( info && info . values && info . values . constructor === Object && info . values [ value ] != undefined )
value = info . values [ value ] ;
if ( typeof node . properties [ property ] == "number" ) {
value = Number ( value ) ;
}
if ( type == "array" || type == "object" ) {
value = JSON . parse ( value ) ;
}
node . properties [ property ] = value ;
if ( node . graph ) {
node . graph . _version ++ ;
}
if ( node . onPropertyChanged ) {
node . onPropertyChanged ( property , value ) ;
}
if ( options . onclose )
options . onclose ( ) ;
dialog . close ( ) ;
node . setDirtyCanvas ( true , true ) ;
}
return dialog ;
} ;
// TODO refactor, theer are different dialog, some uses createDialog, some dont
LGraphCanvas . prototype . createDialog = function ( html , options ) {
2023-04-07 19:11:00 +00:00
var def _options = { checkForInput : false , closeOnLeave : true , closeOnLeave _checkModified : true } ;
2023-01-03 06:53:32 +00:00
options = Object . assign ( def _options , options || { } ) ;
var dialog = document . createElement ( "div" ) ;
dialog . className = "graphdialog" ;
dialog . innerHTML = html ;
dialog . is _modified = false ;
var rect = this . canvas . getBoundingClientRect ( ) ;
var offsetx = - 20 ;
var offsety = - 20 ;
if ( rect ) {
offsetx -= rect . left ;
offsety -= rect . top ;
}
if ( options . position ) {
offsetx += options . position [ 0 ] ;
offsety += options . position [ 1 ] ;
} else if ( options . event ) {
offsetx += options . event . clientX ;
offsety += options . event . clientY ;
} //centered
else {
offsetx += this . canvas . width * 0.5 ;
offsety += this . canvas . height * 0.5 ;
}
dialog . style . left = offsetx + "px" ;
dialog . style . top = offsety + "px" ;
this . canvas . parentNode . appendChild ( dialog ) ;
// acheck for input and use default behaviour: save on enter, close on esc
if ( options . checkForInput ) {
var aI = [ ] ;
var focused = false ;
if ( aI = dialog . querySelectorAll ( "input" ) ) {
aI . forEach ( function ( iX ) {
iX . addEventListener ( "keydown" , function ( e ) {
dialog . modified ( ) ;
if ( e . keyCode == 27 ) {
dialog . close ( ) ;
} else if ( e . keyCode != 13 ) {
return ;
}
// set value ?
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
} ) ;
if ( ! focused ) iX . focus ( ) ;
} ) ;
}
}
dialog . modified = function ( ) {
dialog . is _modified = true ;
}
dialog . close = function ( ) {
if ( dialog . parentNode ) {
dialog . parentNode . removeChild ( dialog ) ;
}
} ;
var dialogCloseTimer = null ;
var prevent _timeout = false ;
dialog . addEventListener ( "mouseleave" , function ( e ) {
if ( prevent _timeout )
return ;
if ( options . closeOnLeave || LiteGraph . dialog _close _on _mouse _leave )
if ( ! dialog . is _modified && LiteGraph . dialog _close _on _mouse _leave )
dialogCloseTimer = setTimeout ( dialog . close , LiteGraph . dialog _close _on _mouse _leave _delay ) ; //dialog.close();
} ) ;
dialog . addEventListener ( "mouseenter" , function ( e ) {
if ( options . closeOnLeave || LiteGraph . dialog _close _on _mouse _leave )
if ( dialogCloseTimer ) clearTimeout ( dialogCloseTimer ) ;
} ) ;
var selInDia = dialog . querySelectorAll ( "select" ) ;
if ( selInDia ) {
// if filtering, check focus changed to comboboxes and prevent closing
selInDia . forEach ( function ( selIn ) {
selIn . addEventListener ( "click" , function ( e ) {
prevent _timeout ++ ;
} ) ;
selIn . addEventListener ( "blur" , function ( e ) {
prevent _timeout = 0 ;
} ) ;
selIn . addEventListener ( "change" , function ( e ) {
prevent _timeout = - 1 ;
} ) ;
} ) ;
}
return dialog ;
} ;
LGraphCanvas . prototype . createPanel = function ( title , options ) {
options = options || { } ;
var ref _window = options . window || window ;
var root = document . createElement ( "div" ) ;
root . className = "litegraph dialog" ;
root . innerHTML = "<div class='dialog-header'><span class='dialog-title'></span></div><div class='dialog-content'></div><div style='display:none;' class='dialog-alt-content'></div><div class='dialog-footer'></div>" ;
root . header = root . querySelector ( ".dialog-header" ) ;
if ( options . width )
root . style . width = options . width + ( options . width . constructor === Number ? "px" : "" ) ;
if ( options . height )
root . style . height = options . height + ( options . height . constructor === Number ? "px" : "" ) ;
if ( options . closable )
{
var close = document . createElement ( "span" ) ;
close . innerHTML = "✕" ;
close . classList . add ( "close" ) ;
close . addEventListener ( "click" , function ( ) {
root . close ( ) ;
} ) ;
root . header . appendChild ( close ) ;
}
root . title _element = root . querySelector ( ".dialog-title" ) ;
root . title _element . innerText = title ;
root . content = root . querySelector ( ".dialog-content" ) ;
root . alt _content = root . querySelector ( ".dialog-alt-content" ) ;
root . footer = root . querySelector ( ".dialog-footer" ) ;
root . close = function ( )
{
if ( root . onClose && typeof root . onClose == "function" ) {
root . onClose ( ) ;
}
2023-04-07 19:11:00 +00:00
if ( root . parentNode )
root . parentNode . removeChild ( root ) ;
2023-01-03 06:53:32 +00:00
/* XXX CHECK THIS */
if ( this . parentNode ) {
this . parentNode . removeChild ( this ) ;
}
/* XXX this was not working, was fixed with an IF, check this */
}
// function to swap panel content
root . toggleAltContent = function ( force ) {
if ( typeof force != "undefined" ) {
var vTo = force ? "block" : "none" ;
var vAlt = force ? "none" : "block" ;
} else {
var vTo = root . alt _content . style . display != "block" ? "block" : "none" ;
var vAlt = root . alt _content . style . display != "block" ? "none" : "block" ;
}
root . alt _content . style . display = vTo ;
root . content . style . display = vAlt ;
}
root . toggleFooterVisibility = function ( force ) {
if ( typeof force != "undefined" ) {
var vTo = force ? "block" : "none" ;
} else {
var vTo = root . footer . style . display != "block" ? "block" : "none" ;
}
root . footer . style . display = vTo ;
}
root . clear = function ( )
{
this . content . innerHTML = "" ;
}
root . addHTML = function ( code , classname , on _footer )
{
var elem = document . createElement ( "div" ) ;
if ( classname )
elem . className = classname ;
elem . innerHTML = code ;
if ( on _footer )
root . footer . appendChild ( elem ) ;
else
root . content . appendChild ( elem ) ;
return elem ;
}
root . addButton = function ( name , callback , options )
{
var elem = document . createElement ( "button" ) ;
elem . innerText = name ;
elem . options = options ;
elem . classList . add ( "btn" ) ;
elem . addEventListener ( "click" , callback ) ;
root . footer . appendChild ( elem ) ;
return elem ;
}
root . addSeparator = function ( )
{
var elem = document . createElement ( "div" ) ;
elem . className = "separator" ;
root . content . appendChild ( elem ) ;
}
root . addWidget = function ( type , name , value , options , callback )
{
options = options || { } ;
var str _value = String ( value ) ;
type = type . toLowerCase ( ) ;
if ( type == "number" )
str _value = value . toFixed ( 3 ) ;
var elem = document . createElement ( "div" ) ;
elem . className = "property" ;
elem . innerHTML = "<span class='property_name'></span><span class='property_value'></span>" ;
elem . querySelector ( ".property_name" ) . innerText = options . label || name ;
var value _element = elem . querySelector ( ".property_value" ) ;
value _element . innerText = str _value ;
elem . dataset [ "property" ] = name ;
elem . dataset [ "type" ] = options . type || type ;
elem . options = options ;
elem . value = value ;
if ( type == "code" )
elem . addEventListener ( "click" , function ( e ) { root . inner _showCodePad ( this . dataset [ "property" ] ) ; } ) ;
else if ( type == "boolean" )
{
elem . classList . add ( "boolean" ) ;
if ( value )
elem . classList . add ( "bool-on" ) ;
elem . addEventListener ( "click" , function ( ) {
//var v = node.properties[this.dataset["property"]];
//node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false";
var propname = this . dataset [ "property" ] ;
this . value = ! this . value ;
this . classList . toggle ( "bool-on" ) ;
this . querySelector ( ".property_value" ) . innerText = this . value ? "true" : "false" ;
innerChange ( propname , this . value ) ;
} ) ;
}
else if ( type == "string" || type == "number" )
{
value _element . setAttribute ( "contenteditable" , true ) ;
value _element . addEventListener ( "keydown" , function ( e ) {
if ( e . code == "Enter" && ( type != "string" || ! e . shiftKey ) ) // allow for multiline
{
e . preventDefault ( ) ;
this . blur ( ) ;
}
} ) ;
value _element . addEventListener ( "blur" , function ( ) {
var v = this . innerText ;
var propname = this . parentNode . dataset [ "property" ] ;
var proptype = this . parentNode . dataset [ "type" ] ;
if ( proptype == "number" )
v = Number ( v ) ;
innerChange ( propname , v ) ;
} ) ;
}
else if ( type == "enum" || type == "combo" ) {
var str _value = LGraphCanvas . getPropertyPrintableValue ( value , options . values ) ;
value _element . innerText = str _value ;
value _element . addEventListener ( "click" , function ( event ) {
var values = options . values || [ ] ;
var propname = this . parentNode . dataset [ "property" ] ;
var elem _that = this ;
var menu = new LiteGraph . ContextMenu ( values , {
event : event ,
className : "dark" ,
callback : inner _clicked
} ,
ref _window ) ;
function inner _clicked ( v , option , event ) {
//node.setProperty(propname,v);
//graphcanvas.dirty_canvas = true;
elem _that . innerText = v ;
innerChange ( propname , v ) ;
return false ;
}
} ) ;
}
root . content . appendChild ( elem ) ;
function innerChange ( name , value )
{
//console.log("change",name,value);
//that.dirty_canvas = true;
if ( options . callback )
options . callback ( name , value , options ) ;
if ( callback )
callback ( name , value , options ) ;
}
return elem ;
}
if ( root . onOpen && typeof root . onOpen == "function" ) root . onOpen ( ) ;
return root ;
} ;
LGraphCanvas . getPropertyPrintableValue = function ( value , values )
{
if ( ! values )
return String ( value ) ;
if ( values . constructor === Array )
{
return String ( value ) ;
}
if ( values . constructor === Object )
{
var desc _value = "" ;
for ( var k in values )
{
if ( values [ k ] != value )
continue ;
desc _value = k ;
break ;
}
return String ( value ) + " (" + desc _value + ")" ;
}
}
LGraphCanvas . prototype . closePanels = function ( ) {
var panel = document . querySelector ( "#node-panel" ) ;
if ( panel )
panel . close ( ) ;
var panel = document . querySelector ( "#option-panel" ) ;
if ( panel )
panel . close ( ) ;
}
LGraphCanvas . prototype . showShowGraphOptionsPanel = function ( refOpts , obEv , refMenu , refMenu2 ) {
if ( this . constructor && this . constructor . name == "HTMLDivElement" ) {
// assume coming from the menu event click
if ( ! obEv || ! obEv . event || ! obEv . event . target || ! obEv . event . target . lgraphcanvas ) {
console . warn ( "Canvas not found" ) ; // need a ref to canvas obj
/ * c o n s o l e . d e b u g ( e v e n t ) ;
console . debug ( event . target ) ; * /
return ;
}
var graphcanvas = obEv . event . target . lgraphcanvas ;
} else {
// assume called internally
var graphcanvas = this ;
}
graphcanvas . closePanels ( ) ;
var ref _window = graphcanvas . getCanvasWindow ( ) ;
panel = graphcanvas . createPanel ( "Options" , {
closable : true
, window : ref _window
, onOpen : function ( ) {
graphcanvas . OPTIONPANEL _IS _OPEN = true ;
}
, onClose : function ( ) {
graphcanvas . OPTIONPANEL _IS _OPEN = false ;
graphcanvas . options _panel = null ;
}
} ) ;
graphcanvas . options _panel = panel ;
panel . id = "option-panel" ;
panel . classList . add ( "settings" ) ;
function inner _refresh ( ) {
panel . content . innerHTML = "" ; //clear
var fUpdate = function ( name , value , options ) {
switch ( name ) {
/ * c a s e " R e n d e r m o d e " :
// Case ""..
if ( options . values && options . key ) {
var kV = Object . values ( options . values ) . indexOf ( value ) ;
if ( kV >= 0 && options . values [ kV ] ) {
console . debug ( "update graph options: " + options . key + ": " + kV ) ;
graphcanvas [ options . key ] = kV ;
//console.debug(graphcanvas);
break ;
}
}
console . warn ( "unexpected options" ) ;
console . debug ( options ) ;
break ; * /
default :
//console.debug("want to update graph options: "+name+": "+value);
if ( options && options . key ) {
name = options . key ;
}
if ( options . values ) {
value = Object . values ( options . values ) . indexOf ( value ) ;
}
//console.debug("update graph option: "+name+": "+value);
graphcanvas [ name ] = value ;
break ;
}
} ;
// panel.addWidget( "string", "Graph name", "", {}, fUpdate); // implement
var aProps = LiteGraph . availableCanvasOptions ;
aProps . sort ( ) ;
for ( pI in aProps ) {
var pX = aProps [ pI ] ;
panel . addWidget ( "boolean" , pX , graphcanvas [ pX ] , { key : pX , on : "True" , off : "False" } , fUpdate ) ;
}
var aLinks = [ graphcanvas . links _render _mode ] ;
panel . addWidget ( "combo" , "Render mode" , LiteGraph . LINK _RENDER _MODES [ graphcanvas . links _render _mode ] , { key : "links_render_mode" , values : LiteGraph . LINK _RENDER _MODES } , fUpdate ) ;
panel . addSeparator ( ) ;
panel . footer . innerHTML = "" ; // clear
}
inner _refresh ( ) ;
graphcanvas . canvas . parentNode . appendChild ( panel ) ;
}
LGraphCanvas . prototype . showShowNodePanel = function ( node )
{
this . SELECTED _NODE = node ;
this . closePanels ( ) ;
var ref _window = this . getCanvasWindow ( ) ;
var that = this ;
var graphcanvas = this ;
2023-04-07 19:11:00 +00:00
var panel = this . createPanel ( node . title || "" , {
2023-01-03 06:53:32 +00:00
closable : true
, window : ref _window
, onOpen : function ( ) {
graphcanvas . NODEPANEL _IS _OPEN = true ;
}
, onClose : function ( ) {
graphcanvas . NODEPANEL _IS _OPEN = false ;
graphcanvas . node _panel = null ;
}
} ) ;
graphcanvas . node _panel = panel ;
panel . id = "node-panel" ;
panel . node = node ;
panel . classList . add ( "settings" ) ;
function inner _refresh ( )
{
panel . content . innerHTML = "" ; //clear
panel . addHTML ( "<span class='node_type'>" + node . type + "</span><span class='node_desc'>" + ( node . constructor . desc || "" ) + "</span><span class='separator'></span>" ) ;
panel . addHTML ( "<h3>Properties</h3>" ) ;
var fUpdate = function ( name , value ) {
graphcanvas . graph . beforeChange ( node ) ;
switch ( name ) {
case "Title" :
node . title = value ;
break ;
case "Mode" :
var kV = Object . values ( LiteGraph . NODE _MODES ) . indexOf ( value ) ;
if ( kV >= 0 && LiteGraph . NODE _MODES [ kV ] ) {
node . changeMode ( kV ) ;
} else {
console . warn ( "unexpected mode: " + value ) ;
}
break ;
case "Color" :
if ( LGraphCanvas . node _colors [ value ] ) {
node . color = LGraphCanvas . node _colors [ value ] . color ;
node . bgcolor = LGraphCanvas . node _colors [ value ] . bgcolor ;
} else {
console . warn ( "unexpected color: " + value ) ;
}
break ;
default :
node . setProperty ( name , value ) ;
break ;
}
graphcanvas . graph . afterChange ( ) ;
graphcanvas . dirty _canvas = true ;
} ;
panel . addWidget ( "string" , "Title" , node . title , { } , fUpdate ) ;
panel . addWidget ( "combo" , "Mode" , LiteGraph . NODE _MODES [ node . mode ] , { values : LiteGraph . NODE _MODES } , fUpdate ) ;
var nodeCol = "" ;
if ( node . color !== undefined ) {
nodeCol = Object . keys ( LGraphCanvas . node _colors ) . filter ( function ( nK ) { return LGraphCanvas . node _colors [ nK ] . color == node . color ; } ) ;
}
panel . addWidget ( "combo" , "Color" , nodeCol , { values : Object . keys ( LGraphCanvas . node _colors ) } , fUpdate ) ;
for ( var pName in node . properties )
{
var value = node . properties [ pName ] ;
var info = node . getPropertyInfo ( pName ) ;
var type = info . type || "string" ;
//in case the user wants control over the side panel widget
if ( node . onAddPropertyToPanel && node . onAddPropertyToPanel ( pName , panel ) )
continue ;
panel . addWidget ( info . widget || info . type , pName , value , info , fUpdate ) ;
}
panel . addSeparator ( ) ;
if ( node . onShowCustomPanelInfo )
node . onShowCustomPanelInfo ( panel ) ;
panel . footer . innerHTML = "" ; // clear
panel . addButton ( "Delete" , function ( ) {
if ( node . block _delete )
return ;
node . graph . remove ( node ) ;
panel . close ( ) ;
} ) . classList . add ( "delete" ) ;
}
panel . inner _showCodePad = function ( propname )
{
panel . classList . remove ( "settings" ) ;
panel . classList . add ( "centered" ) ;
/*if(window.CodeFlask) / / disabled for now
{
panel . content . innerHTML = "<div class='code'></div>" ;
var flask = new CodeFlask ( "div.code" , { language : 'js' } ) ;
flask . updateCode ( node . properties [ propname ] ) ;
flask . onUpdate ( function ( code ) {
node . setProperty ( propname , code ) ;
} ) ;
}
else
{ * /
panel . alt _content . innerHTML = "<textarea class='code'></textarea>" ;
var textarea = panel . alt _content . querySelector ( "textarea" ) ;
var fDoneWith = function ( ) {
panel . toggleAltContent ( false ) ; //if(node_prop_div) node_prop_div.style.display = "block"; // panel.close();
panel . toggleFooterVisibility ( true ) ;
textarea . parentNode . removeChild ( textarea ) ;
panel . classList . add ( "settings" ) ;
panel . classList . remove ( "centered" ) ;
inner _refresh ( ) ;
}
textarea . value = node . properties [ propname ] ;
textarea . addEventListener ( "keydown" , function ( e ) {
if ( e . code == "Enter" && e . ctrlKey )
{
node . setProperty ( propname , textarea . value ) ;
fDoneWith ( ) ;
}
} ) ;
panel . toggleAltContent ( true ) ;
panel . toggleFooterVisibility ( false ) ;
textarea . style . height = "calc(100% - 40px)" ;
/*}*/
var assign = panel . addButton ( "Assign" , function ( ) {
node . setProperty ( propname , textarea . value ) ;
fDoneWith ( ) ;
} ) ;
panel . alt _content . appendChild ( assign ) ; //panel.content.appendChild(assign);
var button = panel . addButton ( "Close" , fDoneWith ) ;
button . style . float = "right" ;
panel . alt _content . appendChild ( button ) ; // panel.content.appendChild(button);
}
inner _refresh ( ) ;
this . canvas . parentNode . appendChild ( panel ) ;
}
LGraphCanvas . prototype . showSubgraphPropertiesDialog = function ( node )
{
console . log ( "showing subgraph properties dialog" ) ;
var old _panel = this . canvas . parentNode . querySelector ( ".subgraph_dialog" ) ;
if ( old _panel )
old _panel . close ( ) ;
var panel = this . createPanel ( "Subgraph Inputs" , { closable : true , width : 500 } ) ;
panel . node = node ;
panel . classList . add ( "subgraph_dialog" ) ;
function inner _refresh ( )
{
panel . clear ( ) ;
//show currents
if ( node . inputs )
for ( var i = 0 ; i < node . inputs . length ; ++ i )
{
var input = node . inputs [ i ] ;
if ( input . not _subgraph _input )
continue ;
var html = "<button>✕</button> <span class='bullet_icon'></span><span class='name'></span><span class='type'></span>" ;
var elem = panel . addHTML ( html , "subgraph_property" ) ;
elem . dataset [ "name" ] = input . name ;
elem . dataset [ "slot" ] = i ;
elem . querySelector ( ".name" ) . innerText = input . name ;
elem . querySelector ( ".type" ) . innerText = input . type ;
elem . querySelector ( "button" ) . addEventListener ( "click" , function ( e ) {
node . removeInput ( Number ( this . parentNode . dataset [ "slot" ] ) ) ;
inner _refresh ( ) ;
} ) ;
}
}
//add extra
var html = " + <span class='label'>Name</span><input class='name'/><span class='label'>Type</span><input class='type'></input><button>+</button>" ;
var elem = panel . addHTML ( html , "subgraph_property extra" , true ) ;
elem . querySelector ( "button" ) . addEventListener ( "click" , function ( e ) {
var elem = this . parentNode ;
var name = elem . querySelector ( ".name" ) . value ;
var type = elem . querySelector ( ".type" ) . value ;
if ( ! name || node . findInputSlot ( name ) != - 1 )
return ;
node . addInput ( name , type ) ;
elem . querySelector ( ".name" ) . value = "" ;
elem . querySelector ( ".type" ) . value = "" ;
inner _refresh ( ) ;
} ) ;
inner _refresh ( ) ;
this . canvas . parentNode . appendChild ( panel ) ;
return panel ;
}
LGraphCanvas . prototype . showSubgraphPropertiesDialogRight = function ( node ) {
// console.log("showing subgraph properties dialog");
var that = this ;
// old_panel if old_panel is exist close it
var old _panel = this . canvas . parentNode . querySelector ( ".subgraph_dialog" ) ;
if ( old _panel )
old _panel . close ( ) ;
// new panel
var panel = this . createPanel ( "Subgraph Outputs" , { closable : true , width : 500 } ) ;
panel . node = node ;
panel . classList . add ( "subgraph_dialog" ) ;
function inner _refresh ( ) {
panel . clear ( ) ;
//show currents
if ( node . outputs )
for ( var i = 0 ; i < node . outputs . length ; ++ i ) {
var input = node . outputs [ i ] ;
if ( input . not _subgraph _output )
continue ;
var html = "<button>✕</button> <span class='bullet_icon'></span><span class='name'></span><span class='type'></span>" ;
var elem = panel . addHTML ( html , "subgraph_property" ) ;
elem . dataset [ "name" ] = input . name ;
elem . dataset [ "slot" ] = i ;
elem . querySelector ( ".name" ) . innerText = input . name ;
elem . querySelector ( ".type" ) . innerText = input . type ;
elem . querySelector ( "button" ) . addEventListener ( "click" , function ( e ) {
node . removeOutput ( Number ( this . parentNode . dataset [ "slot" ] ) ) ;
inner _refresh ( ) ;
} ) ;
}
}
//add extra
var html = " + <span class='label'>Name</span><input class='name'/><span class='label'>Type</span><input class='type'></input><button>+</button>" ;
var elem = panel . addHTML ( html , "subgraph_property extra" , true ) ;
elem . querySelector ( ".name" ) . addEventListener ( "keydown" , function ( e ) {
if ( e . keyCode == 13 ) {
addOutput . apply ( this )
}
} )
elem . querySelector ( "button" ) . addEventListener ( "click" , function ( e ) {
addOutput . apply ( this )
} ) ;
function addOutput ( ) {
var elem = this . parentNode ;
var name = elem . querySelector ( ".name" ) . value ;
var type = elem . querySelector ( ".type" ) . value ;
if ( ! name || node . findOutputSlot ( name ) != - 1 )
return ;
node . addOutput ( name , type ) ;
elem . querySelector ( ".name" ) . value = "" ;
elem . querySelector ( ".type" ) . value = "" ;
inner _refresh ( ) ;
}
inner _refresh ( ) ;
this . canvas . parentNode . appendChild ( panel ) ;
return panel ;
}
LGraphCanvas . prototype . checkPanels = function ( )
{
if ( ! this . canvas )
return ;
var panels = this . canvas . parentNode . querySelectorAll ( ".litegraph.dialog" ) ;
for ( var i = 0 ; i < panels . length ; ++ i )
{
var panel = panels [ i ] ;
if ( ! panel . node )
continue ;
if ( ! panel . node . graph || panel . graph != this . graph )
panel . close ( ) ;
}
}
LGraphCanvas . onMenuNodeCollapse = function ( value , options , e , menu , node ) {
node . graph . beforeChange ( /*?*/ ) ;
var fApplyMultiNode = function ( node ) {
node . collapse ( ) ;
}
var graphcanvas = LGraphCanvas . active _canvas ;
if ( ! graphcanvas . selected _nodes || Object . keys ( graphcanvas . selected _nodes ) . length <= 1 ) {
fApplyMultiNode ( node ) ;
} else {
for ( var i in graphcanvas . selected _nodes ) {
fApplyMultiNode ( graphcanvas . selected _nodes [ i ] ) ;
}
}
node . graph . afterChange ( /*?*/ ) ;
} ;
LGraphCanvas . onMenuNodePin = function ( value , options , e , menu , node ) {
node . pin ( ) ;
} ;
LGraphCanvas . onMenuNodeMode = function ( value , options , e , menu , node ) {
new LiteGraph . ContextMenu (
LiteGraph . NODE _MODES ,
{ event : e , callback : inner _clicked , parentMenu : menu , node : node }
) ;
function inner _clicked ( v ) {
if ( ! node ) {
return ;
}
var kV = Object . values ( LiteGraph . NODE _MODES ) . indexOf ( v ) ;
var fApplyMultiNode = function ( node ) {
if ( kV >= 0 && LiteGraph . NODE _MODES [ kV ] )
node . changeMode ( kV ) ;
else {
console . warn ( "unexpected mode: " + v ) ;
node . changeMode ( LiteGraph . ALWAYS ) ;
}
}
var graphcanvas = LGraphCanvas . active _canvas ;
if ( ! graphcanvas . selected _nodes || Object . keys ( graphcanvas . selected _nodes ) . length <= 1 ) {
fApplyMultiNode ( node ) ;
} else {
for ( var i in graphcanvas . selected _nodes ) {
fApplyMultiNode ( graphcanvas . selected _nodes [ i ] ) ;
}
}
}
return false ;
} ;
LGraphCanvas . onMenuNodeColors = function ( value , options , e , menu , node ) {
if ( ! node ) {
throw "no node for color" ;
}
var values = [ ] ;
values . push ( {
value : null ,
content :
"<span style='display: block; padding-left: 4px;'>No color</span>"
} ) ;
for ( var i in LGraphCanvas . node _colors ) {
var color = LGraphCanvas . node _colors [ i ] ;
var value = {
value : i ,
content :
"<span style='display: block; color: #999; padding-left: 4px; border-left: 8px solid " +
color . color +
"; background-color:" +
color . bgcolor +
"'>" +
i +
"</span>"
} ;
values . push ( value ) ;
}
new LiteGraph . ContextMenu ( values , {
event : e ,
callback : inner _clicked ,
parentMenu : menu ,
node : node
} ) ;
function inner _clicked ( v ) {
if ( ! node ) {
return ;
}
var color = v . value ? LGraphCanvas . node _colors [ v . value ] : null ;
var fApplyColor = function ( node ) {
if ( color ) {
if ( node . constructor === LiteGraph . LGraphGroup ) {
node . color = color . groupcolor ;
} else {
node . color = color . color ;
node . bgcolor = color . bgcolor ;
}
} else {
delete node . color ;
delete node . bgcolor ;
}
}
var graphcanvas = LGraphCanvas . active _canvas ;
if ( ! graphcanvas . selected _nodes || Object . keys ( graphcanvas . selected _nodes ) . length <= 1 ) {
fApplyColor ( node ) ;
} else {
for ( var i in graphcanvas . selected _nodes ) {
fApplyColor ( graphcanvas . selected _nodes [ i ] ) ;
}
}
node . setDirtyCanvas ( true , true ) ;
}
return false ;
} ;
LGraphCanvas . onMenuNodeShapes = function ( value , options , e , menu , node ) {
if ( ! node ) {
throw "no node passed" ;
}
new LiteGraph . ContextMenu ( LiteGraph . VALID _SHAPES , {
event : e ,
callback : inner _clicked ,
parentMenu : menu ,
node : node
} ) ;
function inner _clicked ( v ) {
if ( ! node ) {
return ;
}
node . graph . beforeChange ( /*?*/ ) ; //node
var fApplyMultiNode = function ( node ) {
node . shape = v ;
}
var graphcanvas = LGraphCanvas . active _canvas ;
if ( ! graphcanvas . selected _nodes || Object . keys ( graphcanvas . selected _nodes ) . length <= 1 ) {
fApplyMultiNode ( node ) ;
} else {
for ( var i in graphcanvas . selected _nodes ) {
fApplyMultiNode ( graphcanvas . selected _nodes [ i ] ) ;
}
}
node . graph . afterChange ( /*?*/ ) ; //node
node . setDirtyCanvas ( true ) ;
}
return false ;
} ;
LGraphCanvas . onMenuNodeRemove = function ( value , options , e , menu , node ) {
if ( ! node ) {
throw "no node passed" ;
}
var graph = node . graph ;
graph . beforeChange ( ) ;
var fApplyMultiNode = function ( node ) {
if ( node . removable === false ) {
return ;
}
graph . remove ( node ) ;
}
var graphcanvas = LGraphCanvas . active _canvas ;
if ( ! graphcanvas . selected _nodes || Object . keys ( graphcanvas . selected _nodes ) . length <= 1 ) {
fApplyMultiNode ( node ) ;
} else {
for ( var i in graphcanvas . selected _nodes ) {
fApplyMultiNode ( graphcanvas . selected _nodes [ i ] ) ;
}
}
graph . afterChange ( ) ;
node . setDirtyCanvas ( true , true ) ;
} ;
LGraphCanvas . onMenuNodeToSubgraph = function ( value , options , e , menu , node ) {
var graph = node . graph ;
var graphcanvas = LGraphCanvas . active _canvas ;
if ( ! graphcanvas ) //??
return ;
var nodes _list = Object . values ( graphcanvas . selected _nodes || { } ) ;
if ( ! nodes _list . length )
nodes _list = [ node ] ;
var subgraph _node = LiteGraph . createNode ( "graph/subgraph" ) ;
subgraph _node . pos = node . pos . concat ( ) ;
graph . add ( subgraph _node ) ;
subgraph _node . buildFromNodes ( nodes _list ) ;
graphcanvas . deselectAllNodes ( ) ;
node . setDirtyCanvas ( true , true ) ;
} ;
LGraphCanvas . onMenuNodeClone = function ( value , options , e , menu , node ) {
node . graph . beforeChange ( ) ;
var newSelected = { } ;
var fApplyMultiNode = function ( node ) {
if ( node . clonable == false ) {
return ;
}
var newnode = node . clone ( ) ;
if ( ! newnode ) {
return ;
}
newnode . pos = [ node . pos [ 0 ] + 5 , node . pos [ 1 ] + 5 ] ;
node . graph . add ( newnode ) ;
newSelected [ newnode . id ] = newnode ;
}
var graphcanvas = LGraphCanvas . active _canvas ;
if ( ! graphcanvas . selected _nodes || Object . keys ( graphcanvas . selected _nodes ) . length <= 1 ) {
fApplyMultiNode ( node ) ;
} else {
for ( var i in graphcanvas . selected _nodes ) {
fApplyMultiNode ( graphcanvas . selected _nodes [ i ] ) ;
}
}
if ( Object . keys ( newSelected ) . length ) {
graphcanvas . selectNodes ( newSelected ) ;
}
node . graph . afterChange ( ) ;
node . setDirtyCanvas ( true , true ) ;
} ;
LGraphCanvas . node _colors = {
red : { color : "#322" , bgcolor : "#533" , groupcolor : "#A88" } ,
brown : { color : "#332922" , bgcolor : "#593930" , groupcolor : "#b06634" } ,
green : { color : "#232" , bgcolor : "#353" , groupcolor : "#8A8" } ,
blue : { color : "#223" , bgcolor : "#335" , groupcolor : "#88A" } ,
pale _blue : {
color : "#2a363b" ,
bgcolor : "#3f5159" ,
groupcolor : "#3f789e"
} ,
cyan : { color : "#233" , bgcolor : "#355" , groupcolor : "#8AA" } ,
purple : { color : "#323" , bgcolor : "#535" , groupcolor : "#a1309b" } ,
yellow : { color : "#432" , bgcolor : "#653" , groupcolor : "#b58b2a" } ,
black : { color : "#222" , bgcolor : "#000" , groupcolor : "#444" }
} ;
LGraphCanvas . prototype . getCanvasMenuOptions = function ( ) {
var options = null ;
var that = this ;
if ( this . getMenuOptions ) {
options = this . getMenuOptions ( ) ;
} else {
options = [
{
content : "Add Node" ,
has _submenu : true ,
callback : LGraphCanvas . onMenuAdd
} ,
{ content : "Add Group" , callback : LGraphCanvas . onGroupAdd } ,
//{ content: "Arrange", callback: that.graph.arrange },
//{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll }
] ;
/ * i f ( L i t e G r a p h . s h o w C a n v a s O p t i o n s ) {
options . push ( { content : "Options" , callback : that . showShowGraphOptionsPanel } ) ;
} * /
if ( this . _graph _stack && this . _graph _stack . length > 0 ) {
options . push ( null , {
content : "Close subgraph" ,
callback : this . closeSubgraph . bind ( this )
} ) ;
}
}
if ( this . getExtraMenuOptions ) {
var extra = this . getExtraMenuOptions ( this , options ) ;
if ( extra ) {
options = options . concat ( extra ) ;
}
}
return options ;
} ;
//called by processContextMenu to extract the menu list
LGraphCanvas . prototype . getNodeMenuOptions = function ( node ) {
var options = null ;
if ( node . getMenuOptions ) {
options = node . getMenuOptions ( this ) ;
} else {
options = [
{
content : "Inputs" ,
has _submenu : true ,
disabled : true ,
callback : LGraphCanvas . showMenuNodeOptionalInputs
} ,
{
content : "Outputs" ,
has _submenu : true ,
disabled : true ,
callback : LGraphCanvas . showMenuNodeOptionalOutputs
} ,
null ,
{
content : "Properties" ,
has _submenu : true ,
callback : LGraphCanvas . onShowMenuNodeProperties
} ,
null ,
{
content : "Title" ,
callback : LGraphCanvas . onShowPropertyEditor
} ,
{
content : "Mode" ,
has _submenu : true ,
callback : LGraphCanvas . onMenuNodeMode
} ] ;
if ( node . resizable !== false ) {
options . push ( {
content : "Resize" , callback : LGraphCanvas . onMenuResizeNode
} ) ;
}
options . push (
{
content : "Collapse" ,
callback : LGraphCanvas . onMenuNodeCollapse
} ,
{ content : "Pin" , callback : LGraphCanvas . onMenuNodePin } ,
{
content : "Colors" ,
has _submenu : true ,
callback : LGraphCanvas . onMenuNodeColors
} ,
{
content : "Shapes" ,
has _submenu : true ,
callback : LGraphCanvas . onMenuNodeShapes
} ,
null
) ;
}
if ( node . onGetInputs ) {
var inputs = node . onGetInputs ( ) ;
if ( inputs && inputs . length ) {
options [ 0 ] . disabled = false ;
}
}
if ( node . onGetOutputs ) {
var outputs = node . onGetOutputs ( ) ;
if ( outputs && outputs . length ) {
options [ 1 ] . disabled = false ;
}
}
if ( node . getExtraMenuOptions ) {
var extra = node . getExtraMenuOptions ( this , options ) ;
if ( extra ) {
extra . push ( null ) ;
options = extra . concat ( options ) ;
}
}
if ( node . clonable !== false ) {
options . push ( {
content : "Clone" ,
callback : LGraphCanvas . onMenuNodeClone
} ) ;
}
if ( 0 ) //TODO
options . push ( {
content : "To Subgraph" ,
callback : LGraphCanvas . onMenuNodeToSubgraph
} ) ;
options . push ( null , {
content : "Remove" ,
disabled : ! ( node . removable !== false && ! node . block _delete ) ,
callback : LGraphCanvas . onMenuNodeRemove
} ) ;
if ( node . graph && node . graph . onGetNodeMenuOptions ) {
node . graph . onGetNodeMenuOptions ( options , node ) ;
}
return options ;
} ;
LGraphCanvas . prototype . getGroupMenuOptions = function ( node ) {
var o = [
{ content : "Title" , callback : LGraphCanvas . onShowPropertyEditor } ,
{
content : "Color" ,
has _submenu : true ,
callback : LGraphCanvas . onMenuNodeColors
} ,
{
content : "Font size" ,
property : "font_size" ,
type : "Number" ,
callback : LGraphCanvas . onShowPropertyEditor
} ,
null ,
{ content : "Remove" , callback : LGraphCanvas . onMenuNodeRemove }
] ;
return o ;
} ;
LGraphCanvas . prototype . processContextMenu = function ( node , event ) {
var that = this ;
var canvas = LGraphCanvas . active _canvas ;
var ref _window = canvas . getCanvasWindow ( ) ;
var menu _info = null ;
var options = {
event : event ,
callback : inner _option _clicked ,
extra : node
} ;
if ( node )
options . title = node . type ;
//check if mouse is in input
var slot = null ;
if ( node ) {
slot = node . getSlotInPosition ( event . canvasX , event . canvasY ) ;
LGraphCanvas . active _node = node ;
}
if ( slot ) {
//on slot
menu _info = [ ] ;
if ( node . getSlotMenuOptions ) {
menu _info = node . getSlotMenuOptions ( slot ) ;
} else {
if (
slot &&
slot . output &&
slot . output . links &&
slot . output . links . length
) {
menu _info . push ( { content : "Disconnect Links" , slot : slot } ) ;
}
var _slot = slot . input || slot . output ;
if ( _slot . removable ) {
menu _info . push (
_slot . locked
? "Cannot remove"
: { content : "Remove Slot" , slot : slot }
) ;
}
if ( ! _slot . nameLocked ) {
menu _info . push ( { content : "Rename Slot" , slot : slot } ) ;
}
}
options . title =
( slot . input ? slot . input . type : slot . output . type ) || "*" ;
if ( slot . input && slot . input . type == LiteGraph . ACTION ) {
options . title = "Action" ;
}
if ( slot . output && slot . output . type == LiteGraph . EVENT ) {
options . title = "Event" ;
}
} else {
if ( node ) {
//on node
menu _info = this . getNodeMenuOptions ( node ) ;
} else {
menu _info = this . getCanvasMenuOptions ( ) ;
var group = this . graph . getGroupOnPos (
event . canvasX ,
event . canvasY
) ;
if ( group ) {
//on group
menu _info . push ( null , {
content : "Edit Group" ,
has _submenu : true ,
submenu : {
title : "Group" ,
extra : group ,
options : this . getGroupMenuOptions ( group )
}
} ) ;
}
}
}
//show menu
if ( ! menu _info ) {
return ;
}
var menu = new LiteGraph . ContextMenu ( menu _info , options , ref _window ) ;
function inner _option _clicked ( v , options , e ) {
if ( ! v ) {
return ;
}
if ( v . content == "Remove Slot" ) {
var info = v . slot ;
node . graph . beforeChange ( ) ;
if ( info . input ) {
node . removeInput ( info . slot ) ;
} else if ( info . output ) {
node . removeOutput ( info . slot ) ;
}
node . graph . afterChange ( ) ;
return ;
} else if ( v . content == "Disconnect Links" ) {
var info = v . slot ;
node . graph . beforeChange ( ) ;
if ( info . output ) {
node . disconnectOutput ( info . slot ) ;
} else if ( info . input ) {
node . disconnectInput ( info . slot ) ;
}
node . graph . afterChange ( ) ;
return ;
} else if ( v . content == "Rename Slot" ) {
var info = v . slot ;
var slot _info = info . input
? node . getInputInfo ( info . slot )
: node . getOutputInfo ( info . slot ) ;
var dialog = that . createDialog (
"<span class='name'>Name</span><input autofocus type='text'/><button>OK</button>" ,
options
) ;
var input = dialog . querySelector ( "input" ) ;
if ( input && slot _info ) {
input . value = slot _info . label || "" ;
}
var inner = function ( ) {
node . graph . beforeChange ( ) ;
if ( input . value ) {
if ( slot _info ) {
slot _info . label = input . value ;
}
that . setDirty ( true ) ;
}
dialog . close ( ) ;
node . graph . afterChange ( ) ;
}
dialog . querySelector ( "button" ) . addEventListener ( "click" , inner ) ;
input . addEventListener ( "keydown" , function ( e ) {
dialog . is _modified = true ;
if ( e . keyCode == 27 ) {
//ESC
dialog . close ( ) ;
} else if ( e . keyCode == 13 ) {
inner ( ) ; // save
} else if ( e . keyCode != 13 && e . target . localName != "textarea" ) {
return ;
}
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
} ) ;
input . focus ( ) ;
}
//if(v.callback)
// return v.callback.call(that, node, options, e, menu, that, event );
}
} ;
//API *************************************************
//like rect but rounded corners
if ( typeof ( window ) != "undefined" && window . CanvasRenderingContext2D && ! window . CanvasRenderingContext2D . prototype . roundRect ) {
window . CanvasRenderingContext2D . prototype . roundRect = function (
x ,
y ,
w ,
h ,
radius ,
radius _low
) {
var top _left _radius = 0 ;
var top _right _radius = 0 ;
var bottom _left _radius = 0 ;
var bottom _right _radius = 0 ;
if ( radius === 0 )
{
this . rect ( x , y , w , h ) ;
return ;
}
if ( radius _low === undefined )
radius _low = radius ;
//make it compatible with official one
if ( radius != null && radius . constructor === Array )
{
if ( radius . length == 1 )
top _left _radius = top _right _radius = bottom _left _radius = bottom _right _radius = radius [ 0 ] ;
else if ( radius . length == 2 )
{
top _left _radius = bottom _right _radius = radius [ 0 ] ;
top _right _radius = bottom _left _radius = radius [ 1 ] ;
}
else if ( radius . length == 4 )
{
top _left _radius = radius [ 0 ] ;
top _right _radius = radius [ 1 ] ;
bottom _left _radius = radius [ 2 ] ;
bottom _right _radius = radius [ 3 ] ;
}
else
return ;
}
else //old using numbers
{
top _left _radius = radius || 0 ;
top _right _radius = radius || 0 ;
bottom _left _radius = radius _low || 0 ;
bottom _right _radius = radius _low || 0 ;
}
//top right
this . moveTo ( x + top _left _radius , y ) ;
this . lineTo ( x + w - top _right _radius , y ) ;
this . quadraticCurveTo ( x + w , y , x + w , y + top _right _radius ) ;
//bottom right
this . lineTo ( x + w , y + h - bottom _right _radius ) ;
this . quadraticCurveTo (
x + w ,
y + h ,
x + w - bottom _right _radius ,
y + h
) ;
//bottom left
this . lineTo ( x + bottom _right _radius , y + h ) ;
this . quadraticCurveTo ( x , y + h , x , y + h - bottom _left _radius ) ;
//top left
this . lineTo ( x , y + bottom _left _radius ) ;
this . quadraticCurveTo ( x , y , x + top _left _radius , y ) ;
} ;
} //if
function compareObjects ( a , b ) {
for ( var i in a ) {
if ( a [ i ] != b [ i ] ) {
return false ;
}
}
return true ;
}
LiteGraph . compareObjects = compareObjects ;
function distance ( a , b ) {
return Math . sqrt (
( b [ 0 ] - a [ 0 ] ) * ( b [ 0 ] - a [ 0 ] ) + ( b [ 1 ] - a [ 1 ] ) * ( b [ 1 ] - a [ 1 ] )
) ;
}
LiteGraph . distance = distance ;
function colorToString ( c ) {
return (
"rgba(" +
Math . round ( c [ 0 ] * 255 ) . toFixed ( ) +
"," +
Math . round ( c [ 1 ] * 255 ) . toFixed ( ) +
"," +
Math . round ( c [ 2 ] * 255 ) . toFixed ( ) +
"," +
( c . length == 4 ? c [ 3 ] . toFixed ( 2 ) : "1.0" ) +
")"
) ;
}
LiteGraph . colorToString = colorToString ;
function isInsideRectangle ( x , y , left , top , width , height ) {
if ( left < x && left + width > x && top < y && top + height > y ) {
return true ;
}
return false ;
}
LiteGraph . isInsideRectangle = isInsideRectangle ;
//[minx,miny,maxx,maxy]
function growBounding ( bounding , x , y ) {
if ( x < bounding [ 0 ] ) {
bounding [ 0 ] = x ;
} else if ( x > bounding [ 2 ] ) {
bounding [ 2 ] = x ;
}
if ( y < bounding [ 1 ] ) {
bounding [ 1 ] = y ;
} else if ( y > bounding [ 3 ] ) {
bounding [ 3 ] = y ;
}
}
LiteGraph . growBounding = growBounding ;
//point inside bounding box
function isInsideBounding ( p , bb ) {
if (
p [ 0 ] < bb [ 0 ] [ 0 ] ||
p [ 1 ] < bb [ 0 ] [ 1 ] ||
p [ 0 ] > bb [ 1 ] [ 0 ] ||
p [ 1 ] > bb [ 1 ] [ 1 ]
) {
return false ;
}
return true ;
}
LiteGraph . isInsideBounding = isInsideBounding ;
//bounding overlap, format: [ startx, starty, width, height ]
function overlapBounding ( a , b ) {
var A _end _x = a [ 0 ] + a [ 2 ] ;
var A _end _y = a [ 1 ] + a [ 3 ] ;
var B _end _x = b [ 0 ] + b [ 2 ] ;
var B _end _y = b [ 1 ] + b [ 3 ] ;
if (
a [ 0 ] > B _end _x ||
a [ 1 ] > B _end _y ||
A _end _x < b [ 0 ] ||
A _end _y < b [ 1 ]
) {
return false ;
}
return true ;
}
LiteGraph . overlapBounding = overlapBounding ;
//Convert a hex value to its decimal value - the inputted hex must be in the
// format of a hex triplet - the kind we use for HTML colours. The function
// will return an array with three values.
function hex2num ( hex ) {
if ( hex . charAt ( 0 ) == "#" ) {
hex = hex . slice ( 1 ) ;
} //Remove the '#' char - if there is one.
hex = hex . toUpperCase ( ) ;
var hex _alphabets = "0123456789ABCDEF" ;
var value = new Array ( 3 ) ;
var k = 0 ;
var int1 , int2 ;
for ( var i = 0 ; i < 6 ; i += 2 ) {
int1 = hex _alphabets . indexOf ( hex . charAt ( i ) ) ;
int2 = hex _alphabets . indexOf ( hex . charAt ( i + 1 ) ) ;
value [ k ] = int1 * 16 + int2 ;
k ++ ;
}
return value ;
}
LiteGraph . hex2num = hex2num ;
//Give a array with three values as the argument and the function will return
// the corresponding hex triplet.
function num2hex ( triplet ) {
var hex _alphabets = "0123456789ABCDEF" ;
var hex = "#" ;
var int1 , int2 ;
for ( var i = 0 ; i < 3 ; i ++ ) {
int1 = triplet [ i ] / 16 ;
int2 = triplet [ i ] % 16 ;
hex += hex _alphabets . charAt ( int1 ) + hex _alphabets . charAt ( int2 ) ;
}
return hex ;
}
LiteGraph . num2hex = num2hex ;
/* LiteGraph GUI elements used for canvas editing *************************************/
/ * *
* ContextMenu from LiteGUI
*
* @ class ContextMenu
* @ constructor
* @ param { Array } values ( allows object { title : "Nice text" , callback : function ... } )
* @ param { Object } options [ optional ] Some options : \
* - title : title to show on top of the menu
* - callback : function to call when an option is clicked , it receives the item information
* - ignore _item _callbacks : ignores the callback inside the item , it just calls the options . callback
* - event : you can pass a MouseEvent , this way the ContextMenu appears in that position
* /
function ContextMenu ( values , options ) {
options = options || { } ;
this . options = options ;
var that = this ;
//to link a menu with its parent
if ( options . parentMenu ) {
if ( options . parentMenu . constructor !== this . constructor ) {
console . error (
"parentMenu must be of class ContextMenu, ignoring it"
) ;
options . parentMenu = null ;
} else {
this . parentMenu = options . parentMenu ;
this . parentMenu . lock = true ;
this . parentMenu . current _submenu = this ;
}
}
var eventClass = null ;
if ( options . event ) //use strings because comparing classes between windows doesnt work
eventClass = options . event . constructor . name ;
if ( eventClass !== "MouseEvent" &&
eventClass !== "CustomEvent" &&
eventClass !== "PointerEvent"
) {
console . error (
"Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. (" + eventClass + ")"
) ;
options . event = null ;
}
var root = document . createElement ( "div" ) ;
root . className = "litegraph litecontextmenu litemenubar-panel" ;
if ( options . className ) {
root . className += " " + options . className ;
}
root . style . minWidth = 100 ;
root . style . minHeight = 100 ;
root . style . pointerEvents = "none" ;
setTimeout ( function ( ) {
root . style . pointerEvents = "auto" ;
} , 100 ) ; //delay so the mouse up event is not caught by this element
//this prevents the default context browser menu to open in case this menu was created when pressing right button
LiteGraph . pointerListenerAdd ( root , "up" ,
function ( e ) {
//console.log("pointerevents: ContextMenu up root prevent");
e . preventDefault ( ) ;
return true ;
} ,
true
) ;
root . addEventListener (
"contextmenu" ,
function ( e ) {
if ( e . button != 2 ) {
//right button
return false ;
}
e . preventDefault ( ) ;
return false ;
} ,
true
) ;
LiteGraph . pointerListenerAdd ( root , "down" ,
function ( e ) {
//console.log("pointerevents: ContextMenu down");
if ( e . button == 2 ) {
that . close ( ) ;
e . preventDefault ( ) ;
return true ;
}
} ,
true
) ;
function on _mouse _wheel ( e ) {
var pos = parseInt ( root . style . top ) ;
root . style . top =
( pos + e . deltaY * options . scroll _speed ) . toFixed ( ) + "px" ;
e . preventDefault ( ) ;
return true ;
}
if ( ! options . scroll _speed ) {
options . scroll _speed = 0.1 ;
}
root . addEventListener ( "wheel" , on _mouse _wheel , true ) ;
root . addEventListener ( "mousewheel" , on _mouse _wheel , true ) ;
this . root = root ;
//title
if ( options . title ) {
var element = document . createElement ( "div" ) ;
element . className = "litemenu-title" ;
element . innerHTML = options . title ;
root . appendChild ( element ) ;
}
//entries
var num = 0 ;
for ( var i = 0 ; i < values . length ; i ++ ) {
var name = values . constructor == Array ? values [ i ] : i ;
if ( name != null && name . constructor !== String ) {
name = name . content === undefined ? String ( name ) : name . content ;
}
var value = values [ i ] ;
this . addItem ( name , value , options ) ;
num ++ ;
}
//close on leave? touch enabled devices won't work TODO use a global device detector and condition on that
/ * L i t e G r a p h . p o i n t e r L i s t e n e r A d d ( r o o t , " l e a v e " , f u n c t i o n ( e ) {
console . log ( "pointerevents: ContextMenu leave" ) ;
if ( that . lock ) {
return ;
}
if ( root . closing _timer ) {
clearTimeout ( root . closing _timer ) ;
}
root . closing _timer = setTimeout ( that . close . bind ( that , e ) , 500 ) ;
//that.close(e);
} ) ; * /
LiteGraph . pointerListenerAdd ( root , "enter" , function ( e ) {
//console.log("pointerevents: ContextMenu enter");
if ( root . closing _timer ) {
clearTimeout ( root . closing _timer ) ;
}
} ) ;
//insert before checking position
var root _document = document ;
if ( options . event ) {
root _document = options . event . target . ownerDocument ;
}
if ( ! root _document ) {
root _document = document ;
}
if ( root _document . fullscreenElement )
root _document . fullscreenElement . appendChild ( root ) ;
else
root _document . body . appendChild ( root ) ;
//compute best position
var left = options . left || 0 ;
var top = options . top || 0 ;
if ( options . event ) {
left = options . event . clientX - 10 ;
top = options . event . clientY - 10 ;
if ( options . title ) {
top -= 20 ;
}
if ( options . parentMenu ) {
var rect = options . parentMenu . root . getBoundingClientRect ( ) ;
left = rect . left + rect . width ;
}
var body _rect = document . body . getBoundingClientRect ( ) ;
var root _rect = root . getBoundingClientRect ( ) ;
if ( body _rect . height == 0 )
console . error ( "document.body height is 0. That is dangerous, set html,body { height: 100%; }" ) ;
if ( body _rect . width && left > body _rect . width - root _rect . width - 10 ) {
left = body _rect . width - root _rect . width - 10 ;
}
if ( body _rect . height && top > body _rect . height - root _rect . height - 10 ) {
top = body _rect . height - root _rect . height - 10 ;
}
}
root . style . left = left + "px" ;
root . style . top = top + "px" ;
if ( options . scale ) {
root . style . transform = "scale(" + options . scale + ")" ;
}
}
ContextMenu . prototype . addItem = function ( name , value , options ) {
var that = this ;
options = options || { } ;
var element = document . createElement ( "div" ) ;
element . className = "litemenu-entry submenu" ;
var disabled = false ;
if ( value === null ) {
element . classList . add ( "separator" ) ;
//element.innerHTML = "<hr/>"
//continue;
} else {
element . innerHTML = value && value . title ? value . title : name ;
element . value = value ;
if ( value ) {
if ( value . disabled ) {
disabled = true ;
element . classList . add ( "disabled" ) ;
}
if ( value . submenu || value . has _submenu ) {
element . classList . add ( "has_submenu" ) ;
}
}
if ( typeof value == "function" ) {
element . dataset [ "value" ] = name ;
element . onclick _callback = value ;
} else {
element . dataset [ "value" ] = value ;
}
if ( value . className ) {
element . className += " " + value . className ;
}
}
this . root . appendChild ( element ) ;
if ( ! disabled ) {
element . addEventListener ( "click" , inner _onclick ) ;
}
if ( options . autoopen ) {
LiteGraph . pointerListenerAdd ( element , "enter" , inner _over ) ;
}
function inner _over ( e ) {
var value = this . value ;
if ( ! value || ! value . has _submenu ) {
return ;
}
//if it is a submenu, autoopen like the item was clicked
inner _onclick . call ( this , e ) ;
}
//menu option clicked
function inner _onclick ( e ) {
var value = this . value ;
var close _parent = true ;
if ( that . current _submenu ) {
that . current _submenu . close ( e ) ;
}
//global callback
if ( options . callback ) {
var r = options . callback . call (
this ,
value ,
options ,
e ,
that ,
options . node
) ;
if ( r === true ) {
close _parent = false ;
}
}
//special cases
if ( value ) {
if (
value . callback &&
! options . ignore _item _callbacks &&
value . disabled !== true
) {
//item callback
var r = value . callback . call (
this ,
value ,
options ,
e ,
that ,
options . extra
) ;
if ( r === true ) {
close _parent = false ;
}
}
if ( value . submenu ) {
if ( ! value . submenu . options ) {
throw "ContextMenu submenu needs options" ;
}
var submenu = new that . constructor ( value . submenu . options , {
callback : value . submenu . callback ,
event : e ,
parentMenu : that ,
ignore _item _callbacks :
value . submenu . ignore _item _callbacks ,
title : value . submenu . title ,
extra : value . submenu . extra ,
autoopen : options . autoopen
} ) ;
close _parent = false ;
}
}
if ( close _parent && ! that . lock ) {
that . close ( ) ;
}
}
return element ;
} ;
ContextMenu . prototype . close = function ( e , ignore _parent _menu ) {
if ( this . root . parentNode ) {
this . root . parentNode . removeChild ( this . root ) ;
}
if ( this . parentMenu && ! ignore _parent _menu ) {
this . parentMenu . lock = false ;
this . parentMenu . current _submenu = null ;
if ( e === undefined ) {
this . parentMenu . close ( ) ;
} else if (
e &&
! ContextMenu . isCursorOverElement ( e , this . parentMenu . root )
) {
ContextMenu . trigger ( this . parentMenu . root , LiteGraph . pointerevents _method + "leave" , e ) ;
}
}
if ( this . current _submenu ) {
this . current _submenu . close ( e , true ) ;
}
if ( this . root . closing _timer ) {
clearTimeout ( this . root . closing _timer ) ;
}
// TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu
// on key press, allow filtering/selecting the context menu elements
} ;
//this code is used to trigger events easily (used in the context menu mouseleave
ContextMenu . trigger = function ( element , event _name , params , origin ) {
var evt = document . createEvent ( "CustomEvent" ) ;
evt . initCustomEvent ( event _name , true , true , params ) ; //canBubble, cancelable, detail
evt . srcElement = origin ;
if ( element . dispatchEvent ) {
element . dispatchEvent ( evt ) ;
} else if ( element . _ _events ) {
element . _ _events . dispatchEvent ( evt ) ;
}
//else nothing seems binded here so nothing to do
return evt ;
} ;
//returns the top most menu
ContextMenu . prototype . getTopMenu = function ( ) {
if ( this . options . parentMenu ) {
return this . options . parentMenu . getTopMenu ( ) ;
}
return this ;
} ;
ContextMenu . prototype . getFirstEvent = function ( ) {
if ( this . options . parentMenu ) {
return this . options . parentMenu . getFirstEvent ( ) ;
}
return this . options . event ;
} ;
ContextMenu . isCursorOverElement = function ( event , element ) {
var left = event . clientX ;
var top = event . clientY ;
var rect = element . getBoundingClientRect ( ) ;
if ( ! rect ) {
return false ;
}
if (
top > rect . top &&
top < rect . top + rect . height &&
left > rect . left &&
left < rect . left + rect . width
) {
return true ;
}
return false ;
} ;
LiteGraph . ContextMenu = ContextMenu ;
LiteGraph . closeAllContextMenus = function ( ref _window ) {
ref _window = ref _window || window ;
var elements = ref _window . document . querySelectorAll ( ".litecontextmenu" ) ;
if ( ! elements . length ) {
return ;
}
var result = [ ] ;
for ( var i = 0 ; i < elements . length ; i ++ ) {
result . push ( elements [ i ] ) ;
}
for ( var i = 0 ; i < result . length ; i ++ ) {
if ( result [ i ] . close ) {
result [ i ] . close ( ) ;
} else if ( result [ i ] . parentNode ) {
result [ i ] . parentNode . removeChild ( result [ i ] ) ;
}
}
} ;
LiteGraph . extendClass = function ( target , origin ) {
for ( var i in origin ) {
//copy class properties
if ( target . hasOwnProperty ( i ) ) {
continue ;
}
target [ i ] = origin [ i ] ;
}
if ( origin . prototype ) {
//copy prototype properties
for ( var i in origin . prototype ) {
//only enumerable
if ( ! origin . prototype . hasOwnProperty ( i ) ) {
continue ;
}
if ( target . prototype . hasOwnProperty ( i ) ) {
//avoid overwriting existing ones
continue ;
}
//copy getters
if ( origin . prototype . _ _lookupGetter _ _ ( i ) ) {
target . prototype . _ _defineGetter _ _ (
i ,
origin . prototype . _ _lookupGetter _ _ ( i )
) ;
} else {
target . prototype [ i ] = origin . prototype [ i ] ;
}
//and setters
if ( origin . prototype . _ _lookupSetter _ _ ( i ) ) {
target . prototype . _ _defineSetter _ _ (
i ,
origin . prototype . _ _lookupSetter _ _ ( i )
) ;
}
}
}
} ;
//used by some widgets to render a curve editor
function CurveEditor ( points )
{
this . points = points ;
this . selected = - 1 ;
this . nearest = - 1 ;
this . size = null ; //stores last size used
this . must _update = true ;
this . margin = 5 ;
}
CurveEditor . sampleCurve = function ( f , points )
{
if ( ! points )
return ;
for ( var i = 0 ; i < points . length - 1 ; ++ i )
{
var p = points [ i ] ;
var pn = points [ i + 1 ] ;
if ( pn [ 0 ] < f )
continue ;
var r = ( pn [ 0 ] - p [ 0 ] ) ;
if ( Math . abs ( r ) < 0.00001 )
return p [ 1 ] ;
var local _f = ( f - p [ 0 ] ) / r ;
return p [ 1 ] * ( 1.0 - local _f ) + pn [ 1 ] * local _f ;
}
return 0 ;
}
CurveEditor . prototype . draw = function ( ctx , size , graphcanvas , background _color , line _color , inactive )
{
var points = this . points ;
if ( ! points )
return ;
this . size = size ;
var w = size [ 0 ] - this . margin * 2 ;
var h = size [ 1 ] - this . margin * 2 ;
line _color = line _color || "#666" ;
ctx . save ( ) ;
ctx . translate ( this . margin , this . margin ) ;
if ( background _color )
{
ctx . fillStyle = "#111" ;
ctx . fillRect ( 0 , 0 , w , h ) ;
ctx . fillStyle = "#222" ;
ctx . fillRect ( w * 0.5 , 0 , 1 , h ) ;
ctx . strokeStyle = "#333" ;
ctx . strokeRect ( 0 , 0 , w , h ) ;
}
ctx . strokeStyle = line _color ;
if ( inactive )
ctx . globalAlpha = 0.5 ;
ctx . beginPath ( ) ;
for ( var i = 0 ; i < points . length ; ++ i )
{
var p = points [ i ] ;
ctx . lineTo ( p [ 0 ] * w , ( 1.0 - p [ 1 ] ) * h ) ;
}
ctx . stroke ( ) ;
ctx . globalAlpha = 1 ;
if ( ! inactive )
for ( var i = 0 ; i < points . length ; ++ i )
{
var p = points [ i ] ;
ctx . fillStyle = this . selected == i ? "#FFF" : ( this . nearest == i ? "#DDD" : "#AAA" ) ;
ctx . beginPath ( ) ;
ctx . arc ( p [ 0 ] * w , ( 1.0 - p [ 1 ] ) * h , 2 , 0 , Math . PI * 2 ) ;
ctx . fill ( ) ;
}
ctx . restore ( ) ;
}
//localpos is mouse in curve editor space
CurveEditor . prototype . onMouseDown = function ( localpos , graphcanvas )
{
var points = this . points ;
if ( ! points )
return ;
if ( localpos [ 1 ] < 0 )
return ;
//this.captureInput(true);
var w = this . size [ 0 ] - this . margin * 2 ;
var h = this . size [ 1 ] - this . margin * 2 ;
var x = localpos [ 0 ] - this . margin ;
var y = localpos [ 1 ] - this . margin ;
var pos = [ x , y ] ;
var max _dist = 30 / graphcanvas . ds . scale ;
//search closer one
this . selected = this . getCloserPoint ( pos , max _dist ) ;
//create one
if ( this . selected == - 1 )
{
var point = [ x / w , 1 - y / h ] ;
points . push ( point ) ;
points . sort ( function ( a , b ) { return a [ 0 ] - b [ 0 ] ; } ) ;
this . selected = points . indexOf ( point ) ;
this . must _update = true ;
}
if ( this . selected != - 1 )
return true ;
}
CurveEditor . prototype . onMouseMove = function ( localpos , graphcanvas )
{
var points = this . points ;
if ( ! points )
return ;
var s = this . selected ;
if ( s < 0 )
return ;
var x = ( localpos [ 0 ] - this . margin ) / ( this . size [ 0 ] - this . margin * 2 ) ;
var y = ( localpos [ 1 ] - this . margin ) / ( this . size [ 1 ] - this . margin * 2 ) ;
var curvepos = [ ( localpos [ 0 ] - this . margin ) , ( localpos [ 1 ] - this . margin ) ] ;
var max _dist = 30 / graphcanvas . ds . scale ;
this . _nearest = this . getCloserPoint ( curvepos , max _dist ) ;
var point = points [ s ] ;
if ( point )
{
var is _edge _point = s == 0 || s == points . length - 1 ;
if ( ! is _edge _point && ( localpos [ 0 ] < - 10 || localpos [ 0 ] > this . size [ 0 ] + 10 || localpos [ 1 ] < - 10 || localpos [ 1 ] > this . size [ 1 ] + 10 ) )
{
points . splice ( s , 1 ) ;
this . selected = - 1 ;
return ;
}
if ( ! is _edge _point ) //not edges
point [ 0 ] = Math . clamp ( x , 0 , 1 ) ;
else
point [ 0 ] = s == 0 ? 0 : 1 ;
point [ 1 ] = 1.0 - Math . clamp ( y , 0 , 1 ) ;
points . sort ( function ( a , b ) { return a [ 0 ] - b [ 0 ] ; } ) ;
this . selected = points . indexOf ( point ) ;
this . must _update = true ;
}
}
CurveEditor . prototype . onMouseUp = function ( localpos , graphcanvas )
{
this . selected = - 1 ;
return false ;
}
CurveEditor . prototype . getCloserPoint = function ( pos , max _dist )
{
var points = this . points ;
if ( ! points )
return - 1 ;
max _dist = max _dist || 30 ;
var w = ( this . size [ 0 ] - this . margin * 2 ) ;
var h = ( this . size [ 1 ] - this . margin * 2 ) ;
var num = points . length ;
var p2 = [ 0 , 0 ] ;
var min _dist = 1000000 ;
var closest = - 1 ;
var last _valid = - 1 ;
for ( var i = 0 ; i < num ; ++ i )
{
var p = points [ i ] ;
p2 [ 0 ] = p [ 0 ] * w ;
p2 [ 1 ] = ( 1.0 - p [ 1 ] ) * h ;
if ( p2 [ 0 ] < pos [ 0 ] )
last _valid = i ;
var dist = vec2 . distance ( pos , p2 ) ;
if ( dist > min _dist || dist > max _dist )
continue ;
closest = i ;
min _dist = dist ;
}
return closest ;
}
LiteGraph . CurveEditor = CurveEditor ;
//used to create nodes from wrapping functions
LiteGraph . getParameterNames = function ( func ) {
return ( func + "" )
. replace ( /[/][/].*$/gm , "" ) // strip single-line comments
. replace ( /\s+/g , "" ) // strip white space
. replace ( /[/][*][^/*]*[*][/]/g , "" ) // strip multi-line comments /**/
. split ( "){" , 1 ) [ 0 ]
. replace ( /^[^(]*[(]/ , "" ) // extract the parameters
. replace ( /=[^,]+/g , "" ) // strip any ES6 defaults
. split ( "," )
. filter ( Boolean ) ; // split & filter [""]
} ;
/ * h e l p e r f o r i n t e r a c t i o n : p o i n t e r , t o u c h , m o u s e L i s t e n e r s
used by LGraphCanvas DragAndScale ContextMenu * /
LiteGraph . pointerListenerAdd = function ( oDOM , sEvIn , fCall , capture = false ) {
if ( ! oDOM || ! oDOM . addEventListener || ! sEvIn || typeof fCall !== "function" ) {
//console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall);
return ; // -- break --
}
var sMethod = LiteGraph . pointerevents _method ;
var sEvent = sEvIn ;
// UNDER CONSTRUCTION
// convert pointerevents to touch event when not available
if ( sMethod == "pointer" && ! window . PointerEvent ) {
console . warn ( "sMethod=='pointer' && !window.PointerEvent" ) ;
console . log ( "Converting pointer[" + sEvent + "] : down move up cancel enter TO touchstart touchmove touchend, etc .." ) ;
switch ( sEvent ) {
case "down" : {
sMethod = "touch" ;
sEvent = "start" ;
break ;
}
case "move" : {
sMethod = "touch" ;
//sEvent = "move";
break ;
}
case "up" : {
sMethod = "touch" ;
sEvent = "end" ;
break ;
}
case "cancel" : {
sMethod = "touch" ;
//sEvent = "cancel";
break ;
}
case "enter" : {
console . log ( "debug: Should I send a move event?" ) ; // ???
break ;
}
// case "over": case "out": not used at now
default : {
console . warn ( "PointerEvent not available in this browser ? The event " + sEvent + " would not be called" ) ;
}
}
}
switch ( sEvent ) {
//both pointer and move events
case "down" : case "up" : case "move" : case "over" : case "out" : case "enter" :
{
oDOM . addEventListener ( sMethod + sEvent , fCall , capture ) ;
}
// only pointerevents
case "leave" : case "cancel" : case "gotpointercapture" : case "lostpointercapture" :
{
if ( sMethod != "mouse" ) {
return oDOM . addEventListener ( sMethod + sEvent , fCall , capture ) ;
}
}
// not "pointer" || "mouse"
default :
return oDOM . addEventListener ( sEvent , fCall , capture ) ;
}
}
LiteGraph . pointerListenerRemove = function ( oDOM , sEvent , fCall , capture = false ) {
if ( ! oDOM || ! oDOM . removeEventListener || ! sEvent || typeof fCall !== "function" ) {
//console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall);
return ; // -- break --
}
switch ( sEvent ) {
//both pointer and move events
case "down" : case "up" : case "move" : case "over" : case "out" : case "enter" :
{
if ( LiteGraph . pointerevents _method == "pointer" || LiteGraph . pointerevents _method == "mouse" ) {
oDOM . removeEventListener ( LiteGraph . pointerevents _method + sEvent , fCall , capture ) ;
}
}
// only pointerevents
case "leave" : case "cancel" : case "gotpointercapture" : case "lostpointercapture" :
{
if ( LiteGraph . pointerevents _method == "pointer" ) {
return oDOM . removeEventListener ( LiteGraph . pointerevents _method + sEvent , fCall , capture ) ;
}
}
// not "pointer" || "mouse"
default :
return oDOM . removeEventListener ( sEvent , fCall , capture ) ;
}
}
Math . clamp = function ( v , a , b ) {
return a > v ? a : b < v ? b : v ;
} ;
if ( typeof window != "undefined" && ! window [ "requestAnimationFrame" ] ) {
window . requestAnimationFrame =
window . webkitRequestAnimationFrame ||
window . mozRequestAnimationFrame ||
function ( callback ) {
window . setTimeout ( callback , 1000 / 60 ) ;
} ;
}
} ) ( this ) ;
if ( typeof exports != "undefined" ) {
exports . LiteGraph = this . LiteGraph ;
}