Editor/ShaderGraphPlus/ShaderGraphPlusView.cs
using Editor;
using NodeEditorPlus;
using ShaderGraphPlus.Nodes;
namespace ShaderGraphPlus;
public class ShaderGraphPlusView : GraphView
{
private enum DragEventSource
{
None,
NodePallete,
BlackboardParameter,
SubgraphAsset,
ImageFile,
}
private readonly MainWindow _window;
private readonly BlackboardView _blackboard;
private readonly UndoStack _undoStack;
private DragEventSource _currentDragEventSource = DragEventSource.None;
protected override string ClipboardIdent => "shadergraphplus";
protected override string ViewCookie => _window?.AssetPath;
private static bool? _cachedConnectionStyle;
public static bool EnableGridAlignedWires
{
get => _cachedConnectionStyle ??= EditorCookie.Get( "shadergraphplus.gridwires", false );
set => EditorCookie.Set( "shadergraphplus.gridwires", _cachedConnectionStyle = value );
}
private ConnectionStyle _oldConnectionStyle;
public new ShaderGraphPlus Graph
{
get => (ShaderGraphPlus)base.Graph;
set => base.Graph = value;
}
public Action<BaseNodePlus> OnNodeRemoved { get; set; }
private readonly Dictionary<string, INodeType> AvailableNodes = new( StringComparer.OrdinalIgnoreCase );
private readonly Dictionary<string, IBlackboardParameterType> AvailableParameters = new( StringComparer.OrdinalIgnoreCase );
public override ConnectionStyle ConnectionStyle => EnableGridAlignedWires
? GridConnectionStyle.Instance
: ConnectionStyle.Default;
public ShaderGraphPlusView( Widget parent, MainWindow window, BlackboardView blackboard ) : base( parent )
{
_window = window;
_blackboard = blackboard;
_undoStack = window.UndoStack;
OnSelectionChanged += SelectionChanged;
}
protected override INodeType RerouteNodeType { get; } = new ClassNodeType( EditorTypeLibrary.GetType<ReroutePlus>() );
protected override INodeType CommentNodeType { get; } = new ClassNodeType( EditorTypeLibrary.GetType<CommentNode>() );
public void AddNodeType<T>()
where T : BaseNodePlus
{
AddNodeType( EditorTypeLibrary.GetType<T>() );
}
public void AddNodeType( TypeDescription type )
{
var nodeType = new ClassNodeType( type );
AvailableNodes.TryAdd( nodeType.Identifier, nodeType );
}
public void AddNodeType( string subgraphPath )
{
var subgraphTxt = Editor.FileSystem.Content.ReadAllText( subgraphPath );
var subgraph = new ShaderGraphPlus();
subgraph.Deserialize( subgraphTxt );
if ( !subgraph.AddToNodeLibrary ) return;
var nodeType = new SubgraphNodeType( subgraphPath, EditorTypeLibrary.GetType<SubgraphNode>() );
nodeType.SetDisplayInfo( subgraph );
AvailableNodes.TryAdd( nodeType.Identifier, nodeType );
}
public INodeType FindNodeType( Type type )
{
return AvailableNodes.TryGetValue( type.FullName!, out var nodeType ) ? nodeType : null;
}
public IBlackboardParameterType FindParameterType( Type type )
{
return AvailableParameters.TryGetValue( type.FullName!, out var parameterType ) ? parameterType : null;
}
public void AddParameterType<T>() where T : BlackboardParameter
{
AddParameterType( EditorTypeLibrary.GetType<T>() );
}
public void AddParameterType( TypeDescription type )
{
var parameterType = new ClassBlackboardParameterType( type );
// Use these specific ClassBlackboardParameterType's instead. Fallback to the default just in case.
if ( type.TargetType.IsAssignableTo( typeof( IBlackboardMaterialParameter ) ) ||
type.TargetType.IsAssignableTo( typeof( BlackboardTextureMaterialParameter ) ) ||
type.TargetType.IsAssignableTo( typeof( SamplerStateParameter ) )
)
{
parameterType = new MaterialParameterType( type );
}
else if ( type.TargetType.IsAssignableTo( typeof( IBlackboardSubgraphParameter ) ) )
{
parameterType = new SubgraphParameterType( type );
}
if ( type.TargetType.IsAssignableTo( typeof( IBlackboardShaderFeatureParameter ) ) )
{
parameterType = new ShaderFeatureParameterType( type );
}
AvailableParameters.TryAdd( parameterType.Identifier, parameterType );
}
protected override INodeType NodeTypeFromDragEvent( DragEvent ev )
{
if ( ev.Data.Assets.FirstOrDefault() is { } asset )
{
if ( asset.IsInstalled )
{
if ( string.Equals( Path.GetExtension( asset.AssetPath ), ".shdrfunc", StringComparison.OrdinalIgnoreCase ) )
{
_currentDragEventSource = DragEventSource.SubgraphAsset;
return new SubgraphNodeType( asset.AssetPath, EditorTypeLibrary.GetType<SubgraphNode>() );
}
else
{
var realAsset = asset.GetAssetAsync().Result;
if ( realAsset.AssetType == AssetType.ImageFile )
{
_currentDragEventSource = DragEventSource.ImageFile;
return new ParameterNodeType( EditorTypeLibrary.GetType<Texture2DParameterNode>(), asset.AssetPath, () =>
{
_blackboard.RebuildFromGraph( true );
}
);
}
}
}
}
if ( ev.Data.Object is BlackboardParameter blackboardParameter )
{
_currentDragEventSource = DragEventSource.BlackboardParameter;
return new ParameterNodeType( blackboardParameter );
}
_currentDragEventSource = DragEventSource.NodePallete;
return AvailableNodes.TryGetValue( ev.Data.Text, out var type )
? type
: null;
}
protected override IEnumerable<INodeType> GetRelevantNodes( NodeQuery query )
{
return AvailableNodes.Values.Filter( query ).Where( x =>
{
if ( x is ClassNodeType classNodeType )
{
var targetType = classNodeType.Type.TargetType;
if ( classNodeType.Type.HasAttribute<HideAttribute>() ) return false;
if ( classNodeType.Type.HasAttribute<InternalNodeAttribute>() ) return false;
if ( Graph.IsSubgraph && targetType == typeof( Result ) ) return false;
if ( targetType == typeof( SubgraphNode ) && classNodeType.DisplayInfo.Name == targetType.Name.ToTitleCase() ) return false;
}
return true;
} );
}
private static bool TryGetHandleConfig( Type type, out Type matchingType, out NodeHandleConfig config )
{
if ( ShaderGraphPlusTheme.NodeHandleConfigs.TryGetValue( type, out config ) )
{
matchingType = type;
return true;
}
matchingType = null;
return false;
}
protected override NodeHandleConfig OnGetHandleConfig( Type type )
{
if ( TryGetHandleConfig( type, out var matchingType, out var config ) )
{
return config with { Name = type == matchingType ? config.Name : null };
}
return base.OnGetHandleConfig( type );
}
protected override void OnPopulateNodeMenuSpecialOptions( Menu menu, Vector2 clickPos, NodePlug targetPlug, string filter )
{
void NewParameterMenuOption( Menu menu, IBlackboardParameterType classType, string undoScopeName )
{
var option = menu.AddOption( classType.Type.Title, classType.Type.Icon, () =>
{
using var undoScope = UndoScope( undoScopeName );
var parameter = CreateNewParameter( classType );
var node = CreateNewParameterNode( parameter, clickPos );
SelectNode( node );
_window.OnSelected( parameter );
} );
}
base.OnPopulateNodeMenuSpecialOptions( menu, clickPos, targetPlug, filter );
var isSubgraph = Graph.IsSubgraph;
if ( !targetPlug.IsValid() )
{
if ( !isSubgraph )
{
var createParameterMenu = menu.AddMenu( $"Create Parameter", "add" );
var newMaterialParameterMenu = createParameterMenu.AddMenu( $"Material", "edit_attributes" );
var newAttributeMenu = createParameterMenu.AddMenu( $"Attribute", "edit_attributes" );
var newComboMenu = createParameterMenu.AddMenu( $"Combo", "alt_route" );
foreach ( var classType in BlackboardParameter.GetRelevantParameters( AvailableParameters, false ).OrderBy( x =>
x.Type.GetAttribute<OrderAttribute>().Value ) )
{
if ( classType.Type.TargetType.IsAssignableTo( typeof( IBlackboardShaderFeatureParameter ) ) )
{
NewParameterMenuOption( newComboMenu, classType, "Add Material Shader Feature" );
}
if ( classType.Type.TargetType == typeof( SamplerStateParameter ) )
{
NewParameterMenuOption( newAttributeMenu, classType, "Add Attribute Parameter" );
}
else if ( !classType.Type.TargetType.IsAssignableTo( typeof( IBlackboardShaderFeatureParameter ) ) )
{
NewParameterMenuOption( newMaterialParameterMenu, classType, "Add Material Parameter" );
}
}
}
else
{
var newSubgraphInputParameterMenu = menu.AddMenu( $"Create Subgraph Input", "add" );
foreach ( var classType in BlackboardParameter.GetRelevantParameters( AvailableParameters, true ).Where( x =>
x.Type.TargetType.IsAssignableTo( typeof( IBlackboardSubgraphInputParameter ) ) ).OrderBy( x =>
x.Type.GetAttribute<OrderAttribute>().Value ) )
{
NewParameterMenuOption( newSubgraphInputParameterMenu, classType, "Add Subgraph Input Parameter" );
}
var newSubgraphOutputParameterMenu = menu.AddMenu( $"Create Subgraph Output", "add" );
foreach ( var classType in BlackboardParameter.GetRelevantParameters( AvailableParameters, true ).Where( x =>
x.Type.TargetType.IsAssignableTo( typeof( IBlackboardSubgraphOutputParameter ) ) ).OrderBy( x =>
x.Type.GetAttribute<OrderAttribute>().Value ) )
{
NewParameterMenuOption( newSubgraphOutputParameterMenu, classType, "Add Subgraph Output Parameter" );
}
}
//menu.AddOption( "Add Named Reroute Declaration", "route", () =>
//{
// var nodeType = new NamedRerouteDeclarationNodeType( EditorTypeLibrary.GetType<NamedRerouteDeclarationNode>() );
//
// CreateNewNode( nodeType, clickPos, targetPlug );
//} );
var namedRerouteDeclarations = Graph.Nodes.OfType<NamedRerouteDeclarationNode>();
if ( namedRerouteDeclarations.Any() )
{
var optionsMenu = menu.AddMenu( "Named Reroutes", "route" );
foreach ( var namedReroute in namedRerouteDeclarations )
{
optionsMenu.AddOption( namedReroute.Name, "route", () =>
{
CreateNewNamedReroute( namedReroute.Name, clickPos );
} );
}
}
}
else if ( targetPlug is PlugIn )
{
var namedRerouteDeclarations = Graph.Nodes.OfType<NamedRerouteDeclarationNode>();
if ( namedRerouteDeclarations.Any() )
{
var optionsMenu = menu.AddMenu( "Named Reroutes", "route" );
foreach ( var namedRerouteDeclaration in namedRerouteDeclarations )
{
optionsMenu.AddOption( namedRerouteDeclaration.Name, "route", () =>
{
var nodeType = new NamedRerouteNodeType( EditorTypeLibrary.GetType<NamedRerouteNode>(), namedRerouteDeclaration.Name );
CreateNewNode( nodeType, clickPos, targetPlug );
} );
}
}
}
else if ( targetPlug is PlugOut )
{
menu.AddOption( "Add Named Reroute Declaration", "route", () =>
{
Dialog.AskString( ( string namedRerouteName ) =>
{
CreateNewNamedRerouteDeclaration( namedRerouteName, clickPos, targetPlug );
},
"Specify a Named Reroute name" );
} );
}
menu.AddSeparator();
}
protected override void OnDoubleClickNodeSpecial( NodeUI node )
{
base.OnDoubleClickNodeSpecial( node );
if ( node.Node is NamedRerouteNode namedRerouteNode )
{
var namedRerouteDeclaration = Graph.FindNamedRerouteDeclarationNode( namedRerouteNode.Name );
if ( namedRerouteDeclaration != null )
{
CenterOn( namedRerouteDeclaration.Position );
//SelectNode( namedRerouteDeclaration );
//_window.SetPropertiesTarget( namedRerouteDeclaration );
}
}
}
private void CreateNewNamedReroute( string name, Vector2 position )
{
using var undoScope = UndoScope( "Add Named Reroute" );
var nodeType = new NamedRerouteNodeType( EditorTypeLibrary.GetType<NamedRerouteNode>(), name );
CreateNewNode( nodeType, position );
}
private void CreateNewNamedRerouteDeclaration( string name, Vector2 position, NodePlug targetPlug )
{
var nodeType = new NamedRerouteDeclarationNodeType( EditorTypeLibrary.GetType<NamedRerouteDeclarationNode>(), name );
CreateNewNode( nodeType, position, targetPlug );
}
public override void ChildValuesChanged( Widget source )
{
BindSystem.Flush();
base.ChildValuesChanged( source );
BindSystem.Flush();
}
public override void PushUndo( string name )
{
SGPLogger.Info( $"Push Undo ({name})" );
_undoStack.PushUndo( name, Graph.UndoStackSerialize() );
_window.OnUndoPushed();
}
public override void PushRedo()
{
SGPLogger.Info( "Push Redo" );
_undoStack.PushRedo( Graph.UndoStackSerialize() );
_window.SetDirty();
}
protected override void OnOpenContextMenu( Menu menu, NodePlug targetPlug )
{
BaseNodePlus ConvertConstantNodeToParameter( IConstantNode constantNode )
{
var baseNode = constantNode as BaseNodePlus;
var nodePosition = baseNode.Position;
Dictionary<IPlugIn, IPlugOut> oldOutputConnections = new();
if ( !Graph.IsSubgraph )
{
oldOutputConnections = GatherConnectedOutputs( baseNode );
}
Graph.RemoveNode( baseNode );
var parameterFullTypeName = "";
if ( !Graph.IsSubgraph )
{
parameterFullTypeName = constantNode switch
{
BoolConstantNode => DisplayInfo.ForType( typeof( BoolParameter ) ).Fullname,
IntConstantNode => DisplayInfo.ForType( typeof( IntParameter ) ).Fullname,
FloatConstantNode => DisplayInfo.ForType( typeof( FloatParameter ) ).Fullname,
Float2ConstantNode => DisplayInfo.ForType( typeof( Float2Parameter ) ).Fullname,
Float3ConstantNode => DisplayInfo.ForType( typeof( Float3Parameter ) ).Fullname,
Float4ConstantNode => DisplayInfo.ForType( typeof( Float4Parameter ) ).Fullname,
ColorConstantNode => DisplayInfo.ForType( typeof( ColorParameter ) ).Fullname,
_ => throw new NotImplementedException( $"Unknown type : {constantNode.GetType()}" ),
};
}
else
{
parameterFullTypeName = constantNode switch
{
BoolConstantNode => DisplayInfo.ForType( typeof( BoolSubgraphInputParameter ) ).Fullname,
IntConstantNode => DisplayInfo.ForType( typeof( IntSubgraphInputParameter ) ).Fullname,
FloatConstantNode => DisplayInfo.ForType( typeof( FloatSubgraphInputParameter ) ).Fullname,
Float2ConstantNode => DisplayInfo.ForType( typeof( Float2SubgraphInputParameter ) ).Fullname,
Float3ConstantNode => DisplayInfo.ForType( typeof( Float3SubgraphInputParameter ) ).Fullname,
Float4ConstantNode => DisplayInfo.ForType( typeof( Float4SubgraphInputParameter ) ).Fullname,
ColorConstantNode => DisplayInfo.ForType( typeof( ColorSubgraphInputParameter ) ).Fullname,
_ => throw new NotImplementedException( $"Unknown type : {constantNode.GetType()}" ),
};
}
if ( AvailableParameters.TryGetValue( parameterFullTypeName, out var parameterType ) )
{
var parameter = CreateNewParameter( parameterType );
parameter.SetValue( constantNode.GetValue() );
var parameterNode = CreateNewParameterNode( parameter, nodePosition );
if ( !Graph.IsSubgraph && oldOutputConnections.Any() )
{
// fixup any valid output connections
foreach ( var node in Graph.Nodes )
{
foreach ( var input in node.Inputs )
{
if ( input.ConnectedOutput is null && oldOutputConnections.TryGetValue( input, out var correspondingOutput ) )
{
node.ConnectNode( input.Identifier, correspondingOutput.Identifier, parameterNode.Identifier );
continue;
}
}
}
}
if ( parameterNode != null )
{
return parameterNode;
}
}
return baseNode;
}
base.OnOpenContextMenu( menu, targetPlug );
var selectedNodes = SelectedItems.OfType<NodeUI>().ToArray();
// TODO : FIX CreateSubgraphFromSelection();
//if ( selectedNodes.Length > 1 && !selectedNodes.Any( x => x.Node is BaseResult ) )
//{
// menu.AddOption( "Create Custom Node...", "add_box", () =>
// {
// var fd = new FileDialog( null );
// fd.Title = "Create Shader Graph Function";
// fd.Directory = Project.Current.RootDirectory.FullName;
// fd.DefaultSuffix = $".{ShaderGraphPlusGlobals.SubgraphAssetTypeExtension}";
// fd.SelectFile( $"untitled.{ShaderGraphPlusGlobals.SubgraphAssetTypeExtension}" );
// fd.SetFindFile();
// fd.SetModeSave();
// fd.SetNameFilter( $"ShaderGraph Function (*.{ShaderGraphPlusGlobals.SubgraphAssetTypeExtension})" );
// if ( !fd.Execute() ) return;
//
// CreateSubgraphFromSelection( fd.SelectedFile );
// } );
//}
if ( selectedNodes.Length > 1 && selectedNodes.All( x => x.Node is IConstantNode && x.Node is not IConstantMatrixNode ) )
{
var optionName = $"Convert {selectedNodes.Length} Constants to {(Graph.IsSubgraph ? "Subgraph Inputs" : "Material Parameters")}";
var convertOption = menu.AddOption( optionName, "swap_horiz", () =>
{
using var undoScope = UndoScope( optionName );
var lastNode = selectedNodes.First().Node as BaseNodePlus;
foreach ( var selectedNode in selectedNodes )
{
lastNode = ConvertConstantNodeToParameter( selectedNode.Node as IConstantNode );
}
RebuildFromGraph();
// Select the last node in the list.
_window.OnSelected( lastNode );
SelectNode( lastNode );
} );
}
if ( selectedNodes.Length == 1 )
{
var item = selectedNodes.FirstOrDefault();
if ( item is null )
return;
if ( item.Node is BaseNodePlus baseNode && baseNode is IConstantNode constantNode )
{
var optionName = $"Convert Constant to {(Graph.IsSubgraph ? "Subgraph Input" : "Material Parameter")}";
var convertOption = menu.AddOption( optionName, "swap_horiz", () =>
{
using var undoScope = UndoScope( optionName );
var parameterNode = ConvertConstantNodeToParameter( constantNode );
RebuildFromGraph();
_window.OnSelected( parameterNode );
SelectNode( parameterNode );
} );
}
}
}
private Dictionary<IPlugIn, IPlugOut> GatherConnectedOutputs( BaseNodePlus targetNode )
{
var oldConnections = new Dictionary<IPlugIn, IPlugOut>();
foreach ( var node in Graph.Nodes )
{
foreach ( var input in node.Inputs )
{
if ( input.ConnectedOutput is null )
continue;
if ( input.ConnectedOutput.Node == targetNode )
{
oldConnections[input] = input.ConnectedOutput;
continue;
}
}
}
return oldConnections;
}
private IBlackboardParameter CreateNewParameter( IBlackboardParameterType type, string name = "" )
{
return _blackboard.CreateNewParameter( type, name );
}
private T CreatenNewParameter<T>( ShaderGraphPlus graph ) where T : IBlackboardParameter
{
return (T)_blackboard.CreateNewParameter( FindParameterType( typeof( T ) ) );
}
private BaseNodePlus CreateNewParameterNode( IBlackboardParameter parameter, Vector2 position )
{
var node = parameter.ToNode() as BaseNodePlus;
node.Graph = Graph;
node.Position = position.SnapToGrid( GridSize );
Graph?.AddNode( node );
OnNodeCreated( node );
var nodeUI = node.CreateUI( this );
Add( nodeUI );
_blackboard.RebuildFromGraph( true );
return node;
}
/// <summary>
/// TODO : FIXME!!!
/// </summary>
private void CreateSubgraphFromSelection( string filePath )
{
if ( string.IsNullOrWhiteSpace( filePath ) ) return;
var fileName = Path.GetFileNameWithoutExtension( filePath );
var subgraph = new ShaderGraphPlus();
subgraph.Title = fileName.ToTitleCase();
subgraph.IsSubgraph = true;
// Grab all selected nodes
Vector2 rightmostPos = new Vector2( -9999, 0 );
var selectedNodes = SelectedItems.OfType<NodeUI>();
var selectedParameters = new List<Guid>();
Dictionary<IPlugIn, IPlugOut> oldConnections = new();
foreach ( var node in selectedNodes )
{
if ( node.Node is not BaseNodePlus baseNode ) continue;
foreach ( var input in baseNode.Inputs )
{
oldConnections[input] = input.ConnectedOutput;
}
subgraph.AddNode( baseNode );
rightmostPos.y += baseNode.Position.y;
if ( baseNode.Position.x > rightmostPos.x )
{
rightmostPos = rightmostPos.WithX( baseNode.Position.x );
}
}
rightmostPos.y /= selectedNodes.Count();
// Create Inputs/Constants
var nodesToAdd = new List<BaseNodePlus>();
var previousOutputs = new Dictionary<string, IPlugOut>();
foreach ( var node in subgraph.Nodes )
{
foreach ( var input in node.Inputs )
{
var correspondingOutput = oldConnections[input];
var correspondingNode = subgraph.Nodes.FirstOrDefault( x => x.Identifier == correspondingOutput?.Node?.Identifier );
if ( correspondingOutput is not null && correspondingNode is null )
{
var inputName = $"{input.Identifier}_{correspondingOutput?.Node?.Identifier}";
var existingParameterNode = nodesToAdd.OfType<IParameterNode>().FirstOrDefault( x => x.Name == inputName );
if ( input.ConnectedOutput is not null )
{
previousOutputs[inputName] = input.ConnectedOutput;
}
if ( existingParameterNode is not null )
{
input.ConnectedOutput = (existingParameterNode as BaseNodePlus).Outputs.FirstOrDefault();
continue;
}
BlackboardParameter parameter = null;
if ( input.Type == typeof( bool ) )
{
parameter = CreatenNewParameter<BoolSubgraphInputParameter>( subgraph );
}
if ( input.Type == typeof( int ) )
{
parameter = CreatenNewParameter<IntSubgraphInputParameter>( subgraph );
}
if ( input.Type == typeof( float ) )
{
Log.Info( $"input.Type == typeof( float )" );
parameter = CreatenNewParameter<FloatSubgraphInputParameter>( subgraph );
}
else if ( input.Type == typeof( Vector2 ) )
{
parameter = CreatenNewParameter<Float2SubgraphInputParameter>( subgraph );
}
else if ( input.Type == typeof( Vector3 ) )
{
parameter = CreatenNewParameter<Float3SubgraphInputParameter>( subgraph );
}
else if ( input.Type == typeof( Vector4 ) )
{
parameter = CreatenNewParameter<Float4SubgraphInputParameter>( subgraph );
}
else if ( input.Type == typeof( Color ) )
{
parameter = CreatenNewParameter<ColorSubgraphInputParameter>( subgraph );
}
if ( parameter != null )
{
if ( parameter is IBlackboardSubgraphInputParameter subgraphParameter )
{
subgraphParameter.PortOrder = nodesToAdd.Count;
}
subgraph.AddParameter( parameter );
var subgraphInput = FindNodeType( typeof( SubgraphInput ) ).CreateNode( subgraph );
subgraphInput.Position = node.Position - new Vector2( 240, 0 );
if ( subgraphInput is SubgraphInput subgraphInputNode )
{
subgraphInputNode.ParameterIdentifier = parameter.Identifier;
subgraphInputNode.OnFrame(); // Trigger update to create outputs
input.ConnectedOutput = subgraphInputNode.Outputs.FirstOrDefault();
nodesToAdd.Add( subgraphInputNode );
}
}
else
{
var defaultparameter = CreatenNewParameter<FloatSubgraphInputParameter>( subgraph );
defaultparameter.Name = inputName;
defaultparameter.PortOrder = nodesToAdd.Count;
subgraph.AddParameter( defaultparameter );
// Default to float for unknown types
var subgraphInput = FindNodeType( typeof( SubgraphInput ) ).CreateNode( subgraph );
subgraphInput.Position = node.Position - new Vector2( 240, 0 );
if ( subgraphInput is SubgraphInput subgraphInputNode )
{
subgraphInputNode.ParameterIdentifier = defaultparameter.Identifier;
subgraphInputNode.OnFrame(); // Trigger update to create outputs
input.ConnectedOutput = subgraphInputNode.Outputs.FirstOrDefault();
nodesToAdd.Add( subgraphInputNode );
}
}
}
}
}
// Create Output/Result node
//var frNode = FindNodeType( typeof( FunctionResult ) ).CreateNode( subgraph );
//if ( frNode is FunctionResult resultNode )
//{
// resultNode.Position = rightmostPos + new Vector2( 240, 0 );
// resultNode.FunctionOutputs = new();
// foreach ( var node in subgraph.Nodes )
// {
// foreach ( var output in node.Outputs )
// {
// var correspondingNode = Graph.Nodes.FirstOrDefault( x => !subgraph.Nodes.Contains( x ) && x.Inputs.Any( x => x.ConnectedOutput == output ) );
// if ( correspondingNode is null ) continue;
// var inputName = $"{output.Identifier}_{output.Node.Identifier}";
// resultNode.FunctionOutputs.Add( new FunctionOutput
// {
// Name = inputName,
// TypeName = output.Type.FullName
// } );
// resultNode.CreateInputs();
//
// var input = resultNode.Inputs.FirstOrDefault( x => x is BasePlugIn plugIn && plugIn.Info.Name == inputName );
// input.ConnectedOutput = output;
// break;
// }
// }
// nodesToAdd.Add( resultNode );
//}
//
//// Add all the newly created nodes
//foreach ( var node in nodesToAdd )
//{
// subgraph.AddNode( node );
//}
//
//// Save the newly created sub-graph
//System.IO.File.WriteAllText( filePath, subgraph.Serialize() );
//var asset = AssetSystem.RegisterFile( filePath );
//MainAssetBrowser.Instance?.Local.UpdateAssetList();
//
//PushUndo( "Create Subgraph from Selection" );
//
//// Create the new subgraph node centered on the selected nodes
//Vector2 centerPos = Vector2.Zero;
//foreach ( var node in selectedNodes )
//{
// centerPos += node.Position;
//}
//centerPos /= selectedNodes.Count();
//var subgraphNode = CreateNewNode( new SubgraphNodeType( asset.RelativePath, EditorTypeLibrary.GetType<SubgraphNode>() ) ).Node as SubgraphNode;
//subgraphNode.Position = centerPos;
//
//// Get all the collected inputs/outputs and connect them to the new subgraph node
//foreach ( var node in Graph.Nodes )
//{
// if ( node == subgraphNode ) continue;
//
// if ( selectedNodes.Any( x => x.Node == node ) )
// {
// foreach ( var input in node.Inputs )
// {
// var correspondingOutput = oldConnections[input];
// if ( correspondingOutput is not null && !selectedNodes.Any( x => x.Node == correspondingOutput.Node ) )
// {
// var inputName = $"{input.Identifier}_{correspondingOutput.Node.Identifier}";
// var newInput = subgraphNode.Inputs.FirstOrDefault( x => x.Identifier == inputName );
// if ( previousOutputs.TryGetValue( inputName, out var previousOutput ) )
// {
// newInput.ConnectedOutput = previousOutput;
// }
// }
// }
// }
// else
// {
// foreach ( var input in node.Inputs )
// {
// var correspondingOutput = input.ConnectedOutput;
// if ( correspondingOutput is not null && selectedNodes.Any( x => x.Node == correspondingOutput.Node ) )
// {
// var inputName = $"{correspondingOutput.Identifier}_{correspondingOutput.Node.Identifier}";
// var newOutput = subgraphNode.Outputs.FirstOrDefault( x => x.Identifier == inputName );
// if ( newOutput is not null )
// {
// input.ConnectedOutput = newOutput;
// }
// }
// }
// }
//}
//
//PushRedo();
//DeleteSelection();
//
// Delete all previously selected nodes
//UpdateConnections( Graph.Nodes );
}
private void SelectionChanged()
{
var item = SelectedItems
.OfType<NodeUI>()
.OrderByDescending( n => n is CommentUI )
.FirstOrDefault();
if ( !item.IsValid() )
{
_window.OnSelected( null );
return;
}
_window.OnSelected( (BaseNodePlus)item.Node );
}
protected override void OnMouseClick( MouseEvent e )
{
base.OnMouseClick( e );
var item = SelectedItems
.OfType<NodeUI>()
.OrderByDescending( n => n is CommentUI )
.FirstOrDefault();
if ( !item.IsValid() )
{
_window.OnGraphViewClicked();
}
}
protected override void OnNodeCreated( IGraphNode node )
{
if ( node is SubgraphNode subgraphNode )
{
subgraphNode.OnNodeCreated();
}
}
protected override void OnNodePreviewPreRemove( NodeUI nodePreview )
{
var node = nodePreview.Node as BaseNodePlus;
if ( node is IParameterNode parameterNode )
{
if ( _currentDragEventSource == DragEventSource.ImageFile )
{
Graph.RemoveParameter( parameterNode.ParameterIdentifier );
_blackboard.RebuildFromGraph( true );
}
}
_currentDragEventSource = DragEventSource.None;
}
protected override void OnDragDropFinish()
{
_currentDragEventSource = DragEventSource.None;
}
[EditorEvent.Frame]
public void Frame()
{
foreach ( var node in Items )
{
if ( node is NodeUI nodeUI && nodeUI.Node is BaseNodePlus baseNode )
{
baseNode.OnFrame();
}
}
if ( _oldConnectionStyle != ConnectionStyle )
{
_oldConnectionStyle = ConnectionStyle;
foreach ( var connection in Items.OfType<NodeEditorPlus.Connection>() )
{
connection.Layout();
}
}
}
}