From a511e4ad41953e4b1fdb2fb9518079e21bcf17e0 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:20:54 +0200 Subject: [PATCH 01/31] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e9e3556..8d150b55 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NodeGraphProcessor +# NodeGraphProcessor Fork Node graph editor framework focused on data processing using Unity UIElements, GraphView and C# 4.7 [![Discord](https://img.shields.io/discord/823720615965622323.svg)](https://discord.gg/XuMd3Z5Rym) From d94beb1dae8305913ac785c82cde43244294840c Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Tue, 12 Oct 2021 11:48:12 +0200 Subject: [PATCH 02/31] fixed typo in function name --- Assets/Examples/Editor/GraphAssetCallbacks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Examples/Editor/GraphAssetCallbacks.cs b/Assets/Examples/Editor/GraphAssetCallbacks.cs index dd61c37a..c44904a9 100644 --- a/Assets/Examples/Editor/GraphAssetCallbacks.cs +++ b/Assets/Examples/Editor/GraphAssetCallbacks.cs @@ -9,7 +9,7 @@ public class GraphAssetCallbacks { [MenuItem("Assets/Create/GraphProcessor", false, 10)] - public static void CreateGraphPorcessor() + public static void CreateGraphProcessor() { var graph = ScriptableObject.CreateInstance< BaseGraph >(); ProjectWindowUtil.CreateAsset(graph, "GraphProcessor.asset"); From 43e50877b8f61200116a9c489bd8db104193aac1 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:02:53 +0100 Subject: [PATCH 03/31] Implement conversion nodes --- .../Editor/Logic/EdgeConnectorListener.cs | 2 +- .../Editor/Utils/NodeProvider.cs | 13 ++++- .../Editor/Views/BaseGraphView.cs | 54 ++++++++++++++++++- .../Runtime/Graph/BaseGraph.cs | 21 ++++---- .../Runtime/Processing/TypeAdapter.cs | 23 +++++--- 5 files changed, 92 insertions(+), 21 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Logic/EdgeConnectorListener.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Logic/EdgeConnectorListener.cs index cef006d2..180df519 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Logic/EdgeConnectorListener.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Logic/EdgeConnectorListener.cs @@ -64,7 +64,7 @@ public virtual void OnDrop(GraphView graphView, Edge edge) try { this.graphView.RegisterCompleteObjectUndo("Connected " + edgeView.input.node.name + " and " + edgeView.output.node.name); - if (!this.graphView.Connect(edge as EdgeView, autoDisconnectInputs: !wasOnTheSamePort)) + if (!this.graphView.ConnectConvertable(edge as EdgeView, !wasOnTheSamePort)) this.graphView.Disconnect(edge as EdgeView); } catch (System.Exception) { diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs index da054683..9b525d12 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Utils/NodeProvider.cs @@ -337,9 +337,18 @@ bool IsPortCompatible(PortDescription description) { if ((portView.direction == Direction.Input && description.isInput) || (portView.direction == Direction.Output && !description.isInput)) return false; + + if (portView.direction == Direction.Input) + { + if (!BaseGraph.TypesAreConnectable(description.portType, portView.portType)) + return false; + } + else + { + if (!BaseGraph.TypesAreConnectable( portView.portType, description.portType)) + return false; + } - if (!BaseGraph.TypesAreConnectable(description.portType, portView.portType)) - return false; return true; } diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs index 7940f6ee..7df76fb3 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs @@ -1192,8 +1192,60 @@ public bool Connect(PortView inputPortView, PortView outputPortView, bool autoDi edgeView.input = inputPortView; edgeView.output = outputPortView; + if (ConversionNodeAdapter.AreAssignable(outputPort.portData.displayType, inputPort.portData.displayType)) + { + return ConnectConvertable(edgeView, autoDisconnectInputs); + } else + { + return Connect(edgeView); + } + } - return Connect(edgeView); + /// + /// Same as connect, but also adds custom conversion nodes inbetween the edges input/output, if neccessary + /// + /// + /// + /// + public bool ConnectConvertable(EdgeView e, bool autoDisconnectInputs = true) + { + if (!CanConnectEdge(e, autoDisconnectInputs)) + return false; + + var inputPortView = e.input as PortView; + var outputPortView = e.output as PortView; + var inputNodeView = inputPortView.node as BaseNodeView; + var outputNodeView = outputPortView.node as BaseNodeView; + var inputPort = inputNodeView.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier); + var outputPort = outputNodeView.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier); + + Type conversionNodeType = ConversionNodeAdapter.GetConversionNode(outputPort.portData.displayType, inputPort.portData.displayType); + if (conversionNodeType != null) + { + var nodePosition = (inputPort.owner.position.center + outputPort.owner.position.center) / 2.0f; + BaseNode converterNode = BaseNode.CreateFromType(conversionNodeType, nodePosition); + IConversionNode conversion = (IConversionNode)converterNode; + var converterView = AddNode(converterNode); + + // set nodes center position to be in the middle of the input/output ports + converterNode.position.center = nodePosition - new Vector2(converterNode.position.width / 2.0f,0); + converterView.SetPosition(converterNode.position); + + + var conversionInputName = conversion.GetConversionInput(); + var converterInput = converterView.inputPortViews.Find(view => view.fieldName == conversionInputName); + var conversionOutputName = conversion.GetConversionOutput(); + var converterOutput = converterView.outputPortViews.Find(view => view.fieldName == conversionOutputName); + + Connect(inputPortView, converterOutput, autoDisconnectInputs); + + e.input = converterInput; // change from original input to use the converter node + return Connect(e, autoDisconnectInputs); + } + else + { + return Connect(e, autoDisconnectInputs); + } } public bool Connect(EdgeView e, bool autoDisconnectInputs = true) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/BaseGraph.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/BaseGraph.cs index f7da2f09..46f660f6 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/BaseGraph.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/BaseGraph.cs @@ -823,27 +823,30 @@ void DestroyBrokenGraphElements() /// /// Tell if two types can be connected in the context of a graph /// - /// - /// + /// + /// /// - public static bool TypesAreConnectable(Type t1, Type t2) + public static bool TypesAreConnectable(Type from, Type to) // NOTE: Extend this later for adding conversion nodes { - if (t1 == null || t2 == null) + if (from == null || to == null) return false; - if (TypeAdapter.AreIncompatible(t1, t2)) + if (TypeAdapter.AreIncompatible(from, to)) return false; //Check if there is custom adapters for this assignation - if (CustomPortIO.IsAssignable(t1, t2)) + if (CustomPortIO.IsAssignable(from, to)) return true; //Check for type assignability - if (t2.IsReallyAssignableFrom(t1)) + if (to.IsReallyAssignableFrom(from)) return true; - // User defined type convertions - if (TypeAdapter.AreAssignable(t1, t2)) + // User defined type conversions + if (TypeAdapter.AreAssignable(from, to)) + return true; + + if (ConversionNodeAdapter.AreAssignable(from, to)) return true; return false; diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/TypeAdapter.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/TypeAdapter.cs index 33592e6e..7108adce 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/TypeAdapter.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/TypeAdapter.cs @@ -22,13 +22,18 @@ public abstract class ITypeAdapter // TODO: turn this back into an interface whe public virtual IEnumerable<(Type, Type)> GetIncompatibleTypes() { yield break; } } + public class ValueTypeConversion : ITypeAdapter + { + public static float ConvertIntToFloat(int from) => from; + } + public static class TypeAdapter { static Dictionary< (Type from, Type to), Func > adapters = new Dictionary< (Type, Type), Func >(); static Dictionary< (Type from, Type to), MethodInfo > adapterMethods = new Dictionary< (Type, Type), MethodInfo >(); static List< (Type from, Type to)> incompatibleTypes = new List<( Type from, Type to) >(); - [System.NonSerialized] + [NonSerialized] static bool adaptersLoaded = false; #if !ENABLE_IL2CPP @@ -67,12 +72,12 @@ static void LoadAllAdapters() { if (method.GetParameters().Length != 1) { - Debug.LogError($"Ignoring convertion method {method} because it does not have exactly one parameter"); + Debug.LogError($"Ignoring conversion method {method} because it does not have exactly one parameter"); continue; } if (method.ReturnType == typeof(void)) { - Debug.LogError($"Ignoring convertion method {method} because it does not returns anything"); + Debug.LogError($"Ignoring conversion method {method} because it does not returns anything"); continue; } Type from = method.GetParameters()[0].ParameterType; @@ -81,7 +86,7 @@ static void LoadAllAdapters() try { #if ENABLE_IL2CPP - // IL2CPP doesn't suport calling generic functions via reflection (AOT can't generate templated code) + // IL2CPP doesn't support calling generic functions via reflection (AOT can't generate templated code) Func r = (object param) => { return (object)method.Invoke(null, new object[]{ param }); }; #else MethodInfo genericHelper = typeof(TypeAdapter).GetMethod("ConvertTypeMethodHelper", @@ -97,19 +102,21 @@ static void LoadAllAdapters() adapters.Add((method.GetParameters()[0].ParameterType, method.ReturnType), r); adapterMethods.Add((method.GetParameters()[0].ParameterType, method.ReturnType), method); } catch (Exception e) { - Debug.LogError($"Failed to load the type convertion method: {method}\n{e}"); + Debug.LogError($"Failed to load the type conversion method: {method}\n{e}"); } } } } - // Ensure that the dictionary contains all the convertions in both ways + /* + // Ensure that the dictionary contains all the conversions in both ways // ex: float to vector but no vector to float foreach (var kp in adapters) { if (!adapters.ContainsKey((kp.Key.to, kp.Key.from))) - Debug.LogError($"Missing convertion method. There is one for {kp.Key.from} to {kp.Key.to} but not for {kp.Key.to} to {kp.Key.from}"); + Debug.LogError($"Missing conversion method. There is one for {kp.Key.from} to {kp.Key.to} but not for {kp.Key.to} to {kp.Key.from}"); } + */ adaptersLoaded = true; } @@ -132,7 +139,7 @@ public static bool AreAssignable(Type from, Type to) return adapters.ContainsKey((from, to)); } - public static MethodInfo GetConvertionMethod(Type from, Type to) => adapterMethods[(from, to)]; + public static MethodInfo GetConversionMethod(Type from, Type to) => adapterMethods[(from, to)]; public static object Convert(object from, Type targetType) { From ad613d98dc2b7d3311c49d0183ff796a91afa945 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:03:28 +0100 Subject: [PATCH 04/31] Fix typos and ordering of TypesAreConnectable call --- .../Runtime/Elements/NodePort.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs index a5cfde55..dba87e96 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs @@ -194,7 +194,7 @@ PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge) // We keep slow checks inside the editor #if UNITY_EDITOR - if (!BaseGraph.TypesAreConnectable(inputField.FieldType, outputField.FieldType)) + if (!BaseGraph.TypesAreConnectable(outputField.FieldType, inputField.FieldType)) { Debug.LogError("Can't convert from " + inputField.FieldType + " to " + outputField.FieldType + ", you must specify a custom port function (i.e CustomPortInput or CustomPortOutput) for non-implicit convertions"); return null; @@ -207,14 +207,14 @@ PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge) inType = edge.inputPort.portData.displayType ?? inputField.FieldType; outType = edge.outputPort.portData.displayType ?? outputField.FieldType; - // If there is a user defined convertion function, then we call it + // If there is a user defined conversion function, then we call it if (TypeAdapter.AreAssignable(outType, inType)) { // We add a cast in case there we're calling the conversion method with a base class parameter (like object) var convertedParam = Expression.Convert(outputParamField, outType); - outputParamField = Expression.Call(TypeAdapter.GetConvertionMethod(outType, inType), convertedParam); + outputParamField = Expression.Call(TypeAdapter.GetConversionMethod(outType, inType), convertedParam); // In case there is a custom port behavior in the output, then we need to re-cast to the base type because - // the convertion method return type is not always assignable directly: + // the conversion method return type is not always assignable directly: outputParamField = Expression.Convert(outputParamField, inputField.FieldType); } else // otherwise we cast From 0db21a39ab5a1ca868da8f89e99bc7a7976ca7de Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:03:54 +0100 Subject: [PATCH 05/31] Cleared up error message for wrong usage of custom io port functions --- .../Runtime/Processing/CustomPortIO.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/CustomPortIO.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/CustomPortIO.cs index ba5db93b..4db839f2 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/CustomPortIO.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/CustomPortIO.cs @@ -79,15 +79,15 @@ static void LoadCustomPortMethods() deleg = Expression.Lambda< CustomPortIODelegate >(ex, p1, p2, p3).Compile(); #endif - if (deleg == null) + string fieldName = (portInputAttr == null) ? portOutputAttr.fieldName : portInputAttr.fieldName; + Type customType = (portInputAttr == null) ? portOutputAttr.outputType : portInputAttr.inputType; + var field = type.GetField(fieldName, bindingFlags); + if (field == null) { - Debug.LogWarning("Can't use custom IO port function " + method + ": The method have to respect this format: " + typeof(CustomPortIODelegate)); + Debug.LogWarning("Can't use custom IO port function '" + method.Name + "' of class '" + type.Name + "': No field named " + fieldName + " found"); continue ; } - - string fieldName = (portInputAttr == null) ? portOutputAttr.fieldName : portInputAttr.fieldName; - Type customType = (portInputAttr == null) ? portOutputAttr.outputType : portInputAttr.inputType; - Type fieldType = type.GetField(fieldName, bindingFlags).FieldType; + Type fieldType = field.FieldType; AddCustomIOMethod(type, fieldName, deleg); From ed98f489922d3e3cf6272281ac22b10f1ed04bff Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:04:31 +0100 Subject: [PATCH 06/31] Add support for user created property fields that are not in a valid state --- .../Editor/Views/BaseNodeView.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 5f3dfdad..b32dfebd 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -835,6 +835,9 @@ internal void SyncSerializedPropertyPathes() var nodeIndexString = nodeIndex.ToString(); foreach (var propertyField in this.Query().ToList()) { + if(!propertyField.enabledSelf || propertyField.bindingPath == null) + continue; + propertyField.Unbind(); // The property path look like this: nodes.Array.data[x].fieldName // And we want to update the value of x with the new node index: From a525dd21ec53db5ded0474f274fc26abba3ddb0a Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:05:28 +0100 Subject: [PATCH 07/31] Add support for creating no ports from custom port behaviours --- .../Runtime/Elements/BaseNode.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs index 0338c14b..843dabde 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs @@ -376,7 +376,10 @@ public bool UpdatePortsForFieldLocal(string fieldName, bool sendPortUpdatedEvent if (fieldInfo.behavior != null) { foreach (var portData in fieldInfo.behavior(edges)) - AddPortData(portData); + { + if (portData != null) + AddPortData(portData); + } } else { From ae7e2cae6d398678bd700768aade6490aec3148d Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:06:00 +0100 Subject: [PATCH 08/31] Small performance optimization --- .../Editor/Views/BaseGraphView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs index 7df76fb3..7d1b968d 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs @@ -430,7 +430,7 @@ public override List< Port > GetCompatiblePorts(Port startPort, NodeAdapter node { var compatiblePorts = new List< Port >(); - compatiblePorts.AddRange(ports.ToList().Where(p => { + compatiblePorts.AddRange(ports.Where(p => { var portView = p as PortView; if (portView.owner == (startPort as PortView).owner) From 83c1dcb2e24d374774b358740e4ef82ed873c348 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:06:38 +0100 Subject: [PATCH 09/31] Fixed node creation menu displaying interfaces --- .../Editor/Views/BaseGraphView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs index 7d1b968d..9f157c81 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs @@ -779,7 +779,7 @@ public void Initialize(BaseGraph graph) { var interfaces = nodeInfo.type.GetInterfaces(); var exceptInheritedInterfaces = interfaces.Except(interfaces.SelectMany(t => t.GetInterfaces())); - foreach (var i in interfaces) + foreach (var i in exceptInheritedInterfaces) { if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICreateNodeFrom<>)) { From 3747551f37e672e9a6d643e2b11b07a7ca16b4a6 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:07:26 +0100 Subject: [PATCH 10/31] Fixed nullreference when opening and closing the graph window too fast --- .../Editor/Views/BaseGraphView.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs index 9f157c81..dd1239d1 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs @@ -812,7 +812,8 @@ public void ClearGraphElements() void UpdateSerializedProperties() { - serializedGraph = new SerializedObject(graph); + if(graph != null) + serializedGraph = new SerializedObject(graph); } /// From 781256693b38786ab9bf66cbcebbabe65e5666eb Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:19:51 +0100 Subject: [PATCH 11/31] Add example conversion node --- .../DefaultNodes/Nodes/FloatToStringNode.cs | 35 +++++++++++++++++++ .../Nodes/FloatToStringNode.cs.meta | 3 ++ 2 files changed, 38 insertions(+) create mode 100644 Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs create mode 100644 Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs.meta diff --git a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs new file mode 100644 index 00000000..e0a8c051 --- /dev/null +++ b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs @@ -0,0 +1,35 @@ +using System; +using System.Globalization; +using GraphProcessor; +using UnityEngine; + +[Serializable, NodeMenuItem("Convert/Float to String"), ConverterNode(typeof(float), typeof(string))] +public class FloatToStringsNode : BaseNode, IConversionNode +{ + [Input("In")] + public float input; + + public int decimalPlaces = 2; + + [Output("Out")] + public string output; + + public override string name => "To String"; + + public string GetConversionInput() + { + return nameof(input); + } + + public string GetConversionOutput() + { + return nameof(output); + } + + protected override void Process() + { + var mult = Mathf.Pow(10, decimalPlaces); + float val = Mathf.Round(input * mult) / mult; + output = val.ToString(CultureInfo.InvariantCulture); + } +} \ No newline at end of file diff --git a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs.meta b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs.meta new file mode 100644 index 00000000..08d319d5 --- /dev/null +++ b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5947dfd18c94461281d83969aff7d203 +timeCreated: 1643494663 \ No newline at end of file From 5323252e69d8778e146f525dd154bb05ff644f0b Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:21:52 +0100 Subject: [PATCH 12/31] Revert "Update README.md" This reverts commit a511e4ad41953e4b1fdb2fb9518079e21bcf17e0. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d150b55..9e9e3556 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NodeGraphProcessor Fork +# NodeGraphProcessor Node graph editor framework focused on data processing using Unity UIElements, GraphView and C# 4.7 [![Discord](https://img.shields.io/discord/823720615965622323.svg)](https://discord.gg/XuMd3Z5Rym) From 8487de68a317bf9b8775baa0f0d75325e4102d4b Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:25:43 +0100 Subject: [PATCH 13/31] Fix typos --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8d150b55..60c58475 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# NodeGraphProcessor Fork +# NodeGraphProcessor Node graph editor framework focused on data processing using Unity UIElements, GraphView and C# 4.7 [![Discord](https://img.shields.io/discord/823720615965622323.svg)](https://discord.gg/XuMd3Z5Rym) @@ -89,17 +89,17 @@ Join the [NodeGraphProcessor Discord server](https://discord.gg/XuMd3Z5Rym)! - Graph processor which execute node's logic with a dependency order - [Documented C# API to add new nodes / graphs](https://github.com/alelievr/NodeGraphProcessor/wiki/Node-scripting-API) - Exposed parameters that can be set per-asset to customize the graph processing from scripts or the inspector -- Parameter set mode, you can now output data from thegraph using exposed parameters. Their values will be updated when the graph is processed +- Parameter set mode, you can now output data from the graph using exposed parameters. Their values will be updated when the graph is processed - Search window to create new nodes - Colored groups - Node messages (small message with it's icon beside the node) -- Stack Nodes +- Stack nodes - Relay nodes - Display additional settings in the inspector - Node creation menu on edge drop - Simplified edge connection compared to default GraphView (ShaderGraph and VFX Graph) - Multiple graph window workflow (copy/paste) -- Vertical Ports +- Vertical ports - Sticky notes (requires Unity 2020.1) - Renamable nodes From ecc4dc08ed51d6536bf4bfaa39f8767c6ff6a2bd Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:44:44 +0100 Subject: [PATCH 14/31] Relaxed filtering of inactive property fields --- .../Editor/Views/BaseNodeView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index b32dfebd..48b5d69c 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -835,7 +835,7 @@ internal void SyncSerializedPropertyPathes() var nodeIndexString = nodeIndex.ToString(); foreach (var propertyField in this.Query().ToList()) { - if(!propertyField.enabledSelf || propertyField.bindingPath == null) + if(propertyField.bindingPath == null) continue; propertyField.Unbind(); From 5f5c9a4a1fba5ebc5af13c91dafcd80951b7b7a3 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Mon, 31 Jan 2022 11:47:27 +0100 Subject: [PATCH 15/31] Add conversion adapter --- .../Processing/ConversionNodeAdapter.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs new file mode 100644 index 00000000..d8b76d41 --- /dev/null +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +namespace GraphProcessor +{ + [AttributeUsage(AttributeTargets.Class)] + public class ConverterNodeAttribute : Attribute + { + public Type from, to; + + public ConverterNodeAttribute(Type from, Type to) + { + this.from = from; + this.to = to; + } + } + + public interface IConversionNode + { + public string GetConversionInput(); + public string GetConversionOutput(); + } + + public static class ConversionNodeAdapter + { + private static bool conversionsLoaded = false; + + static readonly Dictionary<(Type from, Type to), Type> adapters = new Dictionary<(Type from, Type to), Type>(); + + static void LoadAllAdapters() + { + foreach (Type currType in AppDomain.CurrentDomain.GetAllTypes()) + { + var conversionAttrib = currType.GetCustomAttribute(); + if (conversionAttrib != null) + { + Debug.Assert(typeof(IConversionNode).IsAssignableFrom(currType), + "Class marked with ConverterNode attribute must implement the IConversionNode interface"); + Debug.Assert(typeof(BaseNode).IsAssignableFrom(currType), "Class marked with ConverterNode attribute must inherit from BaseNode"); + + adapters.Add((conversionAttrib.from, conversionAttrib.to), currType); + } + } + + conversionsLoaded = true; + } + + public static bool AreAssignable(Type from, Type to) + { + if (!conversionsLoaded) + LoadAllAdapters(); + + return adapters.ContainsKey((from, to)); + } + + public static Type GetConversionNode(Type from, Type to) + { + if (!conversionsLoaded) + LoadAllAdapters(); + + return adapters.TryGetValue((from, to), out Type nodeType) ? nodeType : null; + } + } +} \ No newline at end of file From e0a1dde87306cef47e097a74b051d9a184846408 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Mon, 31 Jan 2022 11:48:36 +0100 Subject: [PATCH 16/31] Add meta data --- .../Runtime/Processing/ConversionNodeAdapter.cs.meta | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs.meta diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs.meta b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs.meta new file mode 100644 index 00000000..f8bb0d29 --- /dev/null +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Processing/ConversionNodeAdapter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 604ecd0dea834136834bf1737ef7a91f +timeCreated: 1637143540 \ No newline at end of file From 664e867dbb5c4595b3025d95ca520944bd1dbcc9 Mon Sep 17 00:00:00 2001 From: Greebling <56256331+Greebling@users.noreply.github.com> Date: Mon, 31 Jan 2022 12:50:57 +0100 Subject: [PATCH 17/31] Made float rounding much more robust --- Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs index e0a8c051..7613d680 100644 --- a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs @@ -28,8 +28,7 @@ public string GetConversionOutput() protected override void Process() { - var mult = Mathf.Pow(10, decimalPlaces); - float val = Mathf.Round(input * mult) / mult; + output = input.ToString("F" + decimalPlace, CultureInfo.InvariantCulture); output = val.ToString(CultureInfo.InvariantCulture); } -} \ No newline at end of file +} From e07e1841a1a60f690a5d50119714e18350dca8a1 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 02:09:21 +0000 Subject: [PATCH 18/31] Replaced ShowAsDrawer with Input field Added proxiedFieldPath to input for CustomPortBeh ports Added ability to have property fields for dynamic ports. --- .../DefaultNodes/Nodes/DrawerFieldTestNode.cs | 60 +- Assets/Testing.meta | 8 + Assets/Testing/BaseIsConditional.cs | 35 + Assets/Testing/BaseIsConditional.cs.meta | 11 + Assets/Testing/BoolVariable.cs | 12 + Assets/Testing/BoolVariable.cs.meta | 11 + Assets/Testing/ConditionalNameNode.cs | 12 + Assets/Testing/ConditionalNameNode.cs.meta | 11 + .../DelegateEvent.VarObjectEventArgs.cs | 16 + .../DelegateEvent.VarObjectEventArgs.cs.meta | 11 + Assets/Testing/DelegateEvent.cs | 25 + Assets/Testing/DelegateEvent.cs.meta | 11 + Assets/Testing/DynamicNode.cs | 173 ++ Assets/Testing/DynamicNode.cs.meta | 11 + Assets/Testing/DynamicNodeWithOutput.cs | 20 + Assets/Testing/DynamicNodeWithOutput.cs.meta | 11 + Assets/Testing/ExpandableSOAttribute.cs | 15 + Assets/Testing/ExpandableSOAttribute.cs.meta | 11 + Assets/Testing/MyInputAttribute.cs | 22 + Assets/Testing/MyInputAttribute.cs.meta | 11 + Assets/Testing/PortUpdaterNode.cs | 12 + Assets/Testing/PortUpdaterNode.cs.meta | 11 + Assets/Testing/SequenceData.cs | 64 + Assets/Testing/SequenceData.cs.meta | 11 + Assets/Testing/SharedVariable.cs | 33 + Assets/Testing/SharedVariable.cs.meta | 11 + .../Testing/ValueChangedCallbackAttribute.cs | 23 + .../ValueChangedCallbackAttribute.cs.meta | 11 + .../Editor/Views/BaseNodeView.cs | 2338 +++++++++-------- .../Editor/Views/PortView.cs | 319 +-- .../Runtime/Elements/BaseNode.cs | 1722 ++++++------ .../Runtime/Elements/NodePort.cs | 721 ++--- .../Runtime/Graph/Attributes.cs | 480 ++-- Packages/manifest.json | 10 +- Packages/packages-lock.json | 10 +- ProjectSettings/ProjectVersion.txt | 4 +- 36 files changed, 3519 insertions(+), 2758 deletions(-) create mode 100644 Assets/Testing.meta create mode 100644 Assets/Testing/BaseIsConditional.cs create mode 100644 Assets/Testing/BaseIsConditional.cs.meta create mode 100644 Assets/Testing/BoolVariable.cs create mode 100644 Assets/Testing/BoolVariable.cs.meta create mode 100644 Assets/Testing/ConditionalNameNode.cs create mode 100644 Assets/Testing/ConditionalNameNode.cs.meta create mode 100644 Assets/Testing/DelegateEvent.VarObjectEventArgs.cs create mode 100644 Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta create mode 100644 Assets/Testing/DelegateEvent.cs create mode 100644 Assets/Testing/DelegateEvent.cs.meta create mode 100644 Assets/Testing/DynamicNode.cs create mode 100644 Assets/Testing/DynamicNode.cs.meta create mode 100644 Assets/Testing/DynamicNodeWithOutput.cs create mode 100644 Assets/Testing/DynamicNodeWithOutput.cs.meta create mode 100644 Assets/Testing/ExpandableSOAttribute.cs create mode 100644 Assets/Testing/ExpandableSOAttribute.cs.meta create mode 100644 Assets/Testing/MyInputAttribute.cs create mode 100644 Assets/Testing/MyInputAttribute.cs.meta create mode 100644 Assets/Testing/PortUpdaterNode.cs create mode 100644 Assets/Testing/PortUpdaterNode.cs.meta create mode 100644 Assets/Testing/SequenceData.cs create mode 100644 Assets/Testing/SequenceData.cs.meta create mode 100644 Assets/Testing/SharedVariable.cs create mode 100644 Assets/Testing/SharedVariable.cs.meta create mode 100644 Assets/Testing/ValueChangedCallbackAttribute.cs create mode 100644 Assets/Testing/ValueChangedCallbackAttribute.cs.meta diff --git a/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs b/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs index b30fd069..2f80e584 100644 --- a/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs @@ -8,49 +8,49 @@ public class DrawerFieldTestNode : BaseNode { - [Input(name = "Vector 4"), ShowAsDrawer] - public Vector4 vector4; + [Input(name = "Vector 4", showAsDrawer = true)] + public Vector4 vector4; - [Input(name = "Vector 3"), ShowAsDrawer] - public Vector3 vector3; + [Input(name = "Vector 3", showAsDrawer = true)] + public Vector3 vector3; - [Input(name = "Vector 2"), ShowAsDrawer] - public Vector2 vector2; + [Input(name = "Vector 2", showAsDrawer = true)] + public Vector2 vector2; - [Input(name = "Float"), ShowAsDrawer] - public float floatInput; + [Input(name = "Float", showAsDrawer = true)] + public float floatInput; - [Input(name = "Vector 3 Int"), ShowAsDrawer] - public Vector3Int vector3Int; + [Input(name = "Vector 3 Int", showAsDrawer = true)] + public Vector3Int vector3Int; - [Input(name = "Vector 2 Int"), ShowAsDrawer] - public Vector2Int vector2Int; + [Input(name = "Vector 2 Int", showAsDrawer = true)] + public Vector2Int vector2Int; - [Input(name = "Int"), ShowAsDrawer] - public int intInput; + [Input(name = "Int", showAsDrawer = true)] + public int intInput; - [Input(name = "Empty")] - public int intInput2; + [Input(name = "Empty")] + public int intInput2; - [Input(name = "String"), ShowAsDrawer] - public string stringInput; + [Input(name = "String", showAsDrawer = true)] + public string stringInput; - [Input(name = "Color"), ShowAsDrawer] - new public Color color; + [Input(name = "Color", showAsDrawer = true)] + new public Color color; - [Input(name = "Game Object"), ShowAsDrawer] - public GameObject gameObject; + [Input(name = "Game Object", showAsDrawer = true)] + public GameObject gameObject; - [Input(name = "Animation Curve"), ShowAsDrawer] - public AnimationCurve animationCurve; + [Input(name = "Animation Curve", showAsDrawer = true)] + public AnimationCurve animationCurve; - [Input(name = "Rigidbody"), ShowAsDrawer] - public Rigidbody rigidbody; + [Input(name = "Rigidbody", showAsDrawer = true)] + public Rigidbody rigidbody; - [Input("Layer Mask"), ShowAsDrawer] - public LayerMask layerMask; + [Input("Layer Mask", showAsDrawer = true)] + public LayerMask layerMask; - public override string name => "Drawer Field Test"; + public override string name => "Drawer Field Test"; - protected override void Process() {} + protected override void Process() { } } \ No newline at end of file diff --git a/Assets/Testing.meta b/Assets/Testing.meta new file mode 100644 index 00000000..56738015 --- /dev/null +++ b/Assets/Testing.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: db98db52a808b6a11bc0439ea50f7d72 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/BaseIsConditional.cs b/Assets/Testing/BaseIsConditional.cs new file mode 100644 index 00000000..5ae6efb3 --- /dev/null +++ b/Assets/Testing/BaseIsConditional.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using GraphProcessor; +using UnityEngine; + +[System.Serializable] +public class BaseIsConditional +{ + [SerializeField, Input("True Checks", false, true)] protected BoolVariable[] trueChecks; + public BoolVariable[] TrueChecks { get => trueChecks; } + + [SerializeField, Input("False Checks", false, true)] protected BoolVariable[] falseChecks; + public BoolVariable[] FalseChecks { get => falseChecks; } + + public bool IsAvailable() + { + foreach (BoolVariable check in trueChecks) + { + if (!check.RuntimeValue) return false; + } + + foreach (BoolVariable check in falseChecks) + { + if (check.RuntimeValue) return false; + } + return true; + } +} + +public interface IIsConditional +{ + List TrueChecks { get; } + List FalseChecks { get; } + + bool IsAvailable(); +} \ No newline at end of file diff --git a/Assets/Testing/BaseIsConditional.cs.meta b/Assets/Testing/BaseIsConditional.cs.meta new file mode 100644 index 00000000..7f07e8bd --- /dev/null +++ b/Assets/Testing/BaseIsConditional.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9947347622c831651bc3a306795104fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/BoolVariable.cs b/Assets/Testing/BoolVariable.cs new file mode 100644 index 00000000..01b8eb90 --- /dev/null +++ b/Assets/Testing/BoolVariable.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +[CreateAssetMenu(fileName = "BoolVariable", menuName = "Teletext/Variables/Bool")] +public class BoolVariable : SharedVariable +{ + public override bool RuntimeValue { get => GetEvaluation(); set => base.RuntimeValue = value; } + + protected virtual bool GetEvaluation() + { + return base.RuntimeValue; + } +} diff --git a/Assets/Testing/BoolVariable.cs.meta b/Assets/Testing/BoolVariable.cs.meta new file mode 100644 index 00000000..0d4d6ccf --- /dev/null +++ b/Assets/Testing/BoolVariable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c2ed7d6a9e49992bca5a969a1db59655 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/ConditionalNameNode.cs b/Assets/Testing/ConditionalNameNode.cs new file mode 100644 index 00000000..3eb7cc5f --- /dev/null +++ b/Assets/Testing/ConditionalNameNode.cs @@ -0,0 +1,12 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using GraphProcessor; +using System.Linq; +using static SequenceName; + +[System.Serializable, NodeMenuItem("Custom/ConditionalNameNode")] +public class ConditionalNameNode : DynamicNodeWithOutput +{ + public override string name => "ConditionalNameNode"; +} diff --git a/Assets/Testing/ConditionalNameNode.cs.meta b/Assets/Testing/ConditionalNameNode.cs.meta new file mode 100644 index 00000000..e41b6497 --- /dev/null +++ b/Assets/Testing/ConditionalNameNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d5842b118d32744a84e93b4530d1208 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs new file mode 100644 index 00000000..7a5d32ec --- /dev/null +++ b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +public partial class DelegateEvent +{ + public class VarObjectEventArgs : EventArgs + { + private T value; + public T Value => value; + + public VarObjectEventArgs(T value) + { + this.value = value; + } + } +} + diff --git a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta new file mode 100644 index 00000000..dfe97910 --- /dev/null +++ b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d0825e47caa8faf0b509d6413c06002 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/DelegateEvent.cs b/Assets/Testing/DelegateEvent.cs new file mode 100644 index 00000000..392b04e9 --- /dev/null +++ b/Assets/Testing/DelegateEvent.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public partial class DelegateEvent : ScriptableObject +{ + public delegate void EventHandler(object sender, VarObjectEventArgs args); + public event EventHandler Event; + + public void AddListener(EventHandler listener) + { + Event += listener; + } + + public void RemoveListener(EventHandler listener) + { + Event -= listener; + } + + public void Raise(T argument) + { + if (Event != null) + Event.Invoke(this, new VarObjectEventArgs(argument)); + } +} diff --git a/Assets/Testing/DelegateEvent.cs.meta b/Assets/Testing/DelegateEvent.cs.meta new file mode 100644 index 00000000..61429f8c --- /dev/null +++ b/Assets/Testing/DelegateEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b336ffaa1cc79bc929414981fe23fab5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/DynamicNode.cs b/Assets/Testing/DynamicNode.cs new file mode 100644 index 00000000..e5f6a538 --- /dev/null +++ b/Assets/Testing/DynamicNode.cs @@ -0,0 +1,173 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using GraphProcessor; +using System.Linq; +using System.Reflection; +using System; + +[System.Serializable] +public abstract class DynamicNode : PortUpdaterNode +{ + [Input("Action Data", true)] + public Dictionary> actionData = new Dictionary>(); + + [ExpandableSO, ValueChangedCallback(nameof(actionData), nameof(OnDataChanged))] + public T data; + + public override bool needsInspector => true; + + protected override void Process() + { + UpdateActionWithCustomPortData(); + } + + protected virtual void UpdateActionWithCustomPortData() + { + // We clone due to reference issues + Dictionary> actionDataClone = new Dictionary>(actionData); + + foreach (var field in GetInputFieldsOfType()) + { + if (!actionDataClone.ContainsKey(field.fieldInfo.Name)) + { + if (field.inputAttribute.showAsDrawer) + continue; + + field.fieldInfo.SetValue(data, default); + continue; + } + + if (field.inputAttribute is MyInputAttribute) + { + MyInputAttribute inputAttribute = field.inputAttribute as MyInputAttribute; + if (inputAttribute.InputType != null && field.fieldInfo.FieldType.GetInterfaces().Any(x => x == typeof(IList))) + { + IList list = Activator.CreateInstance(field.fieldInfo.FieldType) as IList; + foreach (var value in actionDataClone[field.fieldInfo.Name]) + list.Add(value); + + field.fieldInfo.SetValue(data, list); + continue; + } + } + + field.fieldInfo.SetValue(data, actionDataClone[field.fieldInfo.Name][0]); + } + + actionData.Clear(); + } + + #region Reflection Generation Of Ports + + private List GetInputFieldsOfType() + { + List foundInputFields = new List(); + + Type dataType = data != null ? data.GetType() : typeof(T); + foreach (var field in dataType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) + { + foreach (var attribute in field.GetCustomAttributes(typeof(InputAttribute), true)) + { + if (attribute.GetType() != typeof(InputAttribute) && !attribute.GetType().IsSubclassOf(typeof(InputAttribute))) continue; + + foundInputFields.Add(new FieldPortInfo(field, attribute as InputAttribute)); + break; + } + } + + return foundInputFields; + } + + private FieldPortInfo GetFieldPortInfo(string fieldName) + { + Type dataType = data != null ? data.GetType() : typeof(T); + + FieldInfo fieldInfo = dataType.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + InputAttribute inputAttribute = fieldInfo.GetCustomAttribute(); + + return new FieldPortInfo(fieldInfo, inputAttribute); + } + + [CustomPortInput(nameof(actionData), typeof(object))] + protected void PullInputs(List connectedEdges) + { + if (connectedEdges.Count == 0) return; + + FieldPortInfo field = GetFieldPortInfo(connectedEdges.ElementAt(0).inputPortIdentifier); + + if (actionData == null) actionData = new Dictionary>(); + foreach (var edge in connectedEdges.GetNonRelayEdges().OrderByInputAttribute(field.inputAttribute)) + { + if (!actionData.ContainsKey(field.fieldInfo.Name)) + actionData.Add(field.fieldInfo.Name, new List()); + + actionData[field.fieldInfo.Name].Add(edge.passThroughBuffer); + } + } + + [CustomPortBehavior(nameof(actionData))] + protected IEnumerable ActionDataBehaviour(List edges) // Try changing edge here when ports update + { + foreach (var field in GetInputFieldsOfType()) + { + Type displayType = field.fieldInfo.FieldType; + + if (field.inputAttribute is MyInputAttribute) + { + MyInputAttribute inputAttribute = field.inputAttribute as MyInputAttribute; + if (inputAttribute.InputType != null) + displayType = inputAttribute.InputType; + } + + yield return new PortData + { + displayName = field.inputAttribute.name, + displayType = displayType, + identifier = field.fieldInfo.Name, + showAsDrawer = field.inputAttribute.showAsDrawer, + vertical = false, + proxiedFieldPath = "data." + field.fieldInfo.Name, + acceptMultipleEdges = field.inputAttribute.allowMultiple, + }; + } + + // Debug.Log(this.GetCustomName() + " BEHAVE: " + this.inputPorts.Count); + } + + // public override IEnumerable OverrideFieldOrder(IEnumerable fields) + // { + // return base.OverrideFieldOrder(fields).Reverse(); + + // // static long GetFieldInheritanceLevel(FieldInfo f) + // // { + // // int level = 0; + // // var t = f.DeclaringType; + // // while (t != null) + // // { + // // t = t.BaseType; + // // level++; + // // } + + // // return level; + // // } + + // // // Order by MetadataToken and inheritance level to sync the order with the port order (make sure FieldDrawers are next to the correct port) + // // return fields.OrderByDescending(f => (GetFieldInheritanceLevel(f) << 32) | (long)f.MetadataToken); + + // } + + #endregion +} + +public struct FieldPortInfo +{ + public FieldInfo fieldInfo; + public InputAttribute inputAttribute; + + public FieldPortInfo(FieldInfo fieldInfo, InputAttribute inputAttribute) + { + this.fieldInfo = fieldInfo; + this.inputAttribute = inputAttribute; + } +} \ No newline at end of file diff --git a/Assets/Testing/DynamicNode.cs.meta b/Assets/Testing/DynamicNode.cs.meta new file mode 100644 index 00000000..930590b6 --- /dev/null +++ b/Assets/Testing/DynamicNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c7e41fbf9b5f5a6aaff6ceef7a38e06 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/DynamicNodeWithOutput.cs b/Assets/Testing/DynamicNodeWithOutput.cs new file mode 100644 index 00000000..b92c3c9a --- /dev/null +++ b/Assets/Testing/DynamicNodeWithOutput.cs @@ -0,0 +1,20 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using GraphProcessor; +using System.Linq; + +[System.Serializable] +public abstract class DynamicNodeWithOutput : DynamicNode +{ + [Output(name = "Out")] + public T dataOutput; + + public override string name => "DynamicNodeWithOutput"; + + protected override void Process() + { + base.Process(); + dataOutput = data; + } +} diff --git a/Assets/Testing/DynamicNodeWithOutput.cs.meta b/Assets/Testing/DynamicNodeWithOutput.cs.meta new file mode 100644 index 00000000..72c0ff3e --- /dev/null +++ b/Assets/Testing/DynamicNodeWithOutput.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 403bad8732c99c8efb7192137e8e3301 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/ExpandableSOAttribute.cs b/Assets/Testing/ExpandableSOAttribute.cs new file mode 100644 index 00000000..00c2b219 --- /dev/null +++ b/Assets/Testing/ExpandableSOAttribute.cs @@ -0,0 +1,15 @@ +using System; +using UnityEditor; +using UnityEngine; + +/// +/// Attribute takes the insides of a set ScriptableObject and display it. +/// +public class ExpandableSOAttribute : PropertyAttribute +{ + + /// + /// Required implementation of the interface. + /// + public ExpandableSOAttribute() { } +} diff --git a/Assets/Testing/ExpandableSOAttribute.cs.meta b/Assets/Testing/ExpandableSOAttribute.cs.meta new file mode 100644 index 00000000..198eb13f --- /dev/null +++ b/Assets/Testing/ExpandableSOAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf7bd1d3b461c28ca869d43343391f92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/MyInputAttribute.cs b/Assets/Testing/MyInputAttribute.cs new file mode 100644 index 00000000..d9b488d4 --- /dev/null +++ b/Assets/Testing/MyInputAttribute.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using GraphProcessor; +using UnityEngine; + +[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] +public class MyInputAttribute : InputAttribute +{ + public readonly Type InputType; + public readonly InputSortType SortType; + + public MyInputAttribute(string name = null, bool allowMultiple = false, InputSortType sortType = InputSortType.FIRST_IN, Type inputType = null) + { + this.name = name; + this.allowMultiple = allowMultiple; + this.SortType = sortType; + this.InputType = inputType; + } +} + +public enum InputSortType { FIRST_IN, POSITION_Y } diff --git a/Assets/Testing/MyInputAttribute.cs.meta b/Assets/Testing/MyInputAttribute.cs.meta new file mode 100644 index 00000000..0da6a56d --- /dev/null +++ b/Assets/Testing/MyInputAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0189acdd3b39929d6a8b0a57c3e507bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/PortUpdaterNode.cs b/Assets/Testing/PortUpdaterNode.cs new file mode 100644 index 00000000..5e4cb1ce --- /dev/null +++ b/Assets/Testing/PortUpdaterNode.cs @@ -0,0 +1,12 @@ +using System.Diagnostics; +using System.Reflection; +using GraphProcessor; +using UnityEngine; + +public abstract class PortUpdaterNode : BaseNode +{ + protected virtual void OnDataChanged(FieldInfo originField, UnityEditor.SerializedProperty serializedProperty) + { + UpdatePortsForFieldLocal(originField.Name); + } +} diff --git a/Assets/Testing/PortUpdaterNode.cs.meta b/Assets/Testing/PortUpdaterNode.cs.meta new file mode 100644 index 00000000..6aaa9538 --- /dev/null +++ b/Assets/Testing/PortUpdaterNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 67b3cddba608059109efbb060f6504fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/SequenceData.cs b/Assets/Testing/SequenceData.cs new file mode 100644 index 00000000..4358fc00 --- /dev/null +++ b/Assets/Testing/SequenceData.cs @@ -0,0 +1,64 @@ +using System.Reflection; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using GraphProcessor; +using UnityEngine; + + +[Serializable] +public class SequenceName : List +{ + public string CurrentName => FindLast(x => x.IsAvailable()).Name; + + [Serializable] + public class ConditionalName : BaseIsConditional + { + [SerializeField, Input("Name", true)] string name; + public string Name => name; + } +} + +public static class ListHelpers +{ + public static string GetCurrentName(this List list) + { + return list.FindLast(x => x.IsAvailable()).Name; + } + + public static IList OrderByInputAttribute(this IList edges, InputAttribute inputAttribute) + { + if (inputAttribute is MyInputAttribute) + { + switch ((inputAttribute as MyInputAttribute).SortType) + { + case InputSortType.POSITION_Y: + edges = edges.OrderBy(x => x.outputNode.position.y).ToList(); + break; + } + } + return edges; + } + + public static IList GetNonRelayEdges(this IList edges) + { + List nonrelayEdges = new List(); + foreach (var edge in edges) + { + if (edge.outputNode is RelayNode) + { + RelayNode relay = edge.outputNode as RelayNode; + foreach (var relayEdge in relay.GetNonRelayEdges()) + { + nonrelayEdges.Add(relayEdge); + } + } + else + { + nonrelayEdges.Add(edge); + } + } + return nonrelayEdges; + } +} \ No newline at end of file diff --git a/Assets/Testing/SequenceData.cs.meta b/Assets/Testing/SequenceData.cs.meta new file mode 100644 index 00000000..aeebf036 --- /dev/null +++ b/Assets/Testing/SequenceData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e363163a6a14c292096a628b16828935 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/SharedVariable.cs b/Assets/Testing/SharedVariable.cs new file mode 100644 index 00000000..47d04810 --- /dev/null +++ b/Assets/Testing/SharedVariable.cs @@ -0,0 +1,33 @@ +using System.Data.SqlTypes; +using UnityEngine; + +/// +/// +/// CallbackObject callback is used as a OnValueChanged event. +/// +/// +public abstract class SharedVariable : DelegateEvent +{ + [SerializeField] protected T InitialValue; + + protected T runtimeValue; + public virtual T RuntimeValue + { + get => runtimeValue; + set + { + if (runtimeValue == null && value == null) return; + else if (runtimeValue != null && runtimeValue.Equals(value)) return; + else if (value != null && value.Equals(runtimeValue)) return; + + runtimeValue = value; + Raise(value); + } + } + + // Initialize runtime value with editor's value + protected virtual void OnEnable() + { + RuntimeValue = InitialValue; + } +} \ No newline at end of file diff --git a/Assets/Testing/SharedVariable.cs.meta b/Assets/Testing/SharedVariable.cs.meta new file mode 100644 index 00000000..a4acfa26 --- /dev/null +++ b/Assets/Testing/SharedVariable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ace1829c0b27489fa70482f81ddbed7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Testing/ValueChangedCallbackAttribute.cs b/Assets/Testing/ValueChangedCallbackAttribute.cs new file mode 100644 index 00000000..792f2d0e --- /dev/null +++ b/Assets/Testing/ValueChangedCallbackAttribute.cs @@ -0,0 +1,23 @@ +using System; +using UnityEditor; +using UnityEngine; +/// +/// Check if a given field has changed and if it has calls a method. +/// +/// +public class ValueChangedCallbackAttribute : PropertyAttribute +{ + string fieldName; + public string FieldName => fieldName; + + string methodName; + public string MethodName => methodName; + + /// + /// + public ValueChangedCallbackAttribute(string fieldName, string methodName) + { + this.fieldName = fieldName; + this.methodName = methodName; + } +} \ No newline at end of file diff --git a/Assets/Testing/ValueChangedCallbackAttribute.cs.meta b/Assets/Testing/ValueChangedCallbackAttribute.cs.meta new file mode 100644 index 00000000..f59e492c --- /dev/null +++ b/Assets/Testing/ValueChangedCallbackAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9b5389fba495b15191a31e041bfedba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 5f3dfdad..bc6e9fc5 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -15,1000 +15,1062 @@ namespace GraphProcessor { - [NodeCustomEditor(typeof(BaseNode))] - public class BaseNodeView : NodeView - { - public BaseNode nodeTarget; - - public List< PortView > inputPortViews = new List< PortView >(); - public List< PortView > outputPortViews = new List< PortView >(); - - public BaseGraphView owner { private set; get; } - - protected Dictionary< string, List< PortView > > portsPerFieldName = new Dictionary< string, List< PortView > >(); - - public VisualElement controlsContainer; - protected VisualElement debugContainer; - protected VisualElement rightTitleContainer; - protected VisualElement topPortContainer; - protected VisualElement bottomPortContainer; - private VisualElement inputContainerElement; - - VisualElement settings; - NodeSettingsView settingsContainer; - Button settingButton; - TextField titleTextField; - - Label computeOrderLabel = new Label(); - - public event Action< PortView > onPortConnected; - public event Action< PortView > onPortDisconnected; - - protected virtual bool hasSettings { get; set; } - - public bool initializing = false; //Used for applying SetPosition on locked node at init. - - readonly string baseNodeStyle = "GraphProcessorStyles/BaseNodeView"; - - bool settingsExpanded = false; - - [System.NonSerialized] - List< IconBadge > badges = new List< IconBadge >(); - - private List selectedNodes = new List(); - private float selectedNodesFarLeft; - private float selectedNodesNearLeft; - private float selectedNodesFarRight; - private float selectedNodesNearRight; - private float selectedNodesFarTop; - private float selectedNodesNearTop; - private float selectedNodesFarBottom; - private float selectedNodesNearBottom; - private float selectedNodesAvgHorizontal; - private float selectedNodesAvgVertical; - - #region Initialization - - public void Initialize(BaseGraphView owner, BaseNode node) - { - nodeTarget = node; - this.owner = owner; - - if (!node.deletable) - capabilities &= ~Capabilities.Deletable; - // Note that the Renamable capability is useless right now as it haven't been implemented in Graphview - if (node.isRenamable) - capabilities |= Capabilities.Renamable; - - owner.computeOrderUpdated += ComputeOrderUpdatedCallback; - node.onMessageAdded += AddMessageView; - node.onMessageRemoved += RemoveMessageView; - node.onPortsUpdated += a => schedule.Execute(_ => UpdatePortsForField(a)).ExecuteLater(0); + [NodeCustomEditor(typeof(BaseNode))] + public class BaseNodeView : NodeView + { + public BaseNode nodeTarget; + + public List inputPortViews = new List(); + public List outputPortViews = new List(); + + public BaseGraphView owner { private set; get; } + + protected Dictionary> portsPerFieldName = new Dictionary>(); + + public VisualElement controlsContainer; + protected VisualElement debugContainer; + protected VisualElement rightTitleContainer; + protected VisualElement topPortContainer; + protected VisualElement bottomPortContainer; + private VisualElement inputContainerElement; + + VisualElement settings; + NodeSettingsView settingsContainer; + Button settingButton; + TextField titleTextField; + + Label computeOrderLabel = new Label(); + + public event Action onPortConnected; + public event Action onPortDisconnected; + + protected virtual bool hasSettings { get; set; } + + public bool initializing = false; //Used for applying SetPosition on locked node at init. + + readonly string baseNodeStyle = "GraphProcessorStyles/BaseNodeView"; + + bool settingsExpanded = false; + + [System.NonSerialized] + List badges = new List(); + + private List selectedNodes = new List(); + private float selectedNodesFarLeft; + private float selectedNodesNearLeft; + private float selectedNodesFarRight; + private float selectedNodesNearRight; + private float selectedNodesFarTop; + private float selectedNodesNearTop; + private float selectedNodesFarBottom; + private float selectedNodesNearBottom; + private float selectedNodesAvgHorizontal; + private float selectedNodesAvgVertical; + + #region Initialization + + public void Initialize(BaseGraphView owner, BaseNode node) + { + nodeTarget = node; + this.owner = owner; + + if (!node.deletable) + capabilities &= ~Capabilities.Deletable; + // Note that the Renamable capability is useless right now as it haven't been implemented in Graphview + if (node.isRenamable) + capabilities |= Capabilities.Renamable; + + owner.computeOrderUpdated += ComputeOrderUpdatedCallback; + node.onMessageAdded += AddMessageView; + node.onMessageRemoved += RemoveMessageView; + node.onPortsUpdated += a => schedule.Execute(_ => UpdatePortsForField(a)).ExecuteLater(0); styleSheets.Add(Resources.Load(baseNodeStyle)); if (!string.IsNullOrEmpty(node.layoutStyle)) styleSheets.Add(Resources.Load(node.layoutStyle)); - InitializeView(); - InitializePorts(); - InitializeDebug(); - - // If the standard Enable method is still overwritten, we call it - if (GetType().GetMethod(nameof(Enable), new Type[]{}).DeclaringType != typeof(BaseNodeView)) - ExceptionToLog.Call(() => Enable()); - else - ExceptionToLog.Call(() => Enable(false)); - - InitializeSettings(); - - RefreshExpandedState(); - - this.RefreshPorts(); - - RegisterCallback(OnGeometryChanged); - RegisterCallback(e => ExceptionToLog.Call(Disable)); - OnGeometryChanged(null); - } - - void InitializePorts() - { - var listener = owner.connectorListener; - - foreach (var inputPort in nodeTarget.inputPorts) - { - AddPort(inputPort.fieldInfo, Direction.Input, listener, inputPort.portData); - } - - foreach (var outputPort in nodeTarget.outputPorts) - { - AddPort(outputPort.fieldInfo, Direction.Output, listener, outputPort.portData); - } - } - - void InitializeView() - { - controlsContainer = new VisualElement{ name = "controls" }; - controlsContainer.AddToClassList("NodeControls"); - mainContainer.Add(controlsContainer); - - rightTitleContainer = new VisualElement{ name = "RightTitleContainer" }; - titleContainer.Add(rightTitleContainer); - - topPortContainer = new VisualElement { name = "TopPortContainer" }; - this.Insert(0, topPortContainer); - - bottomPortContainer = new VisualElement { name = "BottomPortContainer" }; - this.Add(bottomPortContainer); - - if (nodeTarget.showControlsOnHover) - { - bool mouseOverControls = false; - controlsContainer.style.display = DisplayStyle.None; - RegisterCallback(e => { - controlsContainer.style.display = DisplayStyle.Flex; - mouseOverControls = true; - }); - RegisterCallback(e => { - var rect = GetPosition(); - var graphMousePosition = owner.contentViewContainer.WorldToLocal(e.mousePosition); - if (rect.Contains(graphMousePosition) || !nodeTarget.showControlsOnHover) - return; - mouseOverControls = false; - schedule.Execute(_ => { - if (!mouseOverControls) - controlsContainer.style.display = DisplayStyle.None; - }).ExecuteLater(500); - }); - } - - Undo.undoRedoPerformed += UpdateFieldValues; - - debugContainer = new VisualElement{ name = "debug" }; - if (nodeTarget.debug) - mainContainer.Add(debugContainer); - - initializing = true; - - UpdateTitle(); + InitializeView(); + InitializePorts(); + InitializeDebug(); + + // If the standard Enable method is still overwritten, we call it + if (GetType().GetMethod(nameof(Enable), new Type[] { }).DeclaringType != typeof(BaseNodeView)) + ExceptionToLog.Call(() => Enable()); + else + ExceptionToLog.Call(() => Enable(false)); + + InitializeSettings(); + + RefreshExpandedState(); + + this.RefreshPorts(); + + RegisterCallback(OnGeometryChanged); + RegisterCallback(e => ExceptionToLog.Call(Disable)); + OnGeometryChanged(null); + } + + void InitializePorts() + { + var listener = owner.connectorListener; + + foreach (var inputPort in nodeTarget.inputPorts) + { + AddPort(inputPort.fieldInfo, Direction.Input, listener, inputPort.portData); + } + + foreach (var outputPort in nodeTarget.outputPorts) + { + AddPort(outputPort.fieldInfo, Direction.Output, listener, outputPort.portData); + } + } + + void InitializeView() + { + controlsContainer = new VisualElement { name = "controls" }; + controlsContainer.AddToClassList("NodeControls"); + mainContainer.Add(controlsContainer); + + rightTitleContainer = new VisualElement { name = "RightTitleContainer" }; + titleContainer.Add(rightTitleContainer); + + topPortContainer = new VisualElement { name = "TopPortContainer" }; + this.Insert(0, topPortContainer); + + bottomPortContainer = new VisualElement { name = "BottomPortContainer" }; + this.Add(bottomPortContainer); + + if (nodeTarget.showControlsOnHover) + { + bool mouseOverControls = false; + controlsContainer.style.display = DisplayStyle.None; + RegisterCallback(e => + { + controlsContainer.style.display = DisplayStyle.Flex; + mouseOverControls = true; + }); + RegisterCallback(e => + { + var rect = GetPosition(); + var graphMousePosition = owner.contentViewContainer.WorldToLocal(e.mousePosition); + if (rect.Contains(graphMousePosition) || !nodeTarget.showControlsOnHover) + return; + mouseOverControls = false; + schedule.Execute(_ => + { + if (!mouseOverControls) + controlsContainer.style.display = DisplayStyle.None; + }).ExecuteLater(500); + }); + } + + Undo.undoRedoPerformed += UpdateFieldValues; + + debugContainer = new VisualElement { name = "debug" }; + if (nodeTarget.debug) + mainContainer.Add(debugContainer); + + initializing = true; + + UpdateTitle(); SetPosition(nodeTarget.position); - SetNodeColor(nodeTarget.color); - - AddInputContainer(); - - // Add renaming capability - if ((capabilities & Capabilities.Renamable) != 0) - SetupRenamableTitle(); - } - - void SetupRenamableTitle() - { - var titleLabel = this.Q("title-label") as Label; - - titleTextField = new TextField{ isDelayed = true }; - titleTextField.style.display = DisplayStyle.None; - titleLabel.parent.Insert(0, titleTextField); - - titleLabel.RegisterCallback(e => { - if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse) - OpenTitleEditor(); - }); - - titleTextField.RegisterValueChangedCallback(e => CloseAndSaveTitleEditor(e.newValue)); - - titleTextField.RegisterCallback(e => { - if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse) - CloseAndSaveTitleEditor(titleTextField.value); - }); - - titleTextField.RegisterCallback(e => CloseAndSaveTitleEditor(titleTextField.value)); - - void OpenTitleEditor() - { - // show title textbox - titleTextField.style.display = DisplayStyle.Flex; - titleLabel.style.display = DisplayStyle.None; - titleTextField.focusable = true; - - titleTextField.SetValueWithoutNotify(title); - titleTextField.Focus(); - titleTextField.SelectAll(); - } - - void CloseAndSaveTitleEditor(string newTitle) - { - owner.RegisterCompleteObjectUndo("Renamed node " + newTitle); - nodeTarget.SetCustomName(newTitle); - - // hide title TextBox - titleTextField.style.display = DisplayStyle.None; - titleLabel.style.display = DisplayStyle.Flex; - titleTextField.focusable = false; - - UpdateTitle(); - } - } - - void UpdateTitle() - { - title = (nodeTarget.GetCustomName() == null) ? nodeTarget.GetType().Name : nodeTarget.GetCustomName(); - } - - void InitializeSettings() - { - // Initialize settings button: - if (hasSettings) - { - CreateSettingButton(); - settingsContainer = new NodeSettingsView(); - settingsContainer.visible = false; - settings = new VisualElement(); - // Add Node type specific settings - settings.Add(CreateSettingsView()); - settingsContainer.Add(settings); - Add(settingsContainer); - - var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - - foreach(var field in fields) - if(field.GetCustomAttribute(typeof(SettingAttribute)) != null) - AddSettingField(field); - } - } - - void OnGeometryChanged(GeometryChangedEvent evt) - { - if (settingButton != null) - { - var settingsButtonLayout = settingButton.ChangeCoordinatesTo(settingsContainer.parent, settingButton.layout); - settingsContainer.style.top = settingsButtonLayout.yMax - 18f; - settingsContainer.style.left = settingsButtonLayout.xMin - layout.width + 20f; - } - } - - // Workaround for bug in GraphView that makes the node selection border way too big - VisualElement selectionBorder, nodeBorder; - internal void EnableSyncSelectionBorderHeight() - { - if (selectionBorder == null || nodeBorder == null) - { - selectionBorder = this.Q("selection-border"); - nodeBorder = this.Q("node-border"); - - schedule.Execute(() => { - selectionBorder.style.height = nodeBorder.localBound.height; - }).Every(17); - } - } - - void CreateSettingButton() - { - settingButton = new Button(ToggleSettings){name = "settings-button"}; - settingButton.Add(new Image { name = "icon", scaleMode = ScaleMode.ScaleToFit }); - - titleContainer.Add(settingButton); - } - - void ToggleSettings() - { - settingsExpanded = !settingsExpanded; - if (settingsExpanded) - OpenSettings(); - else - CloseSettings(); - } - - public void OpenSettings() - { - if (settingsContainer != null) - { - owner.ClearSelection(); - owner.AddToSelection(this); - - settingButton.AddToClassList("clicked"); - settingsContainer.visible = true; - settingsExpanded = true; - } - } - - public void CloseSettings() - { - if (settingsContainer != null) - { - settingButton.RemoveFromClassList("clicked"); - settingsContainer.visible = false; - settingsExpanded = false; - } - } - - void InitializeDebug() - { - ComputeOrderUpdatedCallback(); - debugContainer.Add(computeOrderLabel); - } - - #endregion - - #region API - - public List< PortView > GetPortViewsFromFieldName(string fieldName) - { - List< PortView > ret; - - portsPerFieldName.TryGetValue(fieldName, out ret); - - return ret; - } - - public PortView GetFirstPortViewFromFieldName(string fieldName) - { - return GetPortViewsFromFieldName(fieldName)?.First(); - } - - public PortView GetPortViewFromFieldName(string fieldName, string identifier) - { - return GetPortViewsFromFieldName(fieldName)?.FirstOrDefault(pv => { - return (pv.portData.identifier == identifier) || (String.IsNullOrEmpty(pv.portData.identifier) && String.IsNullOrEmpty(identifier)); - }); - } - - - public PortView AddPort(FieldInfo fieldInfo, Direction direction, BaseEdgeConnectorListener listener, PortData portData) - { - PortView p = CreatePortView(direction, fieldInfo, portData, listener); - - if (p.direction == Direction.Input) - { - inputPortViews.Add(p); - - if (portData.vertical) - topPortContainer.Add(p); - else - inputContainer.Add(p); - } - else - { - outputPortViews.Add(p); - - if (portData.vertical) - bottomPortContainer.Add(p); - else - outputContainer.Add(p); - } - - p.Initialize(this, portData?.displayName); - - List< PortView > ports; - portsPerFieldName.TryGetValue(p.fieldName, out ports); - if (ports == null) - { - ports = new List< PortView >(); - portsPerFieldName[p.fieldName] = ports; - } - ports.Add(p); - - return p; - } + SetNodeColor(nodeTarget.color); + + AddInputContainer(); + + // Add renaming capability + if ((capabilities & Capabilities.Renamable) != 0) + SetupRenamableTitle(); + } + + void SetupRenamableTitle() + { + var titleLabel = this.Q("title-label") as Label; + + titleTextField = new TextField { isDelayed = true }; + titleTextField.style.display = DisplayStyle.None; + titleLabel.parent.Insert(0, titleTextField); + + titleLabel.RegisterCallback(e => + { + if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse) + OpenTitleEditor(); + }); + + titleTextField.RegisterValueChangedCallback(e => CloseAndSaveTitleEditor(e.newValue)); + + titleTextField.RegisterCallback(e => + { + if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse) + CloseAndSaveTitleEditor(titleTextField.value); + }); + + titleTextField.RegisterCallback(e => CloseAndSaveTitleEditor(titleTextField.value)); + + void OpenTitleEditor() + { + // show title textbox + titleTextField.style.display = DisplayStyle.Flex; + titleLabel.style.display = DisplayStyle.None; + titleTextField.focusable = true; + + titleTextField.SetValueWithoutNotify(title); + titleTextField.Focus(); + titleTextField.SelectAll(); + } + + void CloseAndSaveTitleEditor(string newTitle) + { + owner.RegisterCompleteObjectUndo("Renamed node " + newTitle); + nodeTarget.SetCustomName(newTitle); + + // hide title TextBox + titleTextField.style.display = DisplayStyle.None; + titleLabel.style.display = DisplayStyle.Flex; + titleTextField.focusable = false; + + UpdateTitle(); + } + } + + void UpdateTitle() + { + title = (nodeTarget.GetCustomName() == null) ? nodeTarget.GetType().Name : nodeTarget.GetCustomName(); + } + + void InitializeSettings() + { + // Initialize settings button: + if (hasSettings) + { + CreateSettingButton(); + settingsContainer = new NodeSettingsView(); + settingsContainer.visible = false; + settings = new VisualElement(); + // Add Node type specific settings + settings.Add(CreateSettingsView()); + settingsContainer.Add(settings); + Add(settingsContainer); + + var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + foreach (var field in fields) + if (field.GetCustomAttribute(typeof(SettingAttribute)) != null) + AddSettingField(field); + } + } + + void OnGeometryChanged(GeometryChangedEvent evt) + { + if (settingButton != null) + { + var settingsButtonLayout = settingButton.ChangeCoordinatesTo(settingsContainer.parent, settingButton.layout); + settingsContainer.style.top = settingsButtonLayout.yMax - 18f; + settingsContainer.style.left = settingsButtonLayout.xMin - layout.width + 20f; + } + } + + // Workaround for bug in GraphView that makes the node selection border way too big + VisualElement selectionBorder, nodeBorder; + internal void EnableSyncSelectionBorderHeight() + { + if (selectionBorder == null || nodeBorder == null) + { + selectionBorder = this.Q("selection-border"); + nodeBorder = this.Q("node-border"); + + schedule.Execute(() => + { + selectionBorder.style.height = nodeBorder.localBound.height; + }).Every(17); + } + } + + void CreateSettingButton() + { + settingButton = new Button(ToggleSettings) { name = "settings-button" }; + settingButton.Add(new Image { name = "icon", scaleMode = ScaleMode.ScaleToFit }); + + titleContainer.Add(settingButton); + } + + void ToggleSettings() + { + settingsExpanded = !settingsExpanded; + if (settingsExpanded) + OpenSettings(); + else + CloseSettings(); + } + + public void OpenSettings() + { + if (settingsContainer != null) + { + owner.ClearSelection(); + owner.AddToSelection(this); + + settingButton.AddToClassList("clicked"); + settingsContainer.visible = true; + settingsExpanded = true; + } + } + + public void CloseSettings() + { + if (settingsContainer != null) + { + settingButton.RemoveFromClassList("clicked"); + settingsContainer.visible = false; + settingsExpanded = false; + } + } + + void InitializeDebug() + { + ComputeOrderUpdatedCallback(); + debugContainer.Add(computeOrderLabel); + } + + #endregion + + #region API + + public List GetPortViewsFromFieldName(string fieldName) + { + List ret; + + portsPerFieldName.TryGetValue(fieldName, out ret); + + return ret; + } + + public PortView GetFirstPortViewFromFieldName(string fieldName) + { + return GetPortViewsFromFieldName(fieldName)?.First(); + } + + public PortView GetPortViewFromFieldName(string fieldName, string identifier) + { + return GetPortViewsFromFieldName(fieldName)?.FirstOrDefault(pv => + { + return (pv.portData.identifier == identifier) || (String.IsNullOrEmpty(pv.portData.identifier) && String.IsNullOrEmpty(identifier)); + }); + } + + + public PortView AddPort(FieldInfo fieldInfo, Direction direction, BaseEdgeConnectorListener listener, PortData portData) + { + PortView p = CreatePortView(direction, fieldInfo, portData, listener); + + if (p.direction == Direction.Input) + { + inputPortViews.Add(p); + + if (portData.vertical) + topPortContainer.Add(p); + else + inputContainer.Add(p); + } + else + { + outputPortViews.Add(p); + + if (portData.vertical) + bottomPortContainer.Add(p); + else + outputContainer.Add(p); + } + + p.Initialize(this, portData?.displayName); + + List ports; + portsPerFieldName.TryGetValue(p.fieldName, out ports); + if (ports == null) + { + ports = new List(); + portsPerFieldName[p.fieldName] = ports; + } + ports.Add(p); + + return p; + } protected virtual PortView CreatePortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener listener) - => PortView.CreatePortView(direction, fieldInfo, portData, listener); + => PortView.CreatePortView(direction, fieldInfo, portData, listener); public void InsertPort(PortView portView, int index) - { - if (portView.direction == Direction.Input) - { - if (portView.portData.vertical) - topPortContainer.Insert(index, portView); - else - inputContainer.Insert(index, portView); - } - else - { - if (portView.portData.vertical) - bottomPortContainer.Insert(index, portView); - else - outputContainer.Insert(index, portView); - } - } - - public void RemovePort(PortView p) - { - // Remove all connected edges: - var edgesCopy = p.GetEdges().ToList(); - foreach (var e in edgesCopy) - owner.Disconnect(e, refreshPorts: false); - - if (p.direction == Direction.Input) - { - if (inputPortViews.Remove(p)) - p.RemoveFromHierarchy(); - } - else - { - if (outputPortViews.Remove(p)) - p.RemoveFromHierarchy(); - } - - List< PortView > ports; - portsPerFieldName.TryGetValue(p.fieldName, out ports); - ports.Remove(p); - } - - private void SetValuesForSelectedNodes() - { - selectedNodes = new List(); - owner.nodes.ForEach(node => - { - if(node.selected) selectedNodes.Add(node); - }); - - if(selectedNodes.Count < 2) return; // No need for any of the calculations below - - selectedNodesFarLeft = int.MinValue; - selectedNodesFarRight = int.MinValue; - selectedNodesFarTop = int.MinValue; - selectedNodesFarBottom = int.MinValue; - - selectedNodesNearLeft = int.MaxValue; - selectedNodesNearRight = int.MaxValue; - selectedNodesNearTop = int.MaxValue; - selectedNodesNearBottom = int.MaxValue; - - foreach(var selectedNode in selectedNodes) - { - var nodeStyle = selectedNode.style; - var nodeWidth = selectedNode.localBound.size.x; - var nodeHeight = selectedNode.localBound.size.y; - - if(nodeStyle.left.value.value > selectedNodesFarLeft) selectedNodesFarLeft = nodeStyle.left.value.value; - if(nodeStyle.left.value.value + nodeWidth > selectedNodesFarRight) selectedNodesFarRight = nodeStyle.left.value.value + nodeWidth; - if(nodeStyle.top.value.value > selectedNodesFarTop) selectedNodesFarTop = nodeStyle.top.value.value; - if(nodeStyle.top.value.value + nodeHeight > selectedNodesFarBottom) selectedNodesFarBottom = nodeStyle.top.value.value + nodeHeight; - - if(nodeStyle.left.value.value < selectedNodesNearLeft) selectedNodesNearLeft = nodeStyle.left.value.value; - if(nodeStyle.left.value.value + nodeWidth < selectedNodesNearRight) selectedNodesNearRight = nodeStyle.left.value.value + nodeWidth; - if(nodeStyle.top.value.value < selectedNodesNearTop) selectedNodesNearTop = nodeStyle.top.value.value; - if(nodeStyle.top.value.value + nodeHeight < selectedNodesNearBottom) selectedNodesNearBottom = nodeStyle.top.value.value + nodeHeight; - } - - selectedNodesAvgHorizontal = (selectedNodesNearLeft + selectedNodesFarRight) / 2f; - selectedNodesAvgVertical = (selectedNodesNearTop + selectedNodesFarBottom) / 2f; - } - - public static Rect GetNodeRect(Node node, float left = int.MaxValue, float top = int.MaxValue) - { - return new Rect( - new Vector2(left != int.MaxValue ? left : node.style.left.value.value, top != int.MaxValue ? top : node.style.top.value.value), - new Vector2(node.style.width.value.value, node.style.height.value.value) - ); - } - - public void AlignToLeft() - { - SetValuesForSelectedNodes(); - if(selectedNodes.Count < 2) return; - - foreach(var selectedNode in selectedNodes) - { - selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesNearLeft)); - } - } - - public void AlignToCenter() - { - SetValuesForSelectedNodes(); - if(selectedNodes.Count < 2) return; - - foreach(var selectedNode in selectedNodes) - { - selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesAvgHorizontal - selectedNode.localBound.size.x / 2f)); - } - } - - public void AlignToRight() - { - SetValuesForSelectedNodes(); - if(selectedNodes.Count < 2) return; - - foreach(var selectedNode in selectedNodes) - { - selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesFarRight - selectedNode.localBound.size.x)); - } - } - - public void AlignToTop() - { - SetValuesForSelectedNodes(); - if(selectedNodes.Count < 2) return; - - foreach(var selectedNode in selectedNodes) - { - selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesNearTop)); - } - } - - public void AlignToMiddle() - { - SetValuesForSelectedNodes(); - if(selectedNodes.Count < 2) return; - - foreach(var selectedNode in selectedNodes) - { - selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesAvgVertical - selectedNode.localBound.size.y / 2f)); - } - } - - public void AlignToBottom() - { - SetValuesForSelectedNodes(); - if(selectedNodes.Count < 2) return; - - foreach(var selectedNode in selectedNodes) - { - selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesFarBottom - selectedNode.localBound.size.y)); - } - } - - public void OpenNodeViewScript() - { - var script = NodeProvider.GetNodeViewScript(GetType()); - - if (script != null) - AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0); - } - - public void OpenNodeScript() - { - var script = NodeProvider.GetNodeScript(nodeTarget.GetType()); - - if (script != null) - AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0); - } - - public void ToggleDebug() - { - nodeTarget.debug = !nodeTarget.debug; - UpdateDebugView(); - } - - public void UpdateDebugView() - { - if (nodeTarget.debug) - mainContainer.Add(debugContainer); - else - mainContainer.Remove(debugContainer); - } - - public void AddMessageView(string message, Texture icon, Color color) - => AddBadge(new NodeBadgeView(message, icon, color)); - - public void AddMessageView(string message, NodeMessageType messageType) - { - IconBadge badge = null; - switch (messageType) - { - case NodeMessageType.Warning: - badge = new NodeBadgeView(message, EditorGUIUtility.IconContent("Collab.Warning").image, Color.yellow); - break ; - case NodeMessageType.Error: - badge = IconBadge.CreateError(message); - break ; - case NodeMessageType.Info: - badge = IconBadge.CreateComment(message); - break ; - default: - case NodeMessageType.None: - badge = new NodeBadgeView(message, null, Color.grey); - break ; - } - - AddBadge(badge); - } - - void AddBadge(IconBadge badge) - { - Add(badge); - badges.Add(badge); - badge.AttachTo(topContainer, SpriteAlignment.TopRight); - } - - void RemoveBadge(Func callback) - { - badges.RemoveAll(b => { - if (callback(b)) - { - b.Detach(); - b.RemoveFromHierarchy(); - return true; - } - return false; - }); - } - - public void RemoveMessageViewContains(string message) => RemoveBadge(b => b.badgeText.Contains(message)); - - public void RemoveMessageView(string message) => RemoveBadge(b => b.badgeText == message); - - public void Highlight() - { - AddToClassList("Highlight"); - } - - public void UnHighlight() - { - RemoveFromClassList("Highlight"); - } - - #endregion - - #region Callbacks & Overrides - - void ComputeOrderUpdatedCallback() - { - //Update debug compute order - computeOrderLabel.text = "Compute order: " + nodeTarget.computeOrder; - } - - public virtual void Enable(bool fromInspector = false) => DrawDefaultInspector(fromInspector); - public virtual void Enable() => DrawDefaultInspector(false); - - public virtual void Disable() {} - - Dictionary> visibleConditions = new Dictionary>(); - Dictionary hideElementIfConnected = new Dictionary(); - Dictionary> fieldControlsMap = new Dictionary>(); - - protected void AddInputContainer() - { - inputContainerElement = new VisualElement {name = "input-container"}; - mainContainer.parent.Add(inputContainerElement); - inputContainerElement.SendToBack(); - inputContainerElement.pickingMode = PickingMode.Ignore; - } - - protected virtual void DrawDefaultInspector(bool fromInspector = false) - { - var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - // Filter fields from the BaseNode type since we are only interested in user-defined fields - // (better than BindingFlags.DeclaredOnly because we keep any inherited user-defined fields) - .Where(f => f.DeclaringType != typeof(BaseNode)); - - fields = nodeTarget.OverrideFieldOrder(fields).Reverse(); - - foreach (var field in fields) - { - //skip if the field is a node setting - if(field.GetCustomAttribute(typeof(SettingAttribute)) != null) - { - hasSettings = true; - continue; - } - - //skip if the field is not serializable - bool serializeField = field.GetCustomAttribute(typeof(SerializeField)) != null; - if((!field.IsPublic && !serializeField) || field.IsNotSerialized) - { - AddEmptyField(field, fromInspector); - continue; - } - - //skip if the field is an input/output and not marked as SerializedField - bool hasInputAttribute = field.GetCustomAttribute(typeof(InputAttribute)) != null; - bool hasInputOrOutputAttribute = hasInputAttribute || field.GetCustomAttribute(typeof(OutputAttribute)) != null; - bool showAsDrawer = !fromInspector && field.GetCustomAttribute(typeof(ShowAsDrawer)) != null; - if (!serializeField && hasInputOrOutputAttribute && !showAsDrawer) - { - AddEmptyField(field, fromInspector); - continue; - } - - //skip if marked with NonSerialized or HideInInspector - if (field.GetCustomAttribute(typeof(System.NonSerializedAttribute)) != null || field.GetCustomAttribute(typeof(HideInInspector)) != null) - { - AddEmptyField(field, fromInspector); - continue; - } - - // Hide the field if we want to display in in the inspector - var showInInspector = field.GetCustomAttribute(); - if (!serializeField && showInInspector != null && !showInInspector.showInNode && !fromInspector) - { - AddEmptyField(field, fromInspector); - continue; - } - - var showInputDrawer = field.GetCustomAttribute(typeof(InputAttribute)) != null && field.GetCustomAttribute(typeof(SerializeField)) != null; - showInputDrawer |= field.GetCustomAttribute(typeof(InputAttribute)) != null && field.GetCustomAttribute(typeof(ShowAsDrawer)) != null; - showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector - showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType); - - string displayName = ObjectNames.NicifyVariableName(field.Name); - - var inspectorNameAttribute = field.GetCustomAttribute(); - if (inspectorNameAttribute != null) - displayName = inspectorNameAttribute.displayName; - - var elem = AddControlField(field, displayName, showInputDrawer); - if (hasInputAttribute) - { - hideElementIfConnected[field.Name] = elem; - - // Hide the field right away if there is already a connection: - if (portsPerFieldName.TryGetValue(field.Name, out var pvs)) - if (pvs.Any(pv => pv.GetEdges().Count > 0)) - elem.style.display = DisplayStyle.None; - } - } - } - - protected virtual void SetNodeColor(Color color) - { - titleContainer.style.borderBottomColor = new StyleColor(color); - titleContainer.style.borderBottomWidth = new StyleFloat(color.a > 0 ? 5f : 0f); - } - - private void AddEmptyField(FieldInfo field, bool fromInspector) - { - if (field.GetCustomAttribute(typeof(InputAttribute)) == null || fromInspector) - return; - - if (field.GetCustomAttribute() != null) - return; - - var box = new VisualElement {name = field.Name}; - box.AddToClassList("port-input-element"); - box.AddToClassList("empty"); - inputContainerElement.Add(box); - } - - void UpdateFieldVisibility(string fieldName, object newValue) - { - if (newValue == null) - return; - if (visibleConditions.TryGetValue(fieldName, out var list)) - { - foreach (var elem in list) - { - if (newValue.Equals(elem.value)) - elem.target.style.display = DisplayStyle.Flex; - else - elem.target.style.display = DisplayStyle.None; - } - } - } - - void UpdateOtherFieldValueSpecific(FieldInfo field, object newValue) - { - foreach (var inputField in fieldControlsMap[field]) - { - var notify = inputField as INotifyValueChanged; - if (notify != null) - notify.SetValueWithoutNotify((T)newValue); - } - } - - static MethodInfo specificUpdateOtherFieldValue = typeof(BaseNodeView).GetMethod(nameof(UpdateOtherFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance); - void UpdateOtherFieldValue(FieldInfo info, object newValue) - { - // Warning: Keep in sync with FieldFactory CreateField - var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType; - var genericUpdate = specificUpdateOtherFieldValue.MakeGenericMethod(fieldType); - - genericUpdate.Invoke(this, new object[]{info, newValue}); - } - - object GetInputFieldValueSpecific(FieldInfo field) - { - if (fieldControlsMap.TryGetValue(field, out var list)) - { - foreach (var inputField in list) - { - if (inputField is INotifyValueChanged notify) - return notify.value; - } - } - return null; - } - - static MethodInfo specificGetValue = typeof(BaseNodeView).GetMethod(nameof(GetInputFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance); - object GetInputFieldValue(FieldInfo info) - { - // Warning: Keep in sync with FieldFactory CreateField - var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType; - var genericUpdate = specificGetValue.MakeGenericMethod(fieldType); - - return genericUpdate.Invoke(this, new object[]{info}); - } - - protected VisualElement AddControlField(string fieldName, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) - => AddControlField(nodeTarget.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), label, showInputDrawer, valueChangedCallback); - - Regex s_ReplaceNodeIndexPropertyPath = new Regex(@"(^nodes.Array.data\[)(\d+)(\])"); - internal void SyncSerializedPropertyPathes() - { - int nodeIndex = owner.graph.nodes.FindIndex(n => n == nodeTarget); - - // If the node is not found, then it means that it has been deleted from serialized data. - if (nodeIndex == -1) - return; - - var nodeIndexString = nodeIndex.ToString(); - foreach (var propertyField in this.Query().ToList()) - { - propertyField.Unbind(); - // The property path look like this: nodes.Array.data[x].fieldName - // And we want to update the value of x with the new node index: - propertyField.bindingPath = s_ReplaceNodeIndexPropertyPath.Replace(propertyField.bindingPath, m => m.Groups[1].Value + nodeIndexString + m.Groups[3].Value); - propertyField.Bind(owner.serializedGraph); - } - } - - protected SerializedProperty FindSerializedProperty(string fieldName) - { - int i = owner.graph.nodes.FindIndex(n => n == nodeTarget); - return owner.serializedGraph.FindProperty("nodes").GetArrayElementAtIndex(i).FindPropertyRelative(fieldName); - } - - protected VisualElement AddControlField(FieldInfo field, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) - { - if (field == null) - return null; - - var element = new PropertyField(FindSerializedProperty(field.Name), showInputDrawer ? "" : label); - element.Bind(owner.serializedGraph); + { + if (portView.direction == Direction.Input) + { + if (portView.portData.vertical) + topPortContainer.Insert(index, portView); + else + inputContainer.Insert(index, portView); + } + else + { + if (portView.portData.vertical) + bottomPortContainer.Insert(index, portView); + else + outputContainer.Insert(index, portView); + } + } + + public void RemovePort(PortView p) + { + // Remove all connected edges: + var edgesCopy = p.GetEdges().ToList(); + foreach (var e in edgesCopy) + owner.Disconnect(e, refreshPorts: false); + + if (p.direction == Direction.Input) + { + if (inputPortViews.Remove(p)) + p.RemoveFromHierarchy(); + } + else + { + if (outputPortViews.Remove(p)) + p.RemoveFromHierarchy(); + } + + List ports; + portsPerFieldName.TryGetValue(p.fieldName, out ports); + ports.Remove(p); + } + + private void SetValuesForSelectedNodes() + { + selectedNodes = new List(); + owner.nodes.ForEach(node => + { + if (node.selected) selectedNodes.Add(node); + }); + + if (selectedNodes.Count < 2) return; // No need for any of the calculations below + + selectedNodesFarLeft = int.MinValue; + selectedNodesFarRight = int.MinValue; + selectedNodesFarTop = int.MinValue; + selectedNodesFarBottom = int.MinValue; + + selectedNodesNearLeft = int.MaxValue; + selectedNodesNearRight = int.MaxValue; + selectedNodesNearTop = int.MaxValue; + selectedNodesNearBottom = int.MaxValue; + + foreach (var selectedNode in selectedNodes) + { + var nodeStyle = selectedNode.style; + var nodeWidth = selectedNode.localBound.size.x; + var nodeHeight = selectedNode.localBound.size.y; + + if (nodeStyle.left.value.value > selectedNodesFarLeft) selectedNodesFarLeft = nodeStyle.left.value.value; + if (nodeStyle.left.value.value + nodeWidth > selectedNodesFarRight) selectedNodesFarRight = nodeStyle.left.value.value + nodeWidth; + if (nodeStyle.top.value.value > selectedNodesFarTop) selectedNodesFarTop = nodeStyle.top.value.value; + if (nodeStyle.top.value.value + nodeHeight > selectedNodesFarBottom) selectedNodesFarBottom = nodeStyle.top.value.value + nodeHeight; + + if (nodeStyle.left.value.value < selectedNodesNearLeft) selectedNodesNearLeft = nodeStyle.left.value.value; + if (nodeStyle.left.value.value + nodeWidth < selectedNodesNearRight) selectedNodesNearRight = nodeStyle.left.value.value + nodeWidth; + if (nodeStyle.top.value.value < selectedNodesNearTop) selectedNodesNearTop = nodeStyle.top.value.value; + if (nodeStyle.top.value.value + nodeHeight < selectedNodesNearBottom) selectedNodesNearBottom = nodeStyle.top.value.value + nodeHeight; + } + + selectedNodesAvgHorizontal = (selectedNodesNearLeft + selectedNodesFarRight) / 2f; + selectedNodesAvgVertical = (selectedNodesNearTop + selectedNodesFarBottom) / 2f; + } + + public static Rect GetNodeRect(Node node, float left = int.MaxValue, float top = int.MaxValue) + { + return new Rect( + new Vector2(left != int.MaxValue ? left : node.style.left.value.value, top != int.MaxValue ? top : node.style.top.value.value), + new Vector2(node.style.width.value.value, node.style.height.value.value) + ); + } + + public void AlignToLeft() + { + SetValuesForSelectedNodes(); + if (selectedNodes.Count < 2) return; + + foreach (var selectedNode in selectedNodes) + { + selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesNearLeft)); + } + } + + public void AlignToCenter() + { + SetValuesForSelectedNodes(); + if (selectedNodes.Count < 2) return; + + foreach (var selectedNode in selectedNodes) + { + selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesAvgHorizontal - selectedNode.localBound.size.x / 2f)); + } + } + + public void AlignToRight() + { + SetValuesForSelectedNodes(); + if (selectedNodes.Count < 2) return; + + foreach (var selectedNode in selectedNodes) + { + selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesFarRight - selectedNode.localBound.size.x)); + } + } + + public void AlignToTop() + { + SetValuesForSelectedNodes(); + if (selectedNodes.Count < 2) return; + + foreach (var selectedNode in selectedNodes) + { + selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesNearTop)); + } + } + + public void AlignToMiddle() + { + SetValuesForSelectedNodes(); + if (selectedNodes.Count < 2) return; + + foreach (var selectedNode in selectedNodes) + { + selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesAvgVertical - selectedNode.localBound.size.y / 2f)); + } + } + + public void AlignToBottom() + { + SetValuesForSelectedNodes(); + if (selectedNodes.Count < 2) return; + + foreach (var selectedNode in selectedNodes) + { + selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesFarBottom - selectedNode.localBound.size.y)); + } + } + + public void OpenNodeViewScript() + { + var script = NodeProvider.GetNodeViewScript(GetType()); + + if (script != null) + AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0); + } + + public void OpenNodeScript() + { + var script = NodeProvider.GetNodeScript(nodeTarget.GetType()); + + if (script != null) + AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0); + } + + public void ToggleDebug() + { + nodeTarget.debug = !nodeTarget.debug; + UpdateDebugView(); + } + + public void UpdateDebugView() + { + if (nodeTarget.debug) + mainContainer.Add(debugContainer); + else + mainContainer.Remove(debugContainer); + } + + public void AddMessageView(string message, Texture icon, Color color) + => AddBadge(new NodeBadgeView(message, icon, color)); + + public void AddMessageView(string message, NodeMessageType messageType) + { + IconBadge badge = null; + switch (messageType) + { + case NodeMessageType.Warning: + badge = new NodeBadgeView(message, EditorGUIUtility.IconContent("Collab.Warning").image, Color.yellow); + break; + case NodeMessageType.Error: + badge = IconBadge.CreateError(message); + break; + case NodeMessageType.Info: + badge = IconBadge.CreateComment(message); + break; + default: + case NodeMessageType.None: + badge = new NodeBadgeView(message, null, Color.grey); + break; + } + + AddBadge(badge); + } + + void AddBadge(IconBadge badge) + { + Add(badge); + badges.Add(badge); + badge.AttachTo(topContainer, SpriteAlignment.TopRight); + } + + void RemoveBadge(Func callback) + { + badges.RemoveAll(b => + { + if (callback(b)) + { + b.Detach(); + b.RemoveFromHierarchy(); + return true; + } + return false; + }); + } + + public void RemoveMessageViewContains(string message) => RemoveBadge(b => b.badgeText.Contains(message)); + + public void RemoveMessageView(string message) => RemoveBadge(b => b.badgeText == message); + + public void Highlight() + { + AddToClassList("Highlight"); + } + + public void UnHighlight() + { + RemoveFromClassList("Highlight"); + } + + #endregion + + #region Callbacks & Overrides + + void ComputeOrderUpdatedCallback() + { + //Update debug compute order + computeOrderLabel.text = "Compute order: " + nodeTarget.computeOrder; + } + + public virtual void Enable(bool fromInspector = false) => DrawDefaultInspector(fromInspector); + public virtual void Enable() => DrawDefaultInspector(false); + + public virtual void Disable() { } + + Dictionary> visibleConditions = new Dictionary>(); + Dictionary hideElementIfConnected = new Dictionary(); + Dictionary> fieldControlsMap = new Dictionary>(); + + protected void AddInputContainer() + { + inputContainerElement = new VisualElement { name = "input-container" }; + mainContainer.parent.Add(inputContainerElement); + inputContainerElement.SendToBack(); + inputContainerElement.pickingMode = PickingMode.Ignore; + } + + protected virtual void DrawDefaultInspector(bool fromInspector = false) + { + var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + // Filter fields from the BaseNode type since we are only interested in user-defined fields + // (better than BindingFlags.DeclaredOnly because we keep any inherited user-defined fields) + .Where(f => f.DeclaringType != typeof(BaseNode)).ToList(); + + fields = nodeTarget.OverrideFieldOrder(fields).Reverse().ToList(); + + + for (int i = 0; i < fields.Count; i++) + { + FieldInfo field = fields[i]; + InputAttribute inputAttribute = field.GetCustomAttribute(); + bool hasInputAttribute = inputAttribute != null; + if (hasInputAttribute && portsPerFieldName.ContainsKey(field.Name)) + { + // IF FIELD IS MULTI_PORT DO THIS LOOP OVER THIS WITH RELATED PORTS AND IGNORE THE BASE PORT + foreach (var port in portsPerFieldName[field.Name]) + { + bool isProxied = !String.IsNullOrEmpty(port.portData.proxiedFieldPath); + string fieldPath = isProxied ? port.portData.proxiedFieldPath : port.fieldName; + DrawField(GetFieldInfoPath(fieldPath), fromInspector, isProxied); + } + } + else + { + DrawField(new List { field }, fromInspector); + } + } + } + + protected virtual void DrawField(List fieldInfoList, bool fromInspector, bool isProxied = false) + { + FieldInfo field = fieldInfoList.Last(); + string fieldPath = fieldInfoList.GetPath(); + + //skip if the field is a node setting + if (field.GetCustomAttribute(typeof(SettingAttribute)) != null) + { + hasSettings = true; + return; + } + + //skip if the field is not serializable + bool serializeField = field.GetCustomAttribute(typeof(SerializeField)) != null; + if ((!field.IsPublic && !serializeField) || field.IsNotSerialized) + { + AddEmptyField(field, fromInspector); + return; + } + + //skip if the field is an input/output and not marked as SerializedField + InputAttribute inputAttribute = field.GetCustomAttribute(); + bool hasInputAttribute = inputAttribute != null; + bool hasInputOrOutputAttribute = hasInputAttribute || field.GetCustomAttribute(typeof(OutputAttribute)) != null; + bool showAsDrawer = !fromInspector && hasInputAttribute && inputAttribute.showAsDrawer; + if ((!serializeField || isProxied) && hasInputOrOutputAttribute && !showAsDrawer) + { + Debug.Log("here: " + field.Name); + AddEmptyField(field, fromInspector); + return; + } + + //skip if marked with NonSerialized or HideInInspector + if (field.GetCustomAttribute(typeof(System.NonSerializedAttribute)) != null || field.GetCustomAttribute(typeof(HideInInspector)) != null) + { + AddEmptyField(field, fromInspector); + return; + } + + // Hide the field if we want to display in in the inspector + var showInInspector = field.GetCustomAttribute(); + if (!serializeField && showInInspector != null && !showInInspector.showInNode && !fromInspector) + { + AddEmptyField(field, fromInspector); + return; + } + + + var showInputDrawer = hasInputAttribute && field.GetCustomAttribute(typeof(SerializeField)) != null; + showInputDrawer |= hasInputAttribute && inputAttribute.showAsDrawer; + showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector + showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType); + + string displayName = ObjectNames.NicifyVariableName(field.Name); + + var inspectorNameAttribute = field.GetCustomAttribute(); + if (inspectorNameAttribute != null) + displayName = inspectorNameAttribute.displayName; + + var elem = AddControlField(fieldPath, displayName, showInputDrawer); + if (hasInputAttribute) + { + hideElementIfConnected[fieldPath] = elem; + + // Hide the field right away if there is already a connection: + if (portsPerFieldName.TryGetValue(fieldPath, out var pvs)) + if (pvs.Any(pv => pv.GetEdges().Count > 0)) + elem.style.display = DisplayStyle.None; + } + } + + private List GetFieldInfoPath(string path) + { + string[] pathArray = path.Split('.'); + List fieldInfoPath = new List(); + object value = nodeTarget; + for (int i = 0; i < pathArray.Length; i++) + { + // Debug.Log(pathArray[i]); + fieldInfoPath.Add(value.GetType().GetField(pathArray[i], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)); + if (i + 1 < pathArray.Length) + { + value = fieldInfoPath[i].GetValue(value); + } + } + return fieldInfoPath; + } + + protected virtual void SetNodeColor(Color color) + { + titleContainer.style.borderBottomColor = new StyleColor(color); + titleContainer.style.borderBottomWidth = new StyleFloat(color.a > 0 ? 5f : 0f); + } + + private void AddEmptyField(FieldInfo field, bool fromInspector) + { + if (field.GetCustomAttribute(typeof(InputAttribute)) == null || fromInspector) + return; + + if (field.GetCustomAttribute() != null) + return; + + var box = new VisualElement { name = field.Name }; + box.AddToClassList("port-input-element"); + box.AddToClassList("empty"); + inputContainerElement.Add(box); + } + + void UpdateFieldVisibility(string fieldName, object newValue) + { + if (newValue == null) + return; + if (visibleConditions.TryGetValue(fieldName, out var list)) + { + foreach (var elem in list) + { + if (newValue.Equals(elem.value)) + elem.target.style.display = DisplayStyle.Flex; + else + elem.target.style.display = DisplayStyle.None; + } + } + } + + void UpdateOtherFieldValueSpecific(FieldInfo field, object newValue) + { + foreach (var inputField in fieldControlsMap[field]) + { + var notify = inputField as INotifyValueChanged; + if (notify != null) + notify.SetValueWithoutNotify((T)newValue); + } + } + + static MethodInfo specificUpdateOtherFieldValue = typeof(BaseNodeView).GetMethod(nameof(UpdateOtherFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance); + void UpdateOtherFieldValue(FieldInfo info, object newValue) + { + // Warning: Keep in sync with FieldFactory CreateField + var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType; + var genericUpdate = specificUpdateOtherFieldValue.MakeGenericMethod(fieldType); + + genericUpdate.Invoke(this, new object[] { info, newValue }); + } + + object GetInputFieldValueSpecific(FieldInfo field) + { + if (fieldControlsMap.TryGetValue(field, out var list)) + { + foreach (var inputField in list) + { + if (inputField is INotifyValueChanged notify) + return notify.value; + } + } + return null; + } + + static MethodInfo specificGetValue = typeof(BaseNodeView).GetMethod(nameof(GetInputFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance); + object GetInputFieldValue(FieldInfo info) + { + // Warning: Keep in sync with FieldFactory CreateField + var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType; + var genericUpdate = specificGetValue.MakeGenericMethod(fieldType); + + return genericUpdate.Invoke(this, new object[] { info }); + } + + protected VisualElement AddControlField(string fieldPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) + { + List fieldInfoPath = GetFieldInfoPath(fieldPath); + return AddControlField(fieldInfoPath, label, showInputDrawer, valueChangedCallback); + } + Regex s_ReplaceNodeIndexPropertyPath = new Regex(@"(^nodes.Array.data\[)(\d+)(\])"); + internal void SyncSerializedPropertyPathes() + { + int nodeIndex = owner.graph.nodes.FindIndex(n => n == nodeTarget); + + // If the node is not found, then it means that it has been deleted from serialized data. + if (nodeIndex == -1) + return; + + var nodeIndexString = nodeIndex.ToString(); + foreach (var propertyField in this.Query().ToList()) + { + propertyField.Unbind(); + // The property path look like this: nodes.Array.data[x].fieldName + // And we want to update the value of x with the new node index: + propertyField.bindingPath = s_ReplaceNodeIndexPropertyPath.Replace(propertyField.bindingPath, m => m.Groups[1].Value + nodeIndexString + m.Groups[3].Value); + propertyField.Bind(owner.serializedGraph); + } + } + + protected SerializedProperty FindSerializedProperty(string fieldName) + { + int i = owner.graph.nodes.FindIndex(n => n == nodeTarget); + return owner.serializedGraph.FindProperty("nodes").GetArrayElementAtIndex(i).FindPropertyRelative(fieldName); + } + + protected VisualElement AddControlField(List fieldInfoPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) + { + var field = fieldInfoPath.Last(); // + + if (fieldInfoPath == null && fieldInfoPath.IsValid()) + return null; + + var element = new PropertyField(FindSerializedProperty(fieldInfoPath.GetPath()), showInputDrawer ? "" : label); + element.Bind(owner.serializedGraph); #if UNITY_2020_3 // In Unity 2020.3 the empty label on property field doesn't hide it, so we do it manually if ((showInputDrawer || String.IsNullOrEmpty(label)) && element != null) element.AddToClassList("DrawerField_2020_3"); #endif - if (typeof(IList).IsAssignableFrom(field.FieldType)) - EnableSyncSelectionBorderHeight(); - - element.RegisterValueChangeCallback(e => { - UpdateFieldVisibility(field.Name, field.GetValue(nodeTarget)); - valueChangedCallback?.Invoke(); - NotifyNodeChanged(); - }); - - // Disallow picking scene objects when the graph is not linked to a scene - if (element != null && !owner.graph.IsLinkedToScene()) - { - var objectField = element.Q(); - if (objectField != null) - objectField.allowSceneObjects = false; - } - - if (!fieldControlsMap.TryGetValue(field, out var inputFieldList)) - inputFieldList = fieldControlsMap[field] = new List(); - inputFieldList.Add(element); - - if(element != null) - { - if (showInputDrawer) - { - var box = new VisualElement {name = field.Name}; - box.AddToClassList("port-input-element"); - box.Add(element); - inputContainerElement.Add(box); - } - else - { - controlsContainer.Add(element); - } - element.name = field.Name; - } - else - { - // Make sure we create an empty placeholder if FieldFactory can not provide a drawer - if (showInputDrawer) AddEmptyField(field, false); - } - - var visibleCondition = field.GetCustomAttribute(typeof(VisibleIf)) as VisibleIf; - if (visibleCondition != null) - { - // Check if target field exists: - var conditionField = nodeTarget.GetType().GetField(visibleCondition.fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - if (conditionField == null) - Debug.LogError($"[VisibleIf] Field {visibleCondition.fieldName} does not exists in node {nodeTarget.GetType()}"); - else - { - visibleConditions.TryGetValue(visibleCondition.fieldName, out var list); - if (list == null) - list = visibleConditions[visibleCondition.fieldName] = new List<(object value, VisualElement target)>(); - list.Add((visibleCondition.value, element)); - UpdateFieldVisibility(visibleCondition.fieldName, conditionField.GetValue(nodeTarget)); - } - } - - return element; - } - - void UpdateFieldValues() - { - foreach (var kp in fieldControlsMap) - UpdateOtherFieldValue(kp.Key, kp.Key.GetValue(nodeTarget)); - } - - protected void AddSettingField(FieldInfo field) - { - if (field == null) - return; - - var label = field.GetCustomAttribute().name; - - var element = new PropertyField(FindSerializedProperty(field.Name)); - element.Bind(owner.serializedGraph); - - if (element != null) - { - settingsContainer.Add(element); - element.name = field.Name; - } - } - - internal void OnPortConnected(PortView port) - { - if(port.direction == Direction.Input && inputContainerElement?.Q(port.fieldName) != null) - inputContainerElement.Q(port.fieldName).AddToClassList("empty"); - - if (hideElementIfConnected.TryGetValue(port.fieldName, out var elem)) - elem.style.display = DisplayStyle.None; - - onPortConnected?.Invoke(port); - } - - internal void OnPortDisconnected(PortView port) - { - if (port.direction == Direction.Input && inputContainerElement?.Q(port.fieldName) != null) - { - inputContainerElement.Q(port.fieldName).RemoveFromClassList("empty"); - - if (nodeTarget.nodeFields.TryGetValue(port.fieldName, out var fieldInfo)) - { - var valueBeforeConnection = GetInputFieldValue(fieldInfo.info); - - if (valueBeforeConnection != null) - { - fieldInfo.info.SetValue(nodeTarget, valueBeforeConnection); - } - } - } - - if (hideElementIfConnected.TryGetValue(port.fieldName, out var elem)) - elem.style.display = DisplayStyle.Flex; - - onPortDisconnected?.Invoke(port); - } - - // TODO: a function to force to reload the custom behavior ports (if we want to do a button to add ports for example) - - public virtual void OnRemoved() {} - public virtual void OnCreated() {} - - public override void SetPosition(Rect newPos) - { + if (typeof(IList).IsAssignableFrom(field.FieldType)) + EnableSyncSelectionBorderHeight(); + + element.RegisterValueChangeCallback(e => + { + UpdateFieldVisibility(field.Name, fieldInfoPath.GetFinalValue(nodeTarget)); + valueChangedCallback?.Invoke(); + NotifyNodeChanged(); + }); + + // Disallow picking scene objects when the graph is not linked to a scene + if (element != null && !owner.graph.IsLinkedToScene()) + { + var objectField = element.Q(); + if (objectField != null) + objectField.allowSceneObjects = false; + } + + if (!fieldControlsMap.TryGetValue(field, out var inputFieldList)) + inputFieldList = fieldControlsMap[field] = new List(); + inputFieldList.Add(element); + + if (element != null) + { + if (showInputDrawer) + { + var box = new VisualElement { name = field.Name }; + box.AddToClassList("port-input-element"); + box.Add(element); + inputContainerElement.Add(box); + } + else + { + controlsContainer.Add(element); + } + element.name = field.Name; + } + else + { + // Make sure we create an empty placeholder if FieldFactory can not provide a drawer + if (showInputDrawer) AddEmptyField(field, false); + } + + var visibleCondition = field.GetCustomAttribute(typeof(VisibleIf)) as VisibleIf; + if (visibleCondition != null) + { + // Check if target field exists: + var conditionField = nodeTarget.GetType().GetField(visibleCondition.fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (conditionField == null) + Debug.LogError($"[VisibleIf] Field {visibleCondition.fieldName} does not exists in node {nodeTarget.GetType()}"); + else + { + visibleConditions.TryGetValue(visibleCondition.fieldName, out var list); + if (list == null) + list = visibleConditions[visibleCondition.fieldName] = new List<(object value, VisualElement target)>(); + list.Add((visibleCondition.value, element)); + UpdateFieldVisibility(visibleCondition.fieldName, conditionField.GetValue(nodeTarget)); + } + } + + return element; + } + + void UpdateFieldValues() + { + foreach (var kp in fieldControlsMap) + UpdateOtherFieldValue(kp.Key, kp.Key.GetValue(nodeTarget)); + } + + protected void AddSettingField(FieldInfo field) + { + if (field == null) + return; + + var label = field.GetCustomAttribute().name; + + var element = new PropertyField(FindSerializedProperty(field.Name)); + element.Bind(owner.serializedGraph); + + if (element != null) + { + settingsContainer.Add(element); + element.name = field.Name; + } + } + + internal void OnPortConnected(PortView port) + { + string fieldName = !String.IsNullOrEmpty(port.portData.proxiedFieldPath) ? port.portData.proxiedFieldPath : port.fieldName; + + if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null) + inputContainerElement.Q(fieldName).AddToClassList("empty"); + + if (hideElementIfConnected.TryGetValue(fieldName, out var elem)) + elem.style.display = DisplayStyle.None; + + onPortConnected?.Invoke(port); + } + + internal void OnPortDisconnected(PortView port) // + { + string fieldName = !String.IsNullOrEmpty(port.portData.proxiedFieldPath) ? port.portData.proxiedFieldPath : port.fieldName; + + if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null) + { + inputContainerElement.Q(fieldName).RemoveFromClassList("empty"); + + if (nodeTarget.nodeFields.TryGetValue(fieldName, out var fieldInfo)) + { + var valueBeforeConnection = GetInputFieldValue(fieldInfo.info); + + if (valueBeforeConnection != null) + { + fieldInfo.info.SetValue(nodeTarget, valueBeforeConnection); + } + } + } + + if (hideElementIfConnected.TryGetValue(fieldName, out var elem)) + elem.style.display = DisplayStyle.Flex; + + onPortDisconnected?.Invoke(port); + } + + // TODO: a function to force to reload the custom behavior ports (if we want to do a button to add ports for example) + + public virtual void OnRemoved() { } + public virtual void OnCreated() { } + + public override void SetPosition(Rect newPos) + { if (initializing || !nodeTarget.isLocked) { base.SetPosition(newPos); - if (!initializing) - owner.RegisterCompleteObjectUndo("Moved graph node"); + if (!initializing) + owner.RegisterCompleteObjectUndo("Moved graph node"); nodeTarget.position = newPos; initializing = false; } - } - - public override bool expanded - { - get { return base.expanded; } - set - { - base.expanded = value; - nodeTarget.expanded = value; - } - } + } + + public override bool expanded + { + get { return base.expanded; } + set + { + base.expanded = value; + nodeTarget.expanded = value; + } + } public void ChangeLockStatus() { @@ -1016,26 +1078,26 @@ public void ChangeLockStatus() } public override void BuildContextualMenu(ContextualMenuPopulateEvent evt) - { - BuildAlignMenu(evt); - evt.menu.AppendAction("Open Node Script", (e) => OpenNodeScript(), OpenNodeScriptStatus); - evt.menu.AppendAction("Open Node View Script", (e) => OpenNodeViewScript(), OpenNodeViewScriptStatus); - evt.menu.AppendAction("Debug", (e) => ToggleDebug(), DebugStatus); + { + BuildAlignMenu(evt); + evt.menu.AppendAction("Open Node Script", (e) => OpenNodeScript(), OpenNodeScriptStatus); + evt.menu.AppendAction("Open Node View Script", (e) => OpenNodeViewScript(), OpenNodeViewScriptStatus); + evt.menu.AppendAction("Debug", (e) => ToggleDebug(), DebugStatus); if (nodeTarget.unlockable) evt.menu.AppendAction((nodeTarget.isLocked ? "Unlock" : "Lock"), (e) => ChangeLockStatus(), LockStatus); } - protected void BuildAlignMenu(ContextualMenuPopulateEvent evt) - { - evt.menu.AppendAction("Align/To Left", (e) => AlignToLeft()); - evt.menu.AppendAction("Align/To Center", (e) => AlignToCenter()); - evt.menu.AppendAction("Align/To Right", (e) => AlignToRight()); - evt.menu.AppendSeparator("Align/"); - evt.menu.AppendAction("Align/To Top", (e) => AlignToTop()); - evt.menu.AppendAction("Align/To Middle", (e) => AlignToMiddle()); - evt.menu.AppendAction("Align/To Bottom", (e) => AlignToBottom()); - evt.menu.AppendSeparator(); - } + protected void BuildAlignMenu(ContextualMenuPopulateEvent evt) + { + evt.menu.AppendAction("Align/To Left", (e) => AlignToLeft()); + evt.menu.AppendAction("Align/To Center", (e) => AlignToCenter()); + evt.menu.AppendAction("Align/To Right", (e) => AlignToRight()); + evt.menu.AppendSeparator("Align/"); + evt.menu.AppendAction("Align/To Top", (e) => AlignToTop()); + evt.menu.AppendAction("Align/To Middle", (e) => AlignToMiddle()); + evt.menu.AppendAction("Align/To Bottom", (e) => AlignToBottom()); + evt.menu.AppendSeparator(); + } Status LockStatus(DropdownMenuAction action) { @@ -1043,137 +1105,179 @@ Status LockStatus(DropdownMenuAction action) } Status DebugStatus(DropdownMenuAction action) - { - if (nodeTarget.debug) - return Status.Checked; - return Status.Normal; - } - - Status OpenNodeScriptStatus(DropdownMenuAction action) - { - if (NodeProvider.GetNodeScript(nodeTarget.GetType()) != null) - return Status.Normal; - return Status.Disabled; - } - - Status OpenNodeViewScriptStatus(DropdownMenuAction action) - { - if (NodeProvider.GetNodeViewScript(GetType()) != null) - return Status.Normal; - return Status.Disabled; - } - - IEnumerable< PortView > SyncPortCounts(IEnumerable< NodePort > ports, IEnumerable< PortView > portViews) - { - var listener = owner.connectorListener; - var portViewList = portViews.ToList(); - - // Maybe not good to remove ports as edges are still connected :/ - foreach (var pv in portViews.ToList()) - { - // If the port have disappeared from the node data, we remove the view: - // We can use the identifier here because this function will only be called when there is a custom port behavior - if (!ports.Any(p => p.portData.identifier == pv.portData.identifier)) - { - RemovePort(pv); - portViewList.Remove(pv); - } - } - - foreach (var p in ports) - { - // Add missing port views - if (!portViews.Any(pv => p.portData.identifier == pv.portData.identifier)) - { - Direction portDirection = nodeTarget.IsFieldInput(p.fieldName) ? Direction.Input : Direction.Output; - var pv = AddPort(p.fieldInfo, portDirection, listener, p.portData); - portViewList.Add(pv); - } - } - - return portViewList; - } - - void SyncPortOrder(IEnumerable< NodePort > ports, IEnumerable< PortView > portViews) - { - var portViewList = portViews.ToList(); - var portsList = ports.ToList(); - - // Re-order the port views to match the ports order in case a custom behavior re-ordered the ports - for (int i = 0; i < portsList.Count; i++) - { - var id = portsList[i].portData.identifier; - - var pv = portViewList.FirstOrDefault(p => p.portData.identifier == id); - if (pv != null) - InsertPort(pv, i); - } - } - - public virtual new bool RefreshPorts() - { - // If a port behavior was attached to one port, then - // the port count might have been updated by the node - // so we have to refresh the list of port views. - UpdatePortViewWithPorts(nodeTarget.inputPorts, inputPortViews); - UpdatePortViewWithPorts(nodeTarget.outputPorts, outputPortViews); - - void UpdatePortViewWithPorts(NodePortContainer ports, List< PortView > portViews) - { - if (ports.Count == 0 && portViews.Count == 0) // Nothing to update - return; - - // When there is no current portviews, we can't zip the list so we just add all - if (portViews.Count == 0) - SyncPortCounts(ports, new PortView[]{}); - else if (ports.Count == 0) // Same when there is no ports - SyncPortCounts(new NodePort[]{}, portViews); - else if (portViews.Count != ports.Count) - SyncPortCounts(ports, portViews); - else - { - var p = ports.GroupBy(n => n.fieldName); - var pv = portViews.GroupBy(v => v.fieldName); - p.Zip(pv, (portPerFieldName, portViewPerFieldName) => { - IEnumerable< PortView > portViewsList = portViewPerFieldName; - if (portPerFieldName.Count() != portViewPerFieldName.Count()) - portViewsList = SyncPortCounts(portPerFieldName, portViewPerFieldName); - SyncPortOrder(portPerFieldName, portViewsList); - // We don't care about the result, we just iterate over port and portView - return ""; - }).ToList(); - } - - // Here we're sure that we have the same amount of port and portView - // so we can update the view with the new port data (if the name of a port have been changed for example) - - for (int i = 0; i < portViews.Count; i++) - portViews[i].UpdatePortView(ports[i].portData); - } - - return base.RefreshPorts(); - } - - public void ForceUpdatePorts() - { - nodeTarget.UpdateAllPorts(); - - RefreshPorts(); - } - - void UpdatePortsForField(string fieldName) - { - // TODO: actual code - RefreshPorts(); - } - - protected virtual VisualElement CreateSettingsView() => new Label("Settings") {name = "header"}; - - /// - /// Send an event to the graph telling that the content of this node have changed - /// - public void NotifyNodeChanged() => owner.graph.NotifyNodeChanged(nodeTarget); - - #endregion + { + if (nodeTarget.debug) + return Status.Checked; + return Status.Normal; + } + + Status OpenNodeScriptStatus(DropdownMenuAction action) + { + if (NodeProvider.GetNodeScript(nodeTarget.GetType()) != null) + return Status.Normal; + return Status.Disabled; + } + + Status OpenNodeViewScriptStatus(DropdownMenuAction action) + { + if (NodeProvider.GetNodeViewScript(GetType()) != null) + return Status.Normal; + return Status.Disabled; + } + + IEnumerable SyncPortCounts(IEnumerable ports, IEnumerable portViews) + { + var listener = owner.connectorListener; + var portViewList = portViews.ToList(); + + // Maybe not good to remove ports as edges are still connected :/ + foreach (var pv in portViews.ToList()) + { + // If the port have disappeared from the node data, we remove the view: + // We can use the identifier here because this function will only be called when there is a custom port behavior + if (!ports.Any(p => p.portData.identifier == pv.portData.identifier)) + { + RemovePort(pv); + portViewList.Remove(pv); + } + } + + foreach (var p in ports) + { + // Add missing port views + if (!portViews.Any(pv => p.portData.identifier == pv.portData.identifier)) + { + Direction portDirection = nodeTarget.IsFieldInput(p.fieldName) ? Direction.Input : Direction.Output; + var pv = AddPort(p.fieldInfo, portDirection, listener, p.portData); + portViewList.Add(pv); + } + } + + return portViewList; + } + + void SyncPortOrder(IEnumerable ports, IEnumerable portViews) + { + var portViewList = portViews.ToList(); + var portsList = ports.ToList(); + + // Re-order the port views to match the ports order in case a custom behavior re-ordered the ports + for (int i = 0; i < portsList.Count; i++) + { + var id = portsList[i].portData.identifier; + + var pv = portViewList.FirstOrDefault(p => p.portData.identifier == id); + if (pv != null) + InsertPort(pv, i); + } + } + + public virtual new bool RefreshPorts() + { + // If a port behavior was attached to one port, then + // the port count might have been updated by the node + // so we have to refresh the list of port views. + UpdatePortViewWithPorts(nodeTarget.inputPorts, inputPortViews); + UpdatePortViewWithPorts(nodeTarget.outputPorts, outputPortViews); + + void UpdatePortViewWithPorts(NodePortContainer ports, List portViews) + { + if (ports.Count == 0 && portViews.Count == 0) // Nothing to update + return; + + // When there is no current portviews, we can't zip the list so we just add all + if (portViews.Count == 0) + SyncPortCounts(ports, new PortView[] { }); + else if (ports.Count == 0) // Same when there is no ports + SyncPortCounts(new NodePort[] { }, portViews); + else if (portViews.Count != ports.Count) + SyncPortCounts(ports, portViews); + else + { + var p = ports.GroupBy(n => n.fieldName); + var pv = portViews.GroupBy(v => v.fieldName); + p.Zip(pv, (portPerFieldName, portViewPerFieldName) => + { + IEnumerable portViewsList = portViewPerFieldName; + if (portPerFieldName.Count() != portViewPerFieldName.Count()) + portViewsList = SyncPortCounts(portPerFieldName, portViewPerFieldName); + SyncPortOrder(portPerFieldName, portViewsList); + // We don't care about the result, we just iterate over port and portView + return ""; + }).ToList(); + } + + // Here we're sure that we have the same amount of port and portView + // so we can update the view with the new port data (if the name of a port have been changed for example) + + for (int i = 0; i < portViews.Count; i++) + portViews[i].UpdatePortView(ports[i].portData); + } + + return base.RefreshPorts(); + } + + public void ForceUpdatePorts() + { + nodeTarget.UpdateAllPorts(); + + RefreshPorts(); + } + + void UpdatePortsForField(string fieldName) + { + // TODO: actual code + RefreshPorts(); + } + + protected virtual VisualElement CreateSettingsView() => new Label("Settings") { name = "header" }; + + /// + /// Send an event to the graph telling that the content of this node have changed + /// + public void NotifyNodeChanged() => owner.graph.NotifyNodeChanged(nodeTarget); + + #endregion + } + + public static class ListHelpers + { + public static object GetValueAt(this IList list, object startingValue, int index) + { + object currentValue = startingValue; + for (int i = 0; i < list.Count; i++) + { + currentValue = list[i].GetValue(currentValue); + if (i == index) break; + } + return currentValue; + } + + public static object GetFinalValue(this IList list, object startingValue) + { + object currentValue = startingValue; + for (int i = 0; i < list.Count; i++) + { + currentValue = list[i].GetValue(currentValue); + } + return currentValue; + + } + + public static string GetPath(this IList list) + { + string path = ""; + for (int i = 0; i < list.Count; i++) + { + if (i > 0) path += "."; + path += list[i].Name; + } + return path; + } + + public static bool IsValid(this IList list) + { + return list.Any(x => x == null); + } } } \ No newline at end of file diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs index 293dac83..a714699f 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/PortView.cs @@ -8,172 +8,173 @@ namespace GraphProcessor { - public class PortView : Port - { - public string fieldName => fieldInfo.Name; - public Type fieldType => fieldInfo.FieldType; - public new Type portType; - public BaseNodeView owner { get; private set; } - public PortData portData; + public class PortView : Port + { + public string fieldName => fieldInfo.Name; + public Type fieldType => fieldInfo.FieldType; + public new Type portType; + public BaseNodeView owner { get; private set; } + public PortData portData; - public event Action< PortView, Edge > OnConnected; - public event Action< PortView, Edge > OnDisconnected; + public event Action OnConnected; + public event Action OnDisconnected; - protected FieldInfo fieldInfo; - protected BaseEdgeConnectorListener listener; + protected FieldInfo fieldInfo; + protected BaseEdgeConnectorListener listener; - string userPortStyleFile = "PortViewTypes"; + string userPortStyleFile = "PortViewTypes"; - List< EdgeView > edges = new List< EdgeView >(); + List edges = new List(); - public int connectionCount => edges.Count; + public int connectionCount => edges.Count; - readonly string portStyle = "GraphProcessorStyles/PortView"; + readonly string portStyle = "GraphProcessorStyles/PortView"; protected PortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener edgeConnectorListener) : base(portData.vertical ? Orientation.Vertical : Orientation.Horizontal, direction, Capacity.Multi, portData.displayType ?? fieldInfo.FieldType) - { - this.fieldInfo = fieldInfo; - this.listener = edgeConnectorListener; - this.portType = portData.displayType ?? fieldInfo.FieldType; - this.portData = portData; - this.portName = fieldName; - - styleSheets.Add(Resources.Load(portStyle)); - - UpdatePortSize(); - - var userPortStyle = Resources.Load(userPortStyleFile); - if (userPortStyle != null) - styleSheets.Add(userPortStyle); - - if (portData.vertical) - AddToClassList("Vertical"); - - this.tooltip = portData.tooltip; - } - - public static PortView CreatePortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener edgeConnectorListener) - { - var pv = new PortView(direction, fieldInfo, portData, edgeConnectorListener); - pv.m_EdgeConnector = new BaseEdgeConnector(edgeConnectorListener); - pv.AddManipulator(pv.m_EdgeConnector); - - // Force picking in the port label to enlarge the edge creation zone - var portLabel = pv.Q("type"); - if (portLabel != null) - { - portLabel.pickingMode = PickingMode.Position; - portLabel.style.flexGrow = 1; - } - - // hide label when the port is vertical - if (portData.vertical && portLabel != null) - portLabel.style.display = DisplayStyle.None; - - // Fixup picking mode for vertical top ports - if (portData.vertical) - pv.Q("connector").pickingMode = PickingMode.Position; - - return pv; - } - - /// - /// Update the size of the port view (using the portData.sizeInPixel property) - /// - public void UpdatePortSize() - { - int size = portData.sizeInPixel == 0 ? 8 : portData.sizeInPixel; - var connector = this.Q("connector"); - var cap = connector.Q("cap"); - connector.style.width = size; - connector.style.height = size; - cap.style.width = size - 4; - cap.style.height = size - 4; - - // Update connected edge sizes: - edges.ForEach(e => e.UpdateEdgeSize()); - } - - public virtual void Initialize(BaseNodeView nodeView, string name) - { - this.owner = nodeView; - AddToClassList(fieldName); - - // Correct port type if port accept multiple values (and so is a container) - if (direction == Direction.Input && portData.acceptMultipleEdges && portType == fieldType) // If the user haven't set a custom field type - { - if (fieldType.GetGenericArguments().Length > 0) - portType = fieldType.GetGenericArguments()[0]; - } - - if (name != null) - portName = name; - visualClass = "Port_" + portType.Name; - tooltip = portData.tooltip; - } - - public override void Connect(Edge edge) - { - OnConnected?.Invoke(this, edge); - - base.Connect(edge); - - var inputNode = (edge.input as PortView).owner; - var outputNode = (edge.output as PortView).owner; - - edges.Add(edge as EdgeView); - - inputNode.OnPortConnected(edge.input as PortView); - outputNode.OnPortConnected(edge.output as PortView); - } - - public override void Disconnect(Edge edge) - { - OnDisconnected?.Invoke(this, edge); - - base.Disconnect(edge); - - if (!(edge as EdgeView).isConnected) - return ; - - var inputNode = (edge.input as PortView)?.owner; - var outputNode = (edge.output as PortView)?.owner; - - inputNode?.OnPortDisconnected(edge.input as PortView); - outputNode?.OnPortDisconnected(edge.output as PortView); - - edges.Remove(edge as EdgeView); - } - - public void UpdatePortView(PortData data) - { - if (data.displayType != null) - { - base.portType = data.displayType; - portType = data.displayType; - visualClass = "Port_" + portType.Name; - } - if (!String.IsNullOrEmpty(data.displayName)) - base.portName = data.displayName; - - portData = data; - - // Update the edge in case the port color have changed - schedule.Execute(() => { - foreach (var edge in edges) - { - edge.UpdateEdgeControl(); - edge.MarkDirtyRepaint(); - } - }).ExecuteLater(50); // Hummm - - UpdatePortSize(); - } - - public List< EdgeView > GetEdges() - { - return edges; - } - } + { + this.fieldInfo = fieldInfo; + this.listener = edgeConnectorListener; + this.portType = portData.displayType ?? fieldInfo.FieldType; + this.portData = portData; + this.portName = fieldName; + + styleSheets.Add(Resources.Load(portStyle)); + + UpdatePortSize(); + + var userPortStyle = Resources.Load(userPortStyleFile); + if (userPortStyle != null) + styleSheets.Add(userPortStyle); + + if (portData.vertical) + AddToClassList("Vertical"); + + this.tooltip = portData.tooltip; + } + + public static PortView CreatePortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener edgeConnectorListener) + { + var pv = new PortView(direction, fieldInfo, portData, edgeConnectorListener); + pv.m_EdgeConnector = new BaseEdgeConnector(edgeConnectorListener); + pv.AddManipulator(pv.m_EdgeConnector); + + // Force picking in the port label to enlarge the edge creation zone + var portLabel = pv.Q("type"); + if (portLabel != null) + { + portLabel.pickingMode = PickingMode.Position; + portLabel.style.flexGrow = 1; + } + + // hide label when the port is vertical + if (portData.vertical && portLabel != null) + portLabel.style.display = DisplayStyle.None; + + // Fixup picking mode for vertical top ports + if (portData.vertical) + pv.Q("connector").pickingMode = PickingMode.Position; + + return pv; + } + + /// + /// Update the size of the port view (using the portData.sizeInPixel property) + /// + public void UpdatePortSize() + { + int size = portData.sizeInPixel == 0 ? 8 : portData.sizeInPixel; + var connector = this.Q("connector"); + var cap = connector.Q("cap"); + connector.style.width = size; + connector.style.height = size; + cap.style.width = size - 4; + cap.style.height = size - 4; + + // Update connected edge sizes: + edges.ForEach(e => e.UpdateEdgeSize()); + } + + public virtual void Initialize(BaseNodeView nodeView, string name) + { + this.owner = nodeView; + AddToClassList(fieldName); + + // Correct port type if port accept multiple values (and so is a container) + if (direction == Direction.Input && portData.acceptMultipleEdges && portType == fieldType) // If the user haven't set a custom field type + { + if (fieldType.GetGenericArguments().Length > 0) + portType = fieldType.GetGenericArguments()[0]; + } + + if (name != null) + portName = name; + visualClass = "Port_" + portType.Name; + tooltip = portData.tooltip; + } + + public override void Connect(Edge edge) + { + OnConnected?.Invoke(this, edge); + + base.Connect(edge); + + var inputNode = (edge.input as PortView).owner; + var outputNode = (edge.output as PortView).owner; + + edges.Add(edge as EdgeView); + + inputNode.OnPortConnected(edge.input as PortView); + outputNode.OnPortConnected(edge.output as PortView); + } + + public override void Disconnect(Edge edge) + { + OnDisconnected?.Invoke(this, edge); + + base.Disconnect(edge); + + if (!(edge as EdgeView).isConnected) + return; + + var inputNode = (edge.input as PortView)?.owner; + var outputNode = (edge.output as PortView)?.owner; + + inputNode?.OnPortDisconnected(edge.input as PortView); + outputNode?.OnPortDisconnected(edge.output as PortView); + + edges.Remove(edge as EdgeView); + } + + public void UpdatePortView(PortData data) + { + if (data.displayType != null) + { + base.portType = data.displayType; + portType = data.displayType; + visualClass = "Port_" + portType.Name; + } + if (!String.IsNullOrEmpty(data.displayName)) + base.portName = data.displayName; + + portData = data; + + // Update the edge in case the port color have changed + schedule.Execute(() => + { + foreach (var edge in edges) + { + edge.UpdateEdgeControl(); + edge.MarkDirtyRepaint(); + } + }).ExecuteLater(50); // Hummm + + UpdatePortSize(); + } + + public List GetEdges() + { + return edges; + } + } } \ No newline at end of file diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs index 0338c14b..833504c1 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/BaseNode.cs @@ -8,858 +8,882 @@ namespace GraphProcessor { - public delegate IEnumerable< PortData > CustomPortBehaviorDelegate(List< SerializableEdge > edges); - public delegate IEnumerable< PortData > CustomPortTypeBehaviorDelegate(string fieldName, string displayName, object value); - - [Serializable] - public abstract class BaseNode - { - [SerializeField] - internal string nodeCustomName = null; // The name of the node in case it was renamed by a user - - /// - /// Name of the node, it will be displayed in the title section - /// - /// - public virtual string name => GetType().Name; - - /// - /// The accent color of the node - /// - public virtual Color color => Color.clear; - - /// - /// Set a custom uss file for the node. We use a Resources.Load to get the stylesheet so be sure to put the correct resources path - /// https://docs.unity3d.com/ScriptReference/Resources.Load.html - /// - public virtual string layoutStyle => string.Empty; - - /// - /// If the node can be locked or not - /// - public virtual bool unlockable => true; - - /// - /// Is the node is locked (if locked it can't be moved) - /// - public virtual bool isLocked => nodeLock; + public delegate IEnumerable CustomPortBehaviorDelegate(List edges); + public delegate IEnumerable CustomPortTypeBehaviorDelegate(string fieldName, string displayName, object value); + + [Serializable] + public abstract class BaseNode + { + [SerializeField] + internal string nodeCustomName = null; // The name of the node in case it was renamed by a user + + /// + /// Name of the node, it will be displayed in the title section + /// + /// + public virtual string name => GetType().Name; + + /// + /// The accent color of the node + /// + public virtual Color color => Color.clear; + + /// + /// Set a custom uss file for the node. We use a Resources.Load to get the stylesheet so be sure to put the correct resources path + /// https://docs.unity3d.com/ScriptReference/Resources.Load.html + /// + public virtual string layoutStyle => string.Empty; + + /// + /// If the node can be locked or not + /// + public virtual bool unlockable => true; + + /// + /// Is the node is locked (if locked it can't be moved) + /// + public virtual bool isLocked => nodeLock; //id - public string GUID; - - public int computeOrder = -1; - - /// Tell wether or not the node can be processed. Do not check anything from inputs because this step happens before inputs are sent to the node - public virtual bool canProcess => true; - - /// Show the node controlContainer only when the mouse is over the node - public virtual bool showControlsOnHover => false; - - /// True if the node can be deleted, false otherwise - public virtual bool deletable => true; - - /// - /// Container of input ports - /// - [NonSerialized] - public readonly NodeInputPortContainer inputPorts; - /// - /// Container of output ports - /// - [NonSerialized] - public readonly NodeOutputPortContainer outputPorts; - - //Node view datas - public Rect position; - /// - /// Is the node expanded - /// - public bool expanded; - /// - /// Is debug visible - /// - public bool debug; - /// - /// Node locked state - /// - public bool nodeLock; - - public delegate void ProcessDelegate(); - - /// - /// Triggered when the node is processes - /// - public event ProcessDelegate onProcessed; - public event Action< string, NodeMessageType > onMessageAdded; - public event Action< string > onMessageRemoved; - /// - /// Triggered after an edge was connected on the node - /// - public event Action< SerializableEdge > onAfterEdgeConnected; - /// - /// Triggered after an edge was disconnected on the node - /// - public event Action< SerializableEdge > onAfterEdgeDisconnected; - - /// - /// Triggered after a single/list of port(s) is updated, the parameter is the field name - /// - public event Action< string > onPortsUpdated; - - [NonSerialized] - bool _needsInspector = false; - - /// - /// Does the node needs to be visible in the inspector (when selected). - /// - public virtual bool needsInspector => _needsInspector; - - /// - /// Can the node be renamed in the UI. By default a node can be renamed by double clicking it's name. - /// - public virtual bool isRenamable => false; - - /// - /// Is the node created from a duplicate operation (either ctrl-D or copy/paste). - /// - public bool createdFromDuplication {get; internal set; } = false; - - /// - /// True only when the node was created from a duplicate operation and is inside a group that was also duplicated at the same time. - /// - public bool createdWithinGroup {get; internal set; } = false; - - [NonSerialized] - internal Dictionary< string, NodeFieldInformation > nodeFields = new Dictionary< string, NodeFieldInformation >(); - - [NonSerialized] - internal Dictionary< Type, CustomPortTypeBehaviorDelegate> customPortTypeBehaviorMap = new Dictionary(); - - [NonSerialized] - List< string > messages = new List(); - - [NonSerialized] - protected BaseGraph graph; - - internal class NodeFieldInformation - { - public string name; - public string fieldName; - public FieldInfo info; - public bool input; - public bool isMultiple; - public string tooltip; - public CustomPortBehaviorDelegate behavior; - public bool vertical; - - public NodeFieldInformation(FieldInfo info, string name, bool input, bool isMultiple, string tooltip, bool vertical, CustomPortBehaviorDelegate behavior) - { - this.input = input; - this.isMultiple = isMultiple; - this.info = info; - this.name = name; - this.fieldName = info.Name; - this.behavior = behavior; - this.tooltip = tooltip; - this.vertical = vertical; - } - } - - struct PortUpdate - { - public List fieldNames; - public BaseNode node; - - public void Deconstruct(out List fieldNames, out BaseNode node) - { - fieldNames = this.fieldNames; - node = this.node; - } - } - - // Used in port update algorithm - Stack fieldsToUpdate = new Stack(); - HashSet updatedFields = new HashSet(); - - /// - /// Creates a node of type T at a certain position - /// - /// position in the graph in pixels - /// type of the node - /// the node instance - public static T CreateFromType< T >(Vector2 position) where T : BaseNode - { - return CreateFromType(typeof(T), position) as T; - } - - /// - /// Creates a node of type nodeType at a certain position - /// - /// position in the graph in pixels - /// type of the node - /// the node instance - public static BaseNode CreateFromType(Type nodeType, Vector2 position) - { - if (!nodeType.IsSubclassOf(typeof(BaseNode))) - return null; - - var node = Activator.CreateInstance(nodeType) as BaseNode; - - node.position = new Rect(position, new Vector2(100, 100)); - - ExceptionToLog.Call(() => node.OnNodeCreated()); - - return node; - } - - #region Initialization - - // called by the BaseGraph when the node is added to the graph - public void Initialize(BaseGraph graph) - { - this.graph = graph; - - ExceptionToLog.Call(() => Enable()); - - InitializePorts(); - } - - void InitializeCustomPortTypeMethods() - { - MethodInfo[] methods = new MethodInfo[0]; - Type baseType = GetType(); - while (true) - { - methods = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance); - foreach (var method in methods) - { - var typeBehaviors = method.GetCustomAttributes().ToArray(); - - if (typeBehaviors.Length == 0) - continue; - - CustomPortTypeBehaviorDelegate deleg = null; - try - { - deleg = Delegate.CreateDelegate(typeof(CustomPortTypeBehaviorDelegate), this, method) as CustomPortTypeBehaviorDelegate; - } catch (Exception e) - { - Debug.LogError(e); - Debug.LogError($"Cannot convert method {method} to a delegate of type {typeof(CustomPortTypeBehaviorDelegate)}"); - } - - foreach (var typeBehavior in typeBehaviors) - customPortTypeBehaviorMap[typeBehavior.type] = deleg; - } - - // Try to also find private methods in the base class - baseType = baseType.BaseType; - if (baseType == null) - break; - } - } - - /// - /// Use this function to initialize anything related to ports generation in your node - /// This will allow the node creation menu to correctly recognize ports that can be connected between nodes - /// - public virtual void InitializePorts() - { - InitializeCustomPortTypeMethods(); - - foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info))) - { - var nodeField = nodeFields[key.Name]; - - if (HasCustomBehavior(nodeField)) - { - UpdatePortsForField(nodeField.fieldName, sendPortUpdatedEvent: false); - } - else - { - // If we don't have a custom behavior on the node, we just have to create a simple port - AddPort(nodeField.input, nodeField.fieldName, new PortData { acceptMultipleEdges = nodeField.isMultiple, displayName = nodeField.name, tooltip = nodeField.tooltip, vertical = nodeField.vertical }); - } - } - } - - /// - /// Override the field order inside the node. It allows to re-order all the ports and field in the UI. - /// - /// List of fields to sort - /// Sorted list of fields - public virtual IEnumerable OverrideFieldOrder(IEnumerable fields) - { - long GetFieldInheritanceLevel(FieldInfo f) - { - int level = 0; - var t = f.DeclaringType; - while (t != null) - { - t = t.BaseType; - level++; - } - - return level; - } - - // Order by MetadataToken and inheritance level to sync the order with the port order (make sure FieldDrawers are next to the correct port) - return fields.OrderByDescending(f => (long)(((GetFieldInheritanceLevel(f) << 32)) | (long)f.MetadataToken)); - } - - protected BaseNode() - { + public string GUID; + + public int computeOrder = -1; + + /// Tell wether or not the node can be processed. Do not check anything from inputs because this step happens before inputs are sent to the node + public virtual bool canProcess => true; + + /// Show the node controlContainer only when the mouse is over the node + public virtual bool showControlsOnHover => false; + + /// True if the node can be deleted, false otherwise + public virtual bool deletable => true; + + /// + /// Container of input ports + /// + [NonSerialized] + public readonly NodeInputPortContainer inputPorts; + /// + /// Container of output ports + /// + [NonSerialized] + public readonly NodeOutputPortContainer outputPorts; + + //Node view datas + public Rect position; + /// + /// Is the node expanded + /// + public bool expanded; + /// + /// Is debug visible + /// + public bool debug; + /// + /// Node locked state + /// + public bool nodeLock; + + public delegate void ProcessDelegate(); + + /// + /// Triggered when the node is processes + /// + public event ProcessDelegate onProcessed; + public event Action onMessageAdded; + public event Action onMessageRemoved; + /// + /// Triggered after an edge was connected on the node + /// + public event Action onAfterEdgeConnected; + /// + /// Triggered after an edge was disconnected on the node + /// + public event Action onAfterEdgeDisconnected; + + /// + /// Triggered after a single/list of port(s) is updated, the parameter is the field name + /// + public event Action onPortsUpdated; + + [NonSerialized] + bool _needsInspector = false; + + /// + /// Does the node needs to be visible in the inspector (when selected). + /// + public virtual bool needsInspector => _needsInspector; + + /// + /// Can the node be renamed in the UI. By default a node can be renamed by double clicking it's name. + /// + public virtual bool isRenamable => false; + + /// + /// Is the node created from a duplicate operation (either ctrl-D or copy/paste). + /// + public bool createdFromDuplication { get; internal set; } = false; + + /// + /// True only when the node was created from a duplicate operation and is inside a group that was also duplicated at the same time. + /// + public bool createdWithinGroup { get; internal set; } = false; + + [NonSerialized] + internal Dictionary nodeFields = new Dictionary(); + + [NonSerialized] + internal Dictionary customPortTypeBehaviorMap = new Dictionary(); + + [NonSerialized] + List messages = new List(); + + [NonSerialized] + protected BaseGraph graph; + + internal class NodeFieldInformation + { + public string name; + public string fieldName; + public FieldInfo info; + public bool input; + public bool isMultiple; + public string tooltip; + public bool showAsDrawer; + public CustomPortBehaviorDelegate behavior; + public bool vertical; + + public NodeFieldInformation(FieldInfo info, string name, bool input, bool isMultiple, string tooltip, bool showAsDrawer, bool vertical, CustomPortBehaviorDelegate behavior) + { + this.input = input; + this.isMultiple = isMultiple; + this.info = info; + this.name = name; + this.fieldName = info.Name; + this.behavior = behavior; + this.tooltip = tooltip; + this.showAsDrawer = showAsDrawer; + this.vertical = vertical; + } + } + + struct PortUpdate + { + public List fieldNames; + public BaseNode node; + + public void Deconstruct(out List fieldNames, out BaseNode node) + { + fieldNames = this.fieldNames; + node = this.node; + } + } + + // Used in port update algorithm + Stack fieldsToUpdate = new Stack(); + HashSet updatedFields = new HashSet(); + + /// + /// Creates a node of type T at a certain position + /// + /// position in the graph in pixels + /// type of the node + /// the node instance + public static T CreateFromType(Vector2 position) where T : BaseNode + { + return CreateFromType(typeof(T), position) as T; + } + + /// + /// Creates a node of type nodeType at a certain position + /// + /// position in the graph in pixels + /// type of the node + /// the node instance + public static BaseNode CreateFromType(Type nodeType, Vector2 position) + { + if (!nodeType.IsSubclassOf(typeof(BaseNode))) + return null; + + var node = Activator.CreateInstance(nodeType) as BaseNode; + + node.position = new Rect(position, new Vector2(100, 100)); + + ExceptionToLog.Call(() => node.OnNodeCreated()); + + return node; + } + + #region Initialization + + // called by the BaseGraph when the node is added to the graph + public void Initialize(BaseGraph graph) + { + this.graph = graph; + + ExceptionToLog.Call(() => Enable()); + + InitializePorts(); + } + + void InitializeCustomPortTypeMethods() + { + MethodInfo[] methods = new MethodInfo[0]; + Type baseType = GetType(); + while (true) + { + methods = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance); + foreach (var method in methods) + { + var typeBehaviors = method.GetCustomAttributes().ToArray(); + + if (typeBehaviors.Length == 0) + continue; + + CustomPortTypeBehaviorDelegate deleg = null; + try + { + deleg = Delegate.CreateDelegate(typeof(CustomPortTypeBehaviorDelegate), this, method) as CustomPortTypeBehaviorDelegate; + } + catch (Exception e) + { + Debug.LogError(e); + Debug.LogError($"Cannot convert method {method} to a delegate of type {typeof(CustomPortTypeBehaviorDelegate)}"); + } + + foreach (var typeBehavior in typeBehaviors) + customPortTypeBehaviorMap[typeBehavior.type] = deleg; + } + + // Try to also find private methods in the base class + baseType = baseType.BaseType; + if (baseType == null) + break; + } + } + + /// + /// Use this function to initialize anything related to ports generation in your node + /// This will allow the node creation menu to correctly recognize ports that can be connected between nodes + /// + public virtual void InitializePorts() + { + InitializeCustomPortTypeMethods(); + + foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info))) + { + var nodeField = nodeFields[key.Name]; + + if (HasCustomBehavior(nodeField)) + { + UpdatePortsForField(nodeField.fieldName, sendPortUpdatedEvent: false); + } + else + { + // If we don't have a custom behavior on the node, we just have to create a simple port + AddPort( + nodeField.input, + nodeField.fieldName, + new PortData + { + acceptMultipleEdges = nodeField.isMultiple, + displayName = nodeField.name, + tooltip = nodeField.tooltip, + vertical = nodeField.vertical, + showAsDrawer = nodeField.showAsDrawer + } + ); + } + } + } + + /// + /// Override the field order inside the node. It allows to re-order all the ports and field in the UI. + /// + /// List of fields to sort + /// Sorted list of fields + public virtual IEnumerable OverrideFieldOrder(IEnumerable fields) + { + long GetFieldInheritanceLevel(FieldInfo f) + { + int level = 0; + var t = f.DeclaringType; + while (t != null) + { + t = t.BaseType; + level++; + } + + return level; + } + + // Order by MetadataToken and inheritance level to sync the order with the port order (make sure FieldDrawers are next to the correct port) + return fields.OrderByDescending(f => (long)(((GetFieldInheritanceLevel(f) << 32)) | (long)f.MetadataToken)); + } + + protected BaseNode() + { inputPorts = new NodeInputPortContainer(this); outputPorts = new NodeOutputPortContainer(this); - InitializeInOutDatas(); - } - - /// - /// Update all ports of the node - /// - public bool UpdateAllPorts() - { - bool changed = false; - - foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info))) - { - var field = nodeFields[key.Name]; - changed |= UpdatePortsForField(field.fieldName); - } - - return changed; - } - - /// - /// Update all ports of the node without updating the connected ports. Only use this method when you need to update all the nodes ports in your graph. - /// - public bool UpdateAllPortsLocal() - { - bool changed = false; - - foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info))) - { - var field = nodeFields[key.Name]; - changed |= UpdatePortsForFieldLocal(field.fieldName); - } - - return changed; - } - - - /// - /// Update the ports related to one C# property field (only for this node) - /// - /// - public bool UpdatePortsForFieldLocal(string fieldName, bool sendPortUpdatedEvent = true) - { - bool changed = false; - - if (!nodeFields.ContainsKey(fieldName)) - return false; - - var fieldInfo = nodeFields[fieldName]; - - if (!HasCustomBehavior(fieldInfo)) - return false; - - List< string > finalPorts = new List< string >(); - - var portCollection = fieldInfo.input ? (NodePortContainer)inputPorts : outputPorts; - - // Gather all fields for this port (before to modify them) - var nodePorts = portCollection.Where(p => p.fieldName == fieldName); - // Gather all edges connected to these fields: - var edges = nodePorts.SelectMany(n => n.GetEdges()).ToList(); - - if (fieldInfo.behavior != null) - { - foreach (var portData in fieldInfo.behavior(edges)) - AddPortData(portData); - } - else - { - var customPortTypeBehavior = customPortTypeBehaviorMap[fieldInfo.info.FieldType]; - - foreach (var portData in customPortTypeBehavior(fieldName, fieldInfo.name, fieldInfo.info.GetValue(this))) - AddPortData(portData); - } - - void AddPortData(PortData portData) - { - var port = nodePorts.FirstOrDefault(n => n.portData.identifier == portData.identifier); - // Guard using the port identifier so we don't duplicate identifiers - if (port == null) - { - AddPort(fieldInfo.input, fieldName, portData); - changed = true; - } - else - { - // in case the port type have changed for an incompatible type, we disconnect all the edges attached to this port - if (!BaseGraph.TypesAreConnectable(port.portData.displayType, portData.displayType)) - { - foreach (var edge in port.GetEdges().ToList()) - graph.Disconnect(edge.GUID); - } - - // patch the port data - if (port.portData != portData) - { - port.portData.CopyFrom(portData); - changed = true; - } - } - - finalPorts.Add(portData.identifier); - } - - // TODO - // Remove only the ports that are no more in the list - if (nodePorts != null) - { - var currentPortsCopy = nodePorts.ToList(); - foreach (var currentPort in currentPortsCopy) - { - // If the current port does not appear in the list of final ports, we remove it - if (!finalPorts.Any(id => id == currentPort.portData.identifier)) - { - RemovePort(fieldInfo.input, currentPort); - changed = true; - } - } - } - - // Make sure the port order is correct: - portCollection.Sort((p1, p2) => { - int p1Index = finalPorts.FindIndex(id => p1.portData.identifier == id); - int p2Index = finalPorts.FindIndex(id => p2.portData.identifier == id); - - if (p1Index == -1 || p2Index == -1) - return 0; - - return p1Index.CompareTo(p2Index); - }); - - if (sendPortUpdatedEvent) - onPortsUpdated?.Invoke(fieldName); - - return changed; - } - - bool HasCustomBehavior(NodeFieldInformation info) - { - if (info.behavior != null) - return true; - - if (customPortTypeBehaviorMap.ContainsKey(info.info.FieldType)) - return true; - - return false; - } - - /// - /// Update the ports related to one C# property field and all connected nodes in the graph - /// - /// - public bool UpdatePortsForField(string fieldName, bool sendPortUpdatedEvent = true) - { - bool changed = false; - - fieldsToUpdate.Clear(); - updatedFields.Clear(); - - fieldsToUpdate.Push(new PortUpdate{fieldNames = new List(){fieldName}, node = this}); - - // Iterate through all the ports that needs to be updated, following graph connection when the - // port is updated. This is required ton have type propagation multiple nodes that changes port types - // are connected to each other (i.e. the relay node) - while (fieldsToUpdate.Count != 0) - { - var (fields, node) = fieldsToUpdate.Pop(); - - // Avoid updating twice a port - if (updatedFields.Any((t) => t.node == node && fields.SequenceEqual(t.fieldNames))) - continue; - updatedFields.Add(new PortUpdate{fieldNames = fields, node = node}); - - foreach (var field in fields) - { - if (node.UpdatePortsForFieldLocal(field, sendPortUpdatedEvent)) - { - foreach (var port in node.IsFieldInput(field) ? (NodePortContainer)node.inputPorts : node.outputPorts) - { - if (port.fieldName != field) - continue; - - foreach(var edge in port.GetEdges()) - { - var edgeNode = (node.IsFieldInput(field)) ? edge.outputNode : edge.inputNode; - var fieldsWithBehavior = edgeNode.nodeFields.Values.Where(f => HasCustomBehavior(f)).Select(f => f.fieldName).ToList(); - fieldsToUpdate.Push(new PortUpdate{fieldNames = fieldsWithBehavior, node = edgeNode}); - } - } - changed = true; - } - } - } - - return changed; - } - - HashSet portUpdateHashSet = new HashSet(); - - internal void DisableInternal() - { - // port containers are initialized in the OnEnable - inputPorts.Clear(); - outputPorts.Clear(); - - ExceptionToLog.Call(() => Disable()); - } - - internal void DestroyInternal() => ExceptionToLog.Call(() => Destroy()); - - /// - /// Called only when the node is created, not when instantiated - /// - public virtual void OnNodeCreated() => GUID = Guid.NewGuid().ToString(); - - public virtual FieldInfo[] GetNodeFields() - => GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - - void InitializeInOutDatas() - { - var fields = GetNodeFields(); - var methods = GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - - foreach (var field in fields) - { - var inputAttribute = field.GetCustomAttribute< InputAttribute >(); - var outputAttribute = field.GetCustomAttribute< OutputAttribute >(); - var tooltipAttribute = field.GetCustomAttribute< TooltipAttribute >(); - var showInInspector = field.GetCustomAttribute< ShowInInspector >(); - var vertical = field.GetCustomAttribute< VerticalAttribute >(); - bool isMultiple = false; - bool input = false; - string name = field.Name; - string tooltip = null; - - if (showInInspector != null) - _needsInspector = true; - - if (inputAttribute == null && outputAttribute == null) - continue ; - - //check if field is a collection type - isMultiple = (inputAttribute != null) ? inputAttribute.allowMultiple : (outputAttribute.allowMultiple); - input = inputAttribute != null; - tooltip = tooltipAttribute?.tooltip; - - if (!String.IsNullOrEmpty(inputAttribute?.name)) - name = inputAttribute.name; - if (!String.IsNullOrEmpty(outputAttribute?.name)) - name = outputAttribute.name; - - // By default we set the behavior to null, if the field have a custom behavior, it will be set in the loop just below - nodeFields[field.Name] = new NodeFieldInformation(field, name, input, isMultiple, tooltip, vertical != null, null); - } - - foreach (var method in methods) - { - var customPortBehaviorAttribute = method.GetCustomAttribute< CustomPortBehaviorAttribute >(); - CustomPortBehaviorDelegate behavior = null; - - if (customPortBehaviorAttribute == null) - continue ; - - // Check if custom port behavior function is valid - try { - var referenceType = typeof(CustomPortBehaviorDelegate); - behavior = (CustomPortBehaviorDelegate)Delegate.CreateDelegate(referenceType, this, method, true); - } catch { - Debug.LogError("The function " + method + " cannot be converted to the required delegate format: " + typeof(CustomPortBehaviorDelegate)); - } - - if (nodeFields.ContainsKey(customPortBehaviorAttribute.fieldName)) - nodeFields[customPortBehaviorAttribute.fieldName].behavior = behavior; - else - Debug.LogError("Invalid field name for custom port behavior: " + method + ", " + customPortBehaviorAttribute.fieldName); - } - } - - #endregion - - #region Events and Processing - - public void OnEdgeConnected(SerializableEdge edge) - { - bool input = edge.inputNode == this; - NodePortContainer portCollection = (input) ? (NodePortContainer)inputPorts : outputPorts; - - portCollection.Add(edge); - - UpdateAllPorts(); - - onAfterEdgeConnected?.Invoke(edge); - } - - protected virtual bool CanResetPort(NodePort port) => true; - - public void OnEdgeDisconnected(SerializableEdge edge) - { - if (edge == null) - return ; - - bool input = edge.inputNode == this; - NodePortContainer portCollection = (input) ? (NodePortContainer)inputPorts : outputPorts; - - portCollection.Remove(edge); - - // Reset default values of input port: - bool haveConnectedEdges = edge.inputNode.inputPorts.Where(p => p.fieldName == edge.inputFieldName).Any(p => p.GetEdges().Count != 0); - if (edge.inputNode == this && !haveConnectedEdges && CanResetPort(edge.inputPort)) - edge.inputPort?.ResetToDefault(); - - UpdateAllPorts(); - - onAfterEdgeDisconnected?.Invoke(edge); - } - - public void OnProcess() - { - inputPorts.PullDatas(); - - ExceptionToLog.Call(() => Process()); - - InvokeOnProcessed(); - - outputPorts.PushDatas(); - } - - public void InvokeOnProcessed() => onProcessed?.Invoke(); - - /// - /// Called when the node is enabled - /// - protected virtual void Enable() {} - /// - /// Called when the node is disabled - /// - protected virtual void Disable() {} - /// - /// Called when the node is removed - /// - protected virtual void Destroy() {} - - /// - /// Override this method to implement custom processing - /// - protected virtual void Process() {} - - #endregion - - #region API and utils - - /// - /// Add a port - /// - /// is input port - /// C# field name - /// Data of the port - public void AddPort(bool input, string fieldName, PortData portData) - { - // Fixup port data info if needed: - if (portData.displayType == null) - portData.displayType = nodeFields[fieldName].info.FieldType; - - if (input) - inputPorts.Add(new NodePort(this, fieldName, portData)); - else - outputPorts.Add(new NodePort(this, fieldName, portData)); - } - - /// - /// Remove a port - /// - /// is input port - /// the port to delete - public void RemovePort(bool input, NodePort port) - { - if (input) - inputPorts.Remove(port); - else - outputPorts.Remove(port); - } - - /// - /// Remove port(s) from field name - /// - /// is input - /// C# field name - public void RemovePort(bool input, string fieldName) - { - if (input) - inputPorts.RemoveAll(p => p.fieldName == fieldName); - else - outputPorts.RemoveAll(p => p.fieldName == fieldName); - } - - /// - /// Get all the nodes connected to the input ports of this node - /// - /// an enumerable of node - public IEnumerable< BaseNode > GetInputNodes() - { - foreach (var port in inputPorts) - foreach (var edge in port.GetEdges()) - yield return edge.outputNode; - } - - /// - /// Get all the nodes connected to the output ports of this node - /// - /// an enumerable of node - public IEnumerable< BaseNode > GetOutputNodes() - { - foreach (var port in outputPorts) - foreach (var edge in port.GetEdges()) - yield return edge.inputNode; - } - - /// - /// Return a node matching the condition in the dependencies of the node - /// - /// Condition to choose the node - /// Matched node or null - public BaseNode FindInDependencies(Func condition) - { - Stack dependencies = new Stack(); - - dependencies.Push(this); - - int depth = 0; - while (dependencies.Count > 0) - { - var node = dependencies.Pop(); - - // Guard for infinite loop (faster than a HashSet based solution) - depth++; - if (depth > 2000) - break; - - if (condition(node)) - return node; - - foreach (var dep in node.GetInputNodes()) - dependencies.Push(dep); - } - return null; - } - - /// - /// Get the port from field name and identifier - /// - /// C# field name - /// Unique port identifier - /// - public NodePort GetPort(string fieldName, string identifier) - { - return inputPorts.Concat(outputPorts).FirstOrDefault(p => { - var bothNull = String.IsNullOrEmpty(identifier) && String.IsNullOrEmpty(p.portData.identifier); - return p.fieldName == fieldName && (bothNull || identifier == p.portData.identifier); - }); - } - - /// - /// Return all the ports of the node - /// - /// - public IEnumerable GetAllPorts() - { - foreach (var port in inputPorts) - yield return port; - foreach (var port in outputPorts) - yield return port; - } - - /// - /// Return all the connected edges of the node - /// - /// - public IEnumerable GetAllEdges() - { - foreach (var port in GetAllPorts()) - foreach (var edge in port.GetEdges()) - yield return edge; - } - - /// - /// Is the port an input - /// - /// - /// - public bool IsFieldInput(string fieldName) => nodeFields[fieldName].input; - - /// - /// Add a message on the node - /// - /// - /// - public void AddMessage(string message, NodeMessageType messageType) - { - if (messages.Contains(message)) - return; - - onMessageAdded?.Invoke(message, messageType); - messages.Add(message); - } - - /// - /// Remove a message on the node - /// - /// - public void RemoveMessage(string message) - { - onMessageRemoved?.Invoke(message); - messages.Remove(message); - } - - /// - /// Remove a message that contains - /// - /// - public void RemoveMessageContains(string subMessage) - { - string toRemove = messages.Find(m => m.Contains(subMessage)); - messages.Remove(toRemove); - onMessageRemoved?.Invoke(toRemove); - } - - /// - /// Remove all messages on the node - /// - public void ClearMessages() - { - foreach (var message in messages) - onMessageRemoved?.Invoke(message); - messages.Clear(); - } - - /// - /// Set the custom name of the node. This is intended to be used by renamable nodes. - /// This custom name will be serialized inside the node. - /// - /// New name of the node. - public void SetCustomName(string customName) => nodeCustomName = customName; - - /// - /// Get the name of the node. If the node have a custom name (set using the UI by double clicking on the node title) then it will return this name first, otherwise it returns the value of the name field. - /// - /// The name of the node as written in the title - public string GetCustomName() => String.IsNullOrEmpty(nodeCustomName) ? name : nodeCustomName; - - #endregion - } + InitializeInOutDatas(); + } + + /// + /// Update all ports of the node + /// + public bool UpdateAllPorts() + { + bool changed = false; + + foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info))) + { + var field = nodeFields[key.Name]; + changed |= UpdatePortsForField(field.fieldName); + } + + return changed; + } + + /// + /// Update all ports of the node without updating the connected ports. Only use this method when you need to update all the nodes ports in your graph. + /// + public bool UpdateAllPortsLocal() + { + bool changed = false; + + foreach (var key in OverrideFieldOrder(nodeFields.Values.Select(k => k.info))) + { + var field = nodeFields[key.Name]; + changed |= UpdatePortsForFieldLocal(field.fieldName); + } + + return changed; + } + + + /// + /// Update the ports related to one C# property field (only for this node) + /// + /// + public bool UpdatePortsForFieldLocal(string fieldName, bool sendPortUpdatedEvent = true) + { + bool changed = false; + + if (!nodeFields.ContainsKey(fieldName)) + return false; + + var fieldInfo = nodeFields[fieldName]; + + if (!HasCustomBehavior(fieldInfo)) + return false; + + List finalPorts = new List(); + + var portCollection = fieldInfo.input ? (NodePortContainer)inputPorts : outputPorts; + + // Gather all fields for this port (before to modify them) + var nodePorts = portCollection.Where(p => p.fieldName == fieldName); + // Gather all edges connected to these fields: + var edges = nodePorts.SelectMany(n => n.GetEdges()).ToList(); + + if (fieldInfo.behavior != null) + { + foreach (var portData in fieldInfo.behavior(edges)) + AddPortData(portData); + } + else + { + var customPortTypeBehavior = customPortTypeBehaviorMap[fieldInfo.info.FieldType]; + + foreach (var portData in customPortTypeBehavior(fieldName, fieldInfo.name, fieldInfo.info.GetValue(this))) + AddPortData(portData); + } + + void AddPortData(PortData portData) + { + var port = nodePorts.FirstOrDefault(n => n.portData.identifier == portData.identifier); + // Guard using the port identifier so we don't duplicate identifiers + if (port == null) + { + AddPort(fieldInfo.input, fieldName, portData); + changed = true; + } + else + { + // in case the port type have changed for an incompatible type, we disconnect all the edges attached to this port + if (!BaseGraph.TypesAreConnectable(port.portData.displayType, portData.displayType)) + { + foreach (var edge in port.GetEdges().ToList()) + graph.Disconnect(edge.GUID); + } + + // patch the port data + if (port.portData != portData) + { + port.portData.CopyFrom(portData); + changed = true; + } + } + + finalPorts.Add(portData.identifier); + } + + // TODO + // Remove only the ports that are no more in the list + if (nodePorts != null) + { + var currentPortsCopy = nodePorts.ToList(); + foreach (var currentPort in currentPortsCopy) + { + // If the current port does not appear in the list of final ports, we remove it + if (!finalPorts.Any(id => id == currentPort.portData.identifier)) + { + RemovePort(fieldInfo.input, currentPort); + changed = true; + } + } + } + + // Make sure the port order is correct: + portCollection.Sort((p1, p2) => + { + int p1Index = finalPorts.FindIndex(id => p1.portData.identifier == id); + int p2Index = finalPorts.FindIndex(id => p2.portData.identifier == id); + + if (p1Index == -1 || p2Index == -1) + return 0; + + return p1Index.CompareTo(p2Index); + }); + + if (sendPortUpdatedEvent) + onPortsUpdated?.Invoke(fieldName); + + return changed; + } + + bool HasCustomBehavior(NodeFieldInformation info) + { + if (info.behavior != null) + return true; + + if (customPortTypeBehaviorMap.ContainsKey(info.info.FieldType)) + return true; + + return false; + } + + /// + /// Update the ports related to one C# property field and all connected nodes in the graph + /// + /// + public bool UpdatePortsForField(string fieldName, bool sendPortUpdatedEvent = true) + { + bool changed = false; + + fieldsToUpdate.Clear(); + updatedFields.Clear(); + + fieldsToUpdate.Push(new PortUpdate { fieldNames = new List() { fieldName }, node = this }); + + // Iterate through all the ports that needs to be updated, following graph connection when the + // port is updated. This is required ton have type propagation multiple nodes that changes port types + // are connected to each other (i.e. the relay node) + while (fieldsToUpdate.Count != 0) + { + var (fields, node) = fieldsToUpdate.Pop(); + + // Avoid updating twice a port + if (updatedFields.Any((t) => t.node == node && fields.SequenceEqual(t.fieldNames))) + continue; + updatedFields.Add(new PortUpdate { fieldNames = fields, node = node }); + + foreach (var field in fields) + { + if (node.UpdatePortsForFieldLocal(field, sendPortUpdatedEvent)) + { + foreach (var port in node.IsFieldInput(field) ? (NodePortContainer)node.inputPorts : node.outputPorts) + { + if (port.fieldName != field) + continue; + + foreach (var edge in port.GetEdges()) + { + var edgeNode = (node.IsFieldInput(field)) ? edge.outputNode : edge.inputNode; + var fieldsWithBehavior = edgeNode.nodeFields.Values.Where(f => HasCustomBehavior(f)).Select(f => f.fieldName).ToList(); + fieldsToUpdate.Push(new PortUpdate { fieldNames = fieldsWithBehavior, node = edgeNode }); + } + } + changed = true; + } + } + } + + return changed; + } + + HashSet portUpdateHashSet = new HashSet(); + + internal void DisableInternal() + { + // port containers are initialized in the OnEnable + inputPorts.Clear(); + outputPorts.Clear(); + + ExceptionToLog.Call(() => Disable()); + } + + internal void DestroyInternal() => ExceptionToLog.Call(() => Destroy()); + + /// + /// Called only when the node is created, not when instantiated + /// + public virtual void OnNodeCreated() => GUID = Guid.NewGuid().ToString(); + + public virtual FieldInfo[] GetNodeFields() + => GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + void InitializeInOutDatas() + { + var fields = GetNodeFields(); + var methods = GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + foreach (var field in fields) + { + var inputAttribute = field.GetCustomAttribute(); + var outputAttribute = field.GetCustomAttribute(); + var tooltipAttribute = field.GetCustomAttribute(); + var showInInspector = field.GetCustomAttribute(); + var vertical = field.GetCustomAttribute(); + bool isMultiple = false; + bool input = false; + string name = field.Name; + string tooltip = null; + bool showAsDrawer = false; + + if (showInInspector != null) + _needsInspector = true; + + if (inputAttribute == null && outputAttribute == null) + continue; + + //check if field is a collection type + isMultiple = (inputAttribute != null) ? inputAttribute.allowMultiple : (outputAttribute.allowMultiple); + input = inputAttribute != null; + + if (input) + showAsDrawer = inputAttribute.showAsDrawer; + + tooltip = tooltipAttribute?.tooltip; + + if (!String.IsNullOrEmpty(inputAttribute?.name)) + name = inputAttribute.name; + if (!String.IsNullOrEmpty(outputAttribute?.name)) + name = outputAttribute.name; + + // By default we set the behavior to null, if the field have a custom behavior, it will be set in the loop just below + nodeFields[field.Name] = new NodeFieldInformation(field, name, input, isMultiple, tooltip, showAsDrawer, vertical != null, null); + } + + foreach (var method in methods) + { + var customPortBehaviorAttribute = method.GetCustomAttribute(); + CustomPortBehaviorDelegate behavior = null; + + if (customPortBehaviorAttribute == null) + continue; + + // Check if custom port behavior function is valid + try + { + var referenceType = typeof(CustomPortBehaviorDelegate); + behavior = (CustomPortBehaviorDelegate)Delegate.CreateDelegate(referenceType, this, method, true); + } + catch + { + Debug.LogError("The function " + method + " cannot be converted to the required delegate format: " + typeof(CustomPortBehaviorDelegate)); + } + + if (nodeFields.ContainsKey(customPortBehaviorAttribute.fieldName)) + nodeFields[customPortBehaviorAttribute.fieldName].behavior = behavior; + else + Debug.LogError("Invalid field name for custom port behavior: " + method + ", " + customPortBehaviorAttribute.fieldName); + } + } + + #endregion + + #region Events and Processing + + public void OnEdgeConnected(SerializableEdge edge) + { + bool input = edge.inputNode == this; + NodePortContainer portCollection = (input) ? (NodePortContainer)inputPorts : outputPorts; + + portCollection.Add(edge); + + UpdateAllPorts(); + + onAfterEdgeConnected?.Invoke(edge); + } + + protected virtual bool CanResetPort(NodePort port) => true; + + public void OnEdgeDisconnected(SerializableEdge edge) + { + if (edge == null) + return; + + bool input = edge.inputNode == this; + NodePortContainer portCollection = (input) ? (NodePortContainer)inputPorts : outputPorts; + + portCollection.Remove(edge); + + // Reset default values of input port: + bool haveConnectedEdges = edge.inputNode.inputPorts.Where(p => p.fieldName == edge.inputFieldName).Any(p => p.GetEdges().Count != 0); + if (edge.inputNode == this && !haveConnectedEdges && CanResetPort(edge.inputPort)) + edge.inputPort?.ResetToDefault(); + + UpdateAllPorts(); + + onAfterEdgeDisconnected?.Invoke(edge); + } + + public void OnProcess() + { + inputPorts.PullDatas(); + + ExceptionToLog.Call(() => Process()); + + InvokeOnProcessed(); + + outputPorts.PushDatas(); + } + + public void InvokeOnProcessed() => onProcessed?.Invoke(); + + /// + /// Called when the node is enabled + /// + protected virtual void Enable() { } + /// + /// Called when the node is disabled + /// + protected virtual void Disable() { } + /// + /// Called when the node is removed + /// + protected virtual void Destroy() { } + + /// + /// Override this method to implement custom processing + /// + protected virtual void Process() { } + + #endregion + + #region API and utils + + /// + /// Add a port + /// + /// is input port + /// C# field name + /// Data of the port + public void AddPort(bool input, string fieldName, PortData portData) + { + // Fixup port data info if needed: + if (portData.displayType == null) + portData.displayType = nodeFields[fieldName].info.FieldType; + + if (input) + inputPorts.Add(new NodePort(this, fieldName, portData)); + else + outputPorts.Add(new NodePort(this, fieldName, portData)); + } + + /// + /// Remove a port + /// + /// is input port + /// the port to delete + public void RemovePort(bool input, NodePort port) + { + if (input) + inputPorts.Remove(port); + else + outputPorts.Remove(port); + } + + /// + /// Remove port(s) from field name + /// + /// is input + /// C# field name + public void RemovePort(bool input, string fieldName) + { + if (input) + inputPorts.RemoveAll(p => p.fieldName == fieldName); + else + outputPorts.RemoveAll(p => p.fieldName == fieldName); + } + + /// + /// Get all the nodes connected to the input ports of this node + /// + /// an enumerable of node + public IEnumerable GetInputNodes() + { + foreach (var port in inputPorts) + foreach (var edge in port.GetEdges()) + yield return edge.outputNode; + } + + /// + /// Get all the nodes connected to the output ports of this node + /// + /// an enumerable of node + public IEnumerable GetOutputNodes() + { + foreach (var port in outputPorts) + foreach (var edge in port.GetEdges()) + yield return edge.inputNode; + } + + /// + /// Return a node matching the condition in the dependencies of the node + /// + /// Condition to choose the node + /// Matched node or null + public BaseNode FindInDependencies(Func condition) + { + Stack dependencies = new Stack(); + + dependencies.Push(this); + + int depth = 0; + while (dependencies.Count > 0) + { + var node = dependencies.Pop(); + + // Guard for infinite loop (faster than a HashSet based solution) + depth++; + if (depth > 2000) + break; + + if (condition(node)) + return node; + + foreach (var dep in node.GetInputNodes()) + dependencies.Push(dep); + } + return null; + } + + /// + /// Get the port from field name and identifier + /// + /// C# field name + /// Unique port identifier + /// + public NodePort GetPort(string fieldName, string identifier) + { + return inputPorts.Concat(outputPorts).FirstOrDefault(p => + { + var bothNull = String.IsNullOrEmpty(identifier) && String.IsNullOrEmpty(p.portData.identifier); + return p.fieldName == fieldName && (bothNull || identifier == p.portData.identifier); + }); + } + + /// + /// Return all the ports of the node + /// + /// + public IEnumerable GetAllPorts() + { + foreach (var port in inputPorts) + yield return port; + foreach (var port in outputPorts) + yield return port; + } + + /// + /// Return all the connected edges of the node + /// + /// + public IEnumerable GetAllEdges() + { + foreach (var port in GetAllPorts()) + foreach (var edge in port.GetEdges()) + yield return edge; + } + + /// + /// Is the port an input + /// + /// + /// + public bool IsFieldInput(string fieldName) => nodeFields[fieldName].input; + + /// + /// Add a message on the node + /// + /// + /// + public void AddMessage(string message, NodeMessageType messageType) + { + if (messages.Contains(message)) + return; + + onMessageAdded?.Invoke(message, messageType); + messages.Add(message); + } + + /// + /// Remove a message on the node + /// + /// + public void RemoveMessage(string message) + { + onMessageRemoved?.Invoke(message); + messages.Remove(message); + } + + /// + /// Remove a message that contains + /// + /// + public void RemoveMessageContains(string subMessage) + { + string toRemove = messages.Find(m => m.Contains(subMessage)); + messages.Remove(toRemove); + onMessageRemoved?.Invoke(toRemove); + } + + /// + /// Remove all messages on the node + /// + public void ClearMessages() + { + foreach (var message in messages) + onMessageRemoved?.Invoke(message); + messages.Clear(); + } + + /// + /// Set the custom name of the node. This is intended to be used by renamable nodes. + /// This custom name will be serialized inside the node. + /// + /// New name of the node. + public void SetCustomName(string customName) => nodeCustomName = customName; + + /// + /// Get the name of the node. If the node have a custom name (set using the UI by double clicking on the node title) then it will return this name first, otherwise it returns the value of the name field. + /// + /// The name of the node as written in the title + public string GetCustomName() => String.IsNullOrEmpty(nodeCustomName) ? name : nodeCustomName; + + #endregion + } } diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs index a5cfde55..c852fa46 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs @@ -10,168 +10,180 @@ namespace GraphProcessor { - /// - /// Class that describe port attributes for it's creation - /// - public class PortData : IEquatable< PortData > - { - /// - /// Unique identifier for the port - /// - public string identifier; - /// - /// Display name on the node - /// - public string displayName; - /// - /// The type that will be used for coloring with the type stylesheet - /// - public Type displayType; - /// - /// If the port accept multiple connection - /// - public bool acceptMultipleEdges; - /// - /// Port size, will also affect the size of the connected edge - /// - public int sizeInPixel; - /// - /// Tooltip of the port - /// - public string tooltip; - /// - /// Is the port vertical - /// - public bool vertical; + /// + /// Class that describe port attributes for it's creation + /// + public class PortData : IEquatable + { + /// + /// Unique identifier for the port + /// + public string identifier; + /// + /// Display name on the node + /// + public string displayName; + /// + /// The type that will be used for coloring with the type stylesheet + /// + public Type displayType; + /// + /// Whether to show a property drawer with this port (only for input) + /// + public bool showAsDrawer; + /// + /// If the port accept multiple connection + /// + public bool acceptMultipleEdges; + /// + /// The field the port is proxying if using custombehavior + /// + public string proxiedFieldPath; + /// + /// Port size, will also affect the size of the connected edge + /// + public int sizeInPixel; + /// + /// Tooltip of the port + /// + public string tooltip; + /// + /// Is the port vertical + /// + public bool vertical; public bool Equals(PortData other) { - return identifier == other.identifier - && displayName == other.displayName - && displayType == other.displayType - && acceptMultipleEdges == other.acceptMultipleEdges - && sizeInPixel == other.sizeInPixel - && tooltip == other.tooltip - && vertical == other.vertical; + return identifier == other.identifier + && displayName == other.displayName + && displayType == other.displayType + && showAsDrawer == other.showAsDrawer + && acceptMultipleEdges == other.acceptMultipleEdges + && sizeInPixel == other.sizeInPixel + && proxiedFieldPath == other.proxiedFieldPath + && tooltip == other.tooltip + && vertical == other.vertical; } - public void CopyFrom(PortData other) - { - identifier = other.identifier; - displayName = other.displayName; - displayType = other.displayType; - acceptMultipleEdges = other.acceptMultipleEdges; - sizeInPixel = other.sizeInPixel; - tooltip = other.tooltip; - vertical = other.vertical; - } + public void CopyFrom(PortData other) + { + identifier = other.identifier; + displayName = other.displayName; + displayType = other.displayType; + showAsDrawer = other.showAsDrawer; + acceptMultipleEdges = other.acceptMultipleEdges; + sizeInPixel = other.sizeInPixel; + proxiedFieldPath = other.proxiedFieldPath; + tooltip = other.tooltip; + vertical = other.vertical; + } } - /// - /// Runtime class that stores all info about one port that is needed for the processing - /// - public class NodePort - { - /// - /// The actual name of the property behind the port (must be exact, it is used for Reflection) - /// - public string fieldName; - /// - /// The node on which the port is - /// - public BaseNode owner; - /// - /// The fieldInfo from the fieldName - /// - public FieldInfo fieldInfo; - /// - /// Data of the port - /// - public PortData portData; - List< SerializableEdge > edges = new List< SerializableEdge >(); - Dictionary< SerializableEdge, PushDataDelegate > pushDataDelegates = new Dictionary< SerializableEdge, PushDataDelegate >(); - List< SerializableEdge > edgeWithRemoteCustomIO = new List< SerializableEdge >(); - - /// - /// Owner of the FieldInfo, to be used in case of Get/SetValue - /// - public object fieldOwner; - - CustomPortIODelegate customPortIOMethod; - - /// - /// Delegate that is made to send the data from this port to another port connected through an edge - /// This is an optimization compared to dynamically setting values using Reflection (which is really slow) - /// More info: https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/ - /// - public delegate void PushDataDelegate(); - - /// - /// Constructor - /// - /// owner node - /// the C# property name - /// Data of the port - public NodePort(BaseNode owner, string fieldName, PortData portData) : this(owner, owner, fieldName, portData) {} - - /// - /// Constructor - /// - /// owner node - /// - /// the C# property name - /// Data of the port - public NodePort(BaseNode owner, object fieldOwner, string fieldName, PortData portData) - { - this.fieldName = fieldName; - this.owner = owner; - this.portData = portData; - this.fieldOwner = fieldOwner; - - fieldInfo = fieldOwner.GetType().GetField( - fieldName, - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - customPortIOMethod = CustomPortIO.GetCustomPortMethod(owner.GetType(), fieldName); - } - - /// - /// Connect an edge to this port - /// - /// - public void Add(SerializableEdge edge) - { - if (!edges.Contains(edge)) - edges.Add(edge); - - if (edge.inputNode == owner) - { - if (edge.outputPort.customPortIOMethod != null) - edgeWithRemoteCustomIO.Add(edge); - } - else - { - if (edge.inputPort.customPortIOMethod != null) - edgeWithRemoteCustomIO.Add(edge); - } - - //if we have a custom io implementation, we don't need to genereate the defaut one - if (edge.inputPort.customPortIOMethod != null || edge.outputPort.customPortIOMethod != null) - return ; - - PushDataDelegate edgeDelegate = CreatePushDataDelegateForEdge(edge); - - if (edgeDelegate != null) - pushDataDelegates[edge] = edgeDelegate; - } - - PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge) - { - try - { - //Creation of the delegate to move the data from the input node to the output node: - FieldInfo inputField = edge.inputNode.GetType().GetField(edge.inputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - FieldInfo outputField = edge.outputNode.GetType().GetField(edge.outputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - Type inType, outType; + /// + /// Runtime class that stores all info about one port that is needed for the processing + /// + public class NodePort + { + /// + /// The actual name of the property behind the port (must be exact, it is used for Reflection) + /// + public string fieldName; + /// + /// The node on which the port is + /// + public BaseNode owner; + /// + /// The fieldInfo from the fieldName + /// + public FieldInfo fieldInfo; + /// + /// Data of the port + /// + public PortData portData; + List edges = new List(); + Dictionary pushDataDelegates = new Dictionary(); + List edgeWithRemoteCustomIO = new List(); + + /// + /// Owner of the FieldInfo, to be used in case of Get/SetValue + /// + public object fieldOwner; + + CustomPortIODelegate customPortIOMethod; + + /// + /// Delegate that is made to send the data from this port to another port connected through an edge + /// This is an optimization compared to dynamically setting values using Reflection (which is really slow) + /// More info: https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/ + /// + public delegate void PushDataDelegate(); + + /// + /// Constructor + /// + /// owner node + /// the C# property name + /// Data of the port + public NodePort(BaseNode owner, string fieldName, PortData portData) : this(owner, owner, fieldName, portData) { } + + /// + /// Constructor + /// + /// owner node + /// + /// the C# property name + /// Data of the port + public NodePort(BaseNode owner, object fieldOwner, string fieldName, PortData portData) + { + this.fieldName = fieldName; + this.owner = owner; + this.portData = portData; + this.fieldOwner = fieldOwner; + + fieldInfo = fieldOwner.GetType().GetField( + fieldName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + customPortIOMethod = CustomPortIO.GetCustomPortMethod(owner.GetType(), fieldName); + } + + /// + /// Connect an edge to this port + /// + /// + public void Add(SerializableEdge edge) + { + if (!edges.Contains(edge)) + edges.Add(edge); + + if (edge.inputNode == owner) + { + if (edge.outputPort.customPortIOMethod != null) + edgeWithRemoteCustomIO.Add(edge); + } + else + { + if (edge.inputPort.customPortIOMethod != null) + edgeWithRemoteCustomIO.Add(edge); + } + + //if we have a custom io implementation, we don't need to genereate the defaut one + if (edge.inputPort.customPortIOMethod != null || edge.outputPort.customPortIOMethod != null) + return; + + PushDataDelegate edgeDelegate = CreatePushDataDelegateForEdge(edge); + + if (edgeDelegate != null) + pushDataDelegates[edge] = edgeDelegate; + } + + PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge) + { + try + { + //Creation of the delegate to move the data from the input node to the output node: + FieldInfo inputField = edge.inputNode.GetType().GetField(edge.inputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo outputField = edge.outputNode.GetType().GetField(edge.outputFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + Type inType, outType; #if DEBUG_LAMBDA return new PushDataDelegate(() => { @@ -192,205 +204,208 @@ PushDataDelegate CreatePushDataDelegateForEdge(SerializableEdge edge) }); #endif -// We keep slow checks inside the editor + // We keep slow checks inside the editor #if UNITY_EDITOR - if (!BaseGraph.TypesAreConnectable(inputField.FieldType, outputField.FieldType)) - { - Debug.LogError("Can't convert from " + inputField.FieldType + " to " + outputField.FieldType + ", you must specify a custom port function (i.e CustomPortInput or CustomPortOutput) for non-implicit convertions"); - return null; - } + if (!BaseGraph.TypesAreConnectable(inputField.FieldType, outputField.FieldType)) + { + Debug.LogError("Can't convert from " + inputField.FieldType + " to " + outputField.FieldType + ", you must specify a custom port function (i.e CustomPortInput or CustomPortOutput) for non-implicit convertions"); + return null; + } #endif - Expression inputParamField = Expression.Field(Expression.Constant(edge.inputNode), inputField); - Expression outputParamField = Expression.Field(Expression.Constant(edge.outputNode), outputField); - - inType = edge.inputPort.portData.displayType ?? inputField.FieldType; - outType = edge.outputPort.portData.displayType ?? outputField.FieldType; - - // If there is a user defined convertion function, then we call it - if (TypeAdapter.AreAssignable(outType, inType)) - { - // We add a cast in case there we're calling the conversion method with a base class parameter (like object) - var convertedParam = Expression.Convert(outputParamField, outType); - outputParamField = Expression.Call(TypeAdapter.GetConvertionMethod(outType, inType), convertedParam); - // In case there is a custom port behavior in the output, then we need to re-cast to the base type because - // the convertion method return type is not always assignable directly: - outputParamField = Expression.Convert(outputParamField, inputField.FieldType); - } - else // otherwise we cast - outputParamField = Expression.Convert(outputParamField, inputField.FieldType); - - BinaryExpression assign = Expression.Assign(inputParamField, outputParamField); - return Expression.Lambda< PushDataDelegate >(assign).Compile(); - } catch (Exception e) { - Debug.LogError(e); - return null; - } - } - - /// - /// Disconnect an Edge from this port - /// - /// - public void Remove(SerializableEdge edge) - { - if (!edges.Contains(edge)) - return; - - pushDataDelegates.Remove(edge); - edgeWithRemoteCustomIO.Remove(edge); - edges.Remove(edge); - } - - /// - /// Get all the edges connected to this port - /// - /// - public List< SerializableEdge > GetEdges() => edges; - - /// - /// Push the value of the port through the edges - /// This method can only be called on output ports - /// - public void PushData() - { - if (customPortIOMethod != null) - { - customPortIOMethod(owner, edges, this); - return ; - } - - foreach (var pushDataDelegate in pushDataDelegates) - pushDataDelegate.Value(); - - if (edgeWithRemoteCustomIO.Count == 0) - return ; - - //if there are custom IO implementation on the other ports, they'll need our value in the passThrough buffer - object ourValue = fieldInfo.GetValue(fieldOwner); - foreach (var edge in edgeWithRemoteCustomIO) - edge.passThroughBuffer = ourValue; - } - - /// - /// Reset the value of the field to default if possible - /// - public void ResetToDefault() - { - // Clear lists, set classes to null and struct to default value. - if (typeof(IList).IsAssignableFrom(fieldInfo.FieldType)) - (fieldInfo.GetValue(fieldOwner) as IList)?.Clear(); - else if (fieldInfo.FieldType.GetTypeInfo().IsClass) - fieldInfo.SetValue(fieldOwner, null); - else - { - try - { - fieldInfo.SetValue(fieldOwner, Activator.CreateInstance(fieldInfo.FieldType)); - } catch {} // Catch types that don't have any constructors - } - } - - /// - /// Pull values from the edge (in case of a custom convertion method) - /// This method can only be called on input ports - /// - public void PullData() - { - if (customPortIOMethod != null) - { - customPortIOMethod(owner, edges, this); - return ; - } - - // check if this port have connection to ports that have custom output functions - if (edgeWithRemoteCustomIO.Count == 0) - return ; - - // Only one input connection is handled by this code, if you want to - // take multiple inputs, you must create a custom input function see CustomPortsNode.cs - if (edges.Count > 0) - { - var passThroughObject = edges.First().passThroughBuffer; - - // We do an extra convertion step in case the buffer output is not compatible with the input port - if (passThroughObject != null) - if (TypeAdapter.AreAssignable(fieldInfo.FieldType, passThroughObject.GetType())) - passThroughObject = TypeAdapter.Convert(passThroughObject, fieldInfo.FieldType); - - fieldInfo.SetValue(fieldOwner, passThroughObject); - } - } - } - - /// - /// Container of ports and the edges connected to these ports - /// - public abstract class NodePortContainer : List< NodePort > - { - protected BaseNode node; - - public NodePortContainer(BaseNode node) - { - this.node = node; - } - - /// - /// Remove an edge that is connected to one of the node in the container - /// - /// - public void Remove(SerializableEdge edge) - { - ForEach(p => p.Remove(edge)); - } - - /// - /// Add an edge that is connected to one of the node in the container - /// - /// - public void Add(SerializableEdge edge) - { - string portFieldName = (edge.inputNode == node) ? edge.inputFieldName : edge.outputFieldName; - string portIdentifier = (edge.inputNode == node) ? edge.inputPortIdentifier : edge.outputPortIdentifier; - - // Force empty string to null since portIdentifier is a serialized value - if (String.IsNullOrEmpty(portIdentifier)) - portIdentifier = null; - - var port = this.FirstOrDefault(p => - { - return p.fieldName == portFieldName && p.portData.identifier == portIdentifier; - }); - - if (port == null) - { - Debug.LogError("The edge can't be properly connected because it's ports can't be found"); - return; - } - - port.Add(edge); - } - } - - /// - public class NodeInputPortContainer : NodePortContainer - { - public NodeInputPortContainer(BaseNode node) : base(node) {} - - public void PullDatas() - { - ForEach(p => p.PullData()); - } - } - - /// - public class NodeOutputPortContainer : NodePortContainer - { - public NodeOutputPortContainer(BaseNode node) : base(node) {} - - public void PushDatas() - { - ForEach(p => p.PushData()); - } - } + Expression inputParamField = Expression.Field(Expression.Constant(edge.inputNode), inputField); + Expression outputParamField = Expression.Field(Expression.Constant(edge.outputNode), outputField); + + inType = edge.inputPort.portData.displayType ?? inputField.FieldType; + outType = edge.outputPort.portData.displayType ?? outputField.FieldType; + + // If there is a user defined convertion function, then we call it + if (TypeAdapter.AreAssignable(outType, inType)) + { + // We add a cast in case there we're calling the conversion method with a base class parameter (like object) + var convertedParam = Expression.Convert(outputParamField, outType); + outputParamField = Expression.Call(TypeAdapter.GetConvertionMethod(outType, inType), convertedParam); + // In case there is a custom port behavior in the output, then we need to re-cast to the base type because + // the convertion method return type is not always assignable directly: + outputParamField = Expression.Convert(outputParamField, inputField.FieldType); + } + else // otherwise we cast + outputParamField = Expression.Convert(outputParamField, inputField.FieldType); + + BinaryExpression assign = Expression.Assign(inputParamField, outputParamField); + return Expression.Lambda(assign).Compile(); + } + catch (Exception e) + { + Debug.LogError(e); + return null; + } + } + + /// + /// Disconnect an Edge from this port + /// + /// + public void Remove(SerializableEdge edge) + { + if (!edges.Contains(edge)) + return; + + pushDataDelegates.Remove(edge); + edgeWithRemoteCustomIO.Remove(edge); + edges.Remove(edge); + } + + /// + /// Get all the edges connected to this port + /// + /// + public List GetEdges() => edges; + + /// + /// Push the value of the port through the edges + /// This method can only be called on output ports + /// + public void PushData() + { + if (customPortIOMethod != null) + { + customPortIOMethod(owner, edges, this); + return; + } + + foreach (var pushDataDelegate in pushDataDelegates) + pushDataDelegate.Value(); + + if (edgeWithRemoteCustomIO.Count == 0) + return; + + //if there are custom IO implementation on the other ports, they'll need our value in the passThrough buffer + object ourValue = fieldInfo.GetValue(fieldOwner); + foreach (var edge in edgeWithRemoteCustomIO) + edge.passThroughBuffer = ourValue; + } + + /// + /// Reset the value of the field to default if possible + /// + public void ResetToDefault() + { + // Clear lists, set classes to null and struct to default value. + if (typeof(IList).IsAssignableFrom(fieldInfo.FieldType)) + (fieldInfo.GetValue(fieldOwner) as IList)?.Clear(); + else if (fieldInfo.FieldType.GetTypeInfo().IsClass) + fieldInfo.SetValue(fieldOwner, null); + else + { + try + { + fieldInfo.SetValue(fieldOwner, Activator.CreateInstance(fieldInfo.FieldType)); + } + catch { } // Catch types that don't have any constructors + } + } + + /// + /// Pull values from the edge (in case of a custom convertion method) + /// This method can only be called on input ports + /// + public void PullData() + { + if (customPortIOMethod != null) + { + customPortIOMethod(owner, edges, this); + return; + } + + // check if this port have connection to ports that have custom output functions + if (edgeWithRemoteCustomIO.Count == 0) + return; + + // Only one input connection is handled by this code, if you want to + // take multiple inputs, you must create a custom input function see CustomPortsNode.cs + if (edges.Count > 0) + { + var passThroughObject = edges.First().passThroughBuffer; + + // We do an extra convertion step in case the buffer output is not compatible with the input port + if (passThroughObject != null) + if (TypeAdapter.AreAssignable(fieldInfo.FieldType, passThroughObject.GetType())) + passThroughObject = TypeAdapter.Convert(passThroughObject, fieldInfo.FieldType); + + fieldInfo.SetValue(fieldOwner, passThroughObject); + } + } + } + + /// + /// Container of ports and the edges connected to these ports + /// + public abstract class NodePortContainer : List + { + protected BaseNode node; + + public NodePortContainer(BaseNode node) + { + this.node = node; + } + + /// + /// Remove an edge that is connected to one of the node in the container + /// + /// + public void Remove(SerializableEdge edge) + { + ForEach(p => p.Remove(edge)); + } + + /// + /// Add an edge that is connected to one of the node in the container + /// + /// + public void Add(SerializableEdge edge) + { + string portFieldName = (edge.inputNode == node) ? edge.inputFieldName : edge.outputFieldName; + string portIdentifier = (edge.inputNode == node) ? edge.inputPortIdentifier : edge.outputPortIdentifier; + + // Force empty string to null since portIdentifier is a serialized value + if (String.IsNullOrEmpty(portIdentifier)) + portIdentifier = null; + + var port = this.FirstOrDefault(p => + { + return p.fieldName == portFieldName && p.portData.identifier == portIdentifier; + }); + + if (port == null) + { + Debug.LogError("The edge can't be properly connected because it's ports can't be found"); + return; + } + + port.Add(edge); + } + } + + /// + public class NodeInputPortContainer : NodePortContainer + { + public NodeInputPortContainer(BaseNode node) : base(node) { } + + public void PullDatas() + { + ForEach(p => p.PullData()); + } + } + + /// + public class NodeOutputPortContainer : NodePortContainer + { + public NodeOutputPortContainer(BaseNode node) : base(node) { } + + public void PushDatas() + { + ForEach(p => p.PushData()); + } + } } \ No newline at end of file diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs index effd5610..4150e1cf 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs @@ -5,242 +5,246 @@ namespace GraphProcessor { - /// - /// Tell that this field is will generate an input port - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class InputAttribute : Attribute - { - public string name; - public bool allowMultiple = false; - - /// - /// Mark the field as an input port - /// - /// display name - /// is connecting multiple edges allowed - public InputAttribute(string name = null, bool allowMultiple = false) - { - this.name = name; - this.allowMultiple = allowMultiple; - } - } - - /// - /// Tell that this field is will generate an output port - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class OutputAttribute : Attribute - { - public string name; - public bool allowMultiple = true; - - /// - /// Mark the field as an output port - /// - /// display name - /// is connecting multiple edges allowed - public OutputAttribute(string name = null, bool allowMultiple = true) - { - this.name = name; - this.allowMultiple = allowMultiple; - } - } - - /// - /// Creates a vertical port instead of the default horizontal one - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class VerticalAttribute : Attribute - { - } - - /// - /// Register the node in the NodeProvider class. The node will also be available in the node creation window. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class NodeMenuItemAttribute : Attribute - { - public string menuTitle; - public Type onlyCompatibleWithGraph; - - /// - /// Register the node in the NodeProvider class. The node will also be available in the node creation window. - /// - /// Path in the menu, use / as folder separators - public NodeMenuItemAttribute(string menuTitle = null, Type onlyCompatibleWithGraph = null) - { - this.menuTitle = menuTitle; - this.onlyCompatibleWithGraph = onlyCompatibleWithGraph; - } - } - - /// - /// Set a custom drawer for a field. It can then be created using the FieldFactory - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - [Obsolete("You can use the standard Unity CustomPropertyDrawer instead.")] - public class FieldDrawerAttribute : Attribute - { - public Type fieldType; - - /// - /// Register a custom view for a type in the FieldFactory class - /// - /// - public FieldDrawerAttribute(Type fieldType) - { - this.fieldType = fieldType; - } - } - - /// - /// Allow you to customize the input function of a port - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class CustomPortInputAttribute : Attribute - { - public string fieldName; - public Type inputType; - public bool allowCast; - - /// - /// Allow you to customize the input function of a port. - /// See CustomPortsNode example in Samples. - /// - /// local field of the node - /// type of input of the port - /// if cast is allowed when connecting an edge - public CustomPortInputAttribute(string fieldName, Type inputType, bool allowCast = true) - { - this.fieldName = fieldName; - this.inputType = inputType; - this.allowCast = allowCast; - } - } - - /// - /// Allow you to customize the input function of a port - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class CustomPortOutputAttribute : Attribute - { - public string fieldName; - public Type outputType; - public bool allowCast; - - /// - /// Allow you to customize the output function of a port. - /// See CustomPortsNode example in Samples. - /// - /// local field of the node - /// type of input of the port - /// if cast is allowed when connecting an edge - public CustomPortOutputAttribute(string fieldName, Type outputType, bool allowCast = true) - { - this.fieldName = fieldName; - this.outputType = outputType; - this.allowCast = allowCast; - } - } - - /// - /// Allow you to modify the generated port view from a field. Can be used to generate multiple ports from one field. - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class CustomPortBehaviorAttribute : Attribute - { - public string fieldName; - - /// - /// Allow you to modify the generated port view from a field. Can be used to generate multiple ports from one field. - /// You must add this attribute on a function of this signature - /// - /// IEnumerable<PortData> MyCustomPortFunction(List<SerializableEdge> edges); - /// - /// - /// local node field name - public CustomPortBehaviorAttribute(string fieldName) - { - this.fieldName = fieldName; - } - } - - /// - /// Allow to bind a method to generate a specific set of ports based on a field type in a node - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class CustomPortTypeBehavior : Attribute - { - /// - /// Target type - /// - public Type type; - - public CustomPortTypeBehavior(Type type) - { - this.type = type; - } - } - - /// - /// Allow you to have a custom view for your stack nodes - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class CustomStackNodeView : Attribute - { - public Type stackNodeType; - - /// - /// Allow you to have a custom view for your stack nodes - /// - /// The type of the stack node you target - public CustomStackNodeView(Type stackNodeType) - { - this.stackNodeType = stackNodeType; - } - } - - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class VisibleIf : Attribute - { - public string fieldName; - public object value; - - public VisibleIf(string fieldName, object value) - { - this.fieldName = fieldName; - this.value = value; - } - } - - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class ShowInInspector : Attribute - { - public bool showInNode; - - public ShowInInspector(bool showInNode = false) - { - this.showInNode = showInNode; - } - } - - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class ShowAsDrawer : Attribute - { - } - - [AttributeUsage(AttributeTargets.Field)] - public class SettingAttribute : Attribute - { - public string name; - - public SettingAttribute(string name = null) - { - this.name = name; - } - } - - [AttributeUsage(AttributeTargets.Method)] - public class IsCompatibleWithGraph : Attribute {} + /// + /// Tell that this field is will generate an input port + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class InputAttribute : Attribute + { + public string name; + public bool allowMultiple = false; + public bool showAsDrawer = false; + + /// + /// Mark the field as an input port + /// + /// display name + /// is connecting multiple edges allowed + public InputAttribute(string name = null, bool showAsDrawer = false, bool allowMultiple = false) + { + this.name = name; + this.showAsDrawer = showAsDrawer; + this.allowMultiple = allowMultiple; + } + } + + /// + /// Tell that this field is will generate an output port + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class OutputAttribute : Attribute + { + public string name; + public bool allowMultiple = true; + + /// + /// Mark the field as an output port + /// + /// display name + /// is connecting multiple edges allowed + public OutputAttribute(string name = null, bool allowMultiple = true) + { + this.name = name; + this.allowMultiple = allowMultiple; + } + } + + /// + /// Creates a vertical port instead of the default horizontal one + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class VerticalAttribute : Attribute + { + } + + /// + /// Register the node in the NodeProvider class. The node will also be available in the node creation window. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class NodeMenuItemAttribute : Attribute + { + public string menuTitle; + public Type onlyCompatibleWithGraph; + + /// + /// Register the node in the NodeProvider class. The node will also be available in the node creation window. + /// + /// Path in the menu, use / as folder separators + public NodeMenuItemAttribute(string menuTitle = null, Type onlyCompatibleWithGraph = null) + { + this.menuTitle = menuTitle; + this.onlyCompatibleWithGraph = onlyCompatibleWithGraph; + } + } + + /// + /// Set a custom drawer for a field. It can then be created using the FieldFactory + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + [Obsolete("You can use the standard Unity CustomPropertyDrawer instead.")] + public class FieldDrawerAttribute : Attribute + { + public Type fieldType; + + /// + /// Register a custom view for a type in the FieldFactory class + /// + /// + public FieldDrawerAttribute(Type fieldType) + { + this.fieldType = fieldType; + } + } + + /// + /// Allow you to customize the input function of a port + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class CustomPortInputAttribute : Attribute + { + public string fieldName; + public Type inputType; + public bool allowCast; + + /// + /// Allow you to customize the input function of a port. + /// See CustomPortsNode example in Samples. + /// + /// local field of the node + /// type of input of the port + /// if cast is allowed when connecting an edge + public CustomPortInputAttribute(string fieldName, Type inputType, bool allowCast = true) + { + this.fieldName = fieldName; + this.inputType = inputType; + this.allowCast = allowCast; + } + } + + /// + /// Allow you to customize the input function of a port + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class CustomPortOutputAttribute : Attribute + { + public string fieldName; + public Type outputType; + public bool allowCast; + + /// + /// Allow you to customize the output function of a port. + /// See CustomPortsNode example in Samples. + /// + /// local field of the node + /// type of input of the port + /// if cast is allowed when connecting an edge + public CustomPortOutputAttribute(string fieldName, Type outputType, bool allowCast = true) + { + this.fieldName = fieldName; + this.outputType = outputType; + this.allowCast = allowCast; + } + } + + /// + /// Allow you to modify the generated port view from a field. Can be used to generate multiple ports from one field. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class CustomPortBehaviorAttribute : Attribute + { + public string fieldName; + + /// + /// Allow you to modify the generated port view from a field. Can be used to generate multiple ports from one field. + /// You must add this attribute on a function of this signature + /// + /// IEnumerable<PortData> MyCustomPortFunction(List<SerializableEdge> edges); + /// + /// + /// local node field name + public CustomPortBehaviorAttribute(string fieldName) + { + this.fieldName = fieldName; + } + } + + /// + /// Allow to bind a method to generate a specific set of ports based on a field type in a node + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class CustomPortTypeBehavior : Attribute + { + /// + /// Target type + /// + public Type type; + + public CustomPortTypeBehavior(Type type) + { + this.type = type; + } + } + + /// + /// Allow you to have a custom view for your stack nodes + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class CustomStackNodeView : Attribute + { + public Type stackNodeType; + + /// + /// Allow you to have a custom view for your stack nodes + /// + /// The type of the stack node you target + public CustomStackNodeView(Type stackNodeType) + { + this.stackNodeType = stackNodeType; + } + } + + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class VisibleIf : Attribute + { + public string fieldName; + public object value; + + public VisibleIf(string fieldName, object value) + { + this.fieldName = fieldName; + this.value = value; + } + } + + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class ShowInInspector : Attribute + { + public bool showInNode; + + public ShowInInspector(bool showInNode = false) + { + this.showInNode = showInNode; + } + } + + [Obsolete("ShowAsDrawer attribute is deprecated. Please use the InputAttribute showAsDrawer field.")] + + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class ShowAsDrawer : Attribute + { + } + + [AttributeUsage(AttributeTargets.Field)] + public class SettingAttribute : Attribute + { + public string name; + + public SettingAttribute(string name = null) + { + this.name = name; + } + } + + [AttributeUsage(AttributeTargets.Method)] + public class IsCompatibleWithGraph : Attribute { } } \ No newline at end of file diff --git a/Packages/manifest.json b/Packages/manifest.json index ca1a11a9..4ffdacc6 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -4,13 +4,13 @@ "com.unity.2d.tilemap": "1.0.0", "com.unity.ext.nunit": "1.0.6", "com.unity.ide.rider": "3.0.7", - "com.unity.ide.visualstudio": "2.0.9", - "com.unity.ide.vscode": "1.2.3", - "com.unity.test-framework": "1.1.26", + "com.unity.ide.visualstudio": "2.0.12", + "com.unity.ide.vscode": "1.2.4", + "com.unity.test-framework": "1.1.29", "com.unity.textmeshpro": "3.0.6", - "com.unity.timeline": "1.6.0-pre.5", + "com.unity.timeline": "1.6.3", "com.unity.ugui": "1.0.0", - "com.unity.xr.legacyinputhelpers": "2.1.7", + "com.unity.xr.legacyinputhelpers": "2.1.8", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index a27bb1bf..5db5cfa6 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -29,7 +29,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.9", + "version": "2.0.12", "depth": 0, "source": "registry", "dependencies": { @@ -38,14 +38,14 @@ "url": "https://packages.unity.com" }, "com.unity.ide.vscode": { - "version": "1.2.3", + "version": "1.2.4", "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.test-framework": { - "version": "1.1.26", + "version": "1.1.29", "depth": 0, "source": "registry", "dependencies": { @@ -65,7 +65,7 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.6.0-pre.5", + "version": "1.6.3", "depth": 0, "source": "registry", "dependencies": { @@ -86,7 +86,7 @@ } }, "com.unity.xr.legacyinputhelpers": { - "version": "2.1.7", + "version": "2.1.8", "depth": 0, "source": "registry", "dependencies": { diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 5ab5db38..bbae793c 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2021.2.0b3 -m_EditorVersionWithRevision: 2021.2.0b3 (40188ccec128) +m_EditorVersion: 2021.2.7f1 +m_EditorVersionWithRevision: 2021.2.7f1 (6bd9e232123f) From 78cf45faf94c4ee725f74b2da3bf1c7776f00c7d Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 02:11:39 +0000 Subject: [PATCH 19/31] Removed debug --- .../com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index bc6e9fc5..8b89f357 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -733,7 +733,6 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect bool showAsDrawer = !fromInspector && hasInputAttribute && inputAttribute.showAsDrawer; if ((!serializeField || isProxied) && hasInputOrOutputAttribute && !showAsDrawer) { - Debug.Log("here: " + field.Name); AddEmptyField(field, fromInspector); return; } From 5eb25aeb8c51e7a771d9d25b6cdf9ce885f84262 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 02:12:31 +0000 Subject: [PATCH 20/31] Minor --- Assets/Testing/DynamicNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Testing/DynamicNode.cs b/Assets/Testing/DynamicNode.cs index e5f6a538..38ea4318 100644 --- a/Assets/Testing/DynamicNode.cs +++ b/Assets/Testing/DynamicNode.cs @@ -127,7 +127,7 @@ protected IEnumerable ActionDataBehaviour(List edges identifier = field.fieldInfo.Name, showAsDrawer = field.inputAttribute.showAsDrawer, vertical = false, - proxiedFieldPath = "data." + field.fieldInfo.Name, + proxiedFieldPath = nameof(data) + '.' + field.fieldInfo.Name, acceptMultipleEdges = field.inputAttribute.allowMultiple, }; } From d16504e87630501dd5ea85c504d68d879db304c7 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 15:35:58 +0000 Subject: [PATCH 21/31] Added FieldInfo extensions for HasAttribute --- .../Editor/Views/BaseNodeView.cs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 8b89f357..eb2863b2 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -256,7 +256,7 @@ void InitializeSettings() var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (var field in fields) - if (field.GetCustomAttribute(typeof(SettingAttribute)) != null) + if (field.HasCustomAttribute()) AddSettingField(field); } } @@ -687,9 +687,7 @@ protected virtual void DrawDefaultInspector(bool fromInspector = false) for (int i = 0; i < fields.Count; i++) { FieldInfo field = fields[i]; - InputAttribute inputAttribute = field.GetCustomAttribute(); - bool hasInputAttribute = inputAttribute != null; - if (hasInputAttribute && portsPerFieldName.ContainsKey(field.Name)) + if (field.HasCustomAttribute() && portsPerFieldName.ContainsKey(field.Name)) { // IF FIELD IS MULTI_PORT DO THIS LOOP OVER THIS WITH RELATED PORTS AND IGNORE THE BASE PORT foreach (var port in portsPerFieldName[field.Name]) @@ -712,14 +710,14 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect string fieldPath = fieldInfoList.GetPath(); //skip if the field is a node setting - if (field.GetCustomAttribute(typeof(SettingAttribute)) != null) + if (field.HasCustomAttribute()) { hasSettings = true; return; } //skip if the field is not serializable - bool serializeField = field.GetCustomAttribute(typeof(SerializeField)) != null; + bool serializeField = field.HasCustomAttribute(); if ((!field.IsPublic && !serializeField) || field.IsNotSerialized) { AddEmptyField(field, fromInspector); @@ -729,7 +727,7 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect //skip if the field is an input/output and not marked as SerializedField InputAttribute inputAttribute = field.GetCustomAttribute(); bool hasInputAttribute = inputAttribute != null; - bool hasInputOrOutputAttribute = hasInputAttribute || field.GetCustomAttribute(typeof(OutputAttribute)) != null; + bool hasInputOrOutputAttribute = hasInputAttribute || field.HasCustomAttribute(); bool showAsDrawer = !fromInspector && hasInputAttribute && inputAttribute.showAsDrawer; if ((!serializeField || isProxied) && hasInputOrOutputAttribute && !showAsDrawer) { @@ -738,7 +736,7 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect } //skip if marked with NonSerialized or HideInInspector - if (field.GetCustomAttribute(typeof(System.NonSerializedAttribute)) != null || field.GetCustomAttribute(typeof(HideInInspector)) != null) + if (field.HasCustomAttribute() || field.HasCustomAttribute()) { AddEmptyField(field, fromInspector); return; @@ -753,7 +751,7 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect } - var showInputDrawer = hasInputAttribute && field.GetCustomAttribute(typeof(SerializeField)) != null; + var showInputDrawer = hasInputAttribute && serializeField; showInputDrawer |= hasInputAttribute && inputAttribute.showAsDrawer; showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType); @@ -801,10 +799,10 @@ protected virtual void SetNodeColor(Color color) private void AddEmptyField(FieldInfo field, bool fromInspector) { - if (field.GetCustomAttribute(typeof(InputAttribute)) == null || fromInspector) + if (!field.HasCustomAttribute() || fromInspector) return; - if (field.GetCustomAttribute() != null) + if (field.HasCustomAttribute()) return; var box = new VisualElement { name = field.Name }; @@ -961,7 +959,7 @@ protected VisualElement AddControlField(List fieldInfoPath, string la if (showInputDrawer) AddEmptyField(field, false); } - var visibleCondition = field.GetCustomAttribute(typeof(VisibleIf)) as VisibleIf; + var visibleCondition = field.GetCustomAttribute(); if (visibleCondition != null) { // Check if target field exists: @@ -1239,8 +1237,18 @@ void UpdatePortsForField(string fieldName) #endregion } - public static class ListHelpers + public static class Extensions { + public static bool HasCustomAttribute(this FieldInfo fieldInfo) + { + return Attribute.IsDefined(fieldInfo, typeof(T)); + } + + public static bool HasCustomAttribute(this FieldInfo fieldInfo, Type type) + { + return Attribute.IsDefined(fieldInfo, type); + } + public static object GetValueAt(this IList list, object startingValue, int index) { object currentValue = startingValue; From eb092684443c6f1b461159c8bfbafaf6c9e5832e Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 18:19:49 +0000 Subject: [PATCH 22/31] MovedFieldInfoExtensions into its own file --- .../Editor/Views/BaseNodeView.cs | 51 ---------------- .../Runtime/Utils/FieldInfoExtension.cs | 60 +++++++++++++++++++ .../Runtime/Utils/FieldInfoExtension.cs.meta | 11 ++++ 3 files changed, 71 insertions(+), 51 deletions(-) create mode 100644 Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs create mode 100644 Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs.meta diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index eb2863b2..11c0acc9 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -1236,55 +1236,4 @@ void UpdatePortsForField(string fieldName) #endregion } - - public static class Extensions - { - public static bool HasCustomAttribute(this FieldInfo fieldInfo) - { - return Attribute.IsDefined(fieldInfo, typeof(T)); - } - - public static bool HasCustomAttribute(this FieldInfo fieldInfo, Type type) - { - return Attribute.IsDefined(fieldInfo, type); - } - - public static object GetValueAt(this IList list, object startingValue, int index) - { - object currentValue = startingValue; - for (int i = 0; i < list.Count; i++) - { - currentValue = list[i].GetValue(currentValue); - if (i == index) break; - } - return currentValue; - } - - public static object GetFinalValue(this IList list, object startingValue) - { - object currentValue = startingValue; - for (int i = 0; i < list.Count; i++) - { - currentValue = list[i].GetValue(currentValue); - } - return currentValue; - - } - - public static string GetPath(this IList list) - { - string path = ""; - for (int i = 0; i < list.Count; i++) - { - if (i > 0) path += "."; - path += list[i].Name; - } - return path; - } - - public static bool IsValid(this IList list) - { - return list.Any(x => x == null); - } - } } \ No newline at end of file diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs new file mode 100644 index 00000000..4b423dcc --- /dev/null +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace GraphProcessor +{ + public static class FieldInfoExtension + { + public static bool HasCustomAttribute(this FieldInfo fieldInfo) + { + return Attribute.IsDefined(fieldInfo, typeof(T)); + } + + public static bool HasCustomAttribute(this FieldInfo fieldInfo, Type type) + { + return Attribute.IsDefined(fieldInfo, type); + } + + public static object GetValueAt(this IList list, object startingValue, int index) + { + object currentValue = startingValue; + for (int i = 0; i < list.Count; i++) + { + currentValue = list[i].GetValue(currentValue); + if (i == index) break; + } + return currentValue; + } + + public static object GetFinalValue(this IList list, object startingValue) + { + object currentValue = startingValue; + for (int i = 0; i < list.Count; i++) + { + currentValue = list[i].GetValue(currentValue); + } + return currentValue; + + } + + public static string GetPath(this IList list) + { + string path = ""; + for (int i = 0; i < list.Count; i++) + { + if (i > 0) path += "."; + path += list[i].Name; + } + return path; + } + + public static bool IsValid(this IList list) + { + return list.Any(x => x == null); + } + } +} \ No newline at end of file diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs.meta b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs.meta new file mode 100644 index 00000000..cd26b8f4 --- /dev/null +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6fbc650ecb8ca02faa22f7a9e5d9b4a0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 26dce966cb65ad2e497bc33fd22ca5039762236e Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 20:05:45 +0000 Subject: [PATCH 23/31] Also use ShowAsDrawer attribute --- .../DefaultNodes/Nodes/DrawerFieldTestNode.cs | 26 +++++++++---------- Assets/Testing/SequenceData.cs | 2 +- .../Editor/Views/BaseNodeView.cs | 4 +-- .../Runtime/Graph/Attributes.cs | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs b/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs index 2f80e584..5753595c 100644 --- a/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/DrawerFieldTestNode.cs @@ -8,46 +8,46 @@ public class DrawerFieldTestNode : BaseNode { - [Input(name = "Vector 4", showAsDrawer = true)] + [Input(name = "Vector 4"), ShowAsDrawer] public Vector4 vector4; - [Input(name = "Vector 3", showAsDrawer = true)] + [Input(name = "Vector 3"), ShowAsDrawer] public Vector3 vector3; - [Input(name = "Vector 2", showAsDrawer = true)] + [Input(name = "Vector 2"), ShowAsDrawer] public Vector2 vector2; - [Input(name = "Float", showAsDrawer = true)] + [Input(name = "Float"), ShowAsDrawer] public float floatInput; - [Input(name = "Vector 3 Int", showAsDrawer = true)] + [Input(name = "Vector 3 Int"), ShowAsDrawer] public Vector3Int vector3Int; - [Input(name = "Vector 2 Int", showAsDrawer = true)] + [Input(name = "Vector 2 Int"), ShowAsDrawer] public Vector2Int vector2Int; - [Input(name = "Int", showAsDrawer = true)] + [Input(name = "Int"), ShowAsDrawer] public int intInput; [Input(name = "Empty")] public int intInput2; - [Input(name = "String", showAsDrawer = true)] + [Input(name = "String"), ShowAsDrawer] public string stringInput; - [Input(name = "Color", showAsDrawer = true)] + [Input(name = "Color"), ShowAsDrawer] new public Color color; - [Input(name = "Game Object", showAsDrawer = true)] + [Input(name = "Game Object"), ShowAsDrawer] public GameObject gameObject; - [Input(name = "Animation Curve", showAsDrawer = true)] + [Input(name = "Animation Curve"), ShowAsDrawer] public AnimationCurve animationCurve; - [Input(name = "Rigidbody", showAsDrawer = true)] + [Input(name = "Rigidbody"), ShowAsDrawer] public Rigidbody rigidbody; - [Input("Layer Mask", showAsDrawer = true)] + [Input("Layer Mask"), ShowAsDrawer] public LayerMask layerMask; public override string name => "Drawer Field Test"; diff --git a/Assets/Testing/SequenceData.cs b/Assets/Testing/SequenceData.cs index 4358fc00..2130a6e5 100644 --- a/Assets/Testing/SequenceData.cs +++ b/Assets/Testing/SequenceData.cs @@ -15,7 +15,7 @@ public class SequenceName : List [Serializable] public class ConditionalName : BaseIsConditional { - [SerializeField, Input("Name", true)] string name; + [SerializeField, Input("Name"), ShowAsDrawer] string name; public string Name => name; } } diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 11c0acc9..a5b1a80e 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -728,7 +728,7 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect InputAttribute inputAttribute = field.GetCustomAttribute(); bool hasInputAttribute = inputAttribute != null; bool hasInputOrOutputAttribute = hasInputAttribute || field.HasCustomAttribute(); - bool showAsDrawer = !fromInspector && hasInputAttribute && inputAttribute.showAsDrawer; + bool showAsDrawer = !fromInspector && hasInputAttribute && (inputAttribute.showAsDrawer || field.HasCustomAttribute()); if ((!serializeField || isProxied) && hasInputOrOutputAttribute && !showAsDrawer) { AddEmptyField(field, fromInspector); @@ -752,7 +752,7 @@ protected virtual void DrawField(List fieldInfoList, bool fromInspect var showInputDrawer = hasInputAttribute && serializeField; - showInputDrawer |= hasInputAttribute && inputAttribute.showAsDrawer; + showInputDrawer |= showAsDrawer; showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType); diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs index 4150e1cf..339d7a1b 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs @@ -227,7 +227,7 @@ public ShowInInspector(bool showInNode = false) } } - [Obsolete("ShowAsDrawer attribute is deprecated. Please use the InputAttribute showAsDrawer field.")] + // [Obsolete("ShowAsDrawer attribute is deprecated. Please use the InputAttribute showAsDrawer field.")] [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] public class ShowAsDrawer : Attribute From e897ff5426e4d349d7ebf5118edf7cdbbc425756 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 20:23:33 +0000 Subject: [PATCH 24/31] Created Example Added GetNonRelayEdges List extensions --- .../Nodes/DynamicPortGeneration.meta} | 2 +- .../DynamicPortGeneration}/DynamicNode.cs | 26 +------- .../DynamicNode.cs.meta | 0 .../DynamicNodeWithOutput.cs | 0 .../DynamicNodeWithOutput.cs.meta | 0 .../Nodes/DynamicPortGeneration/Namer.cs | 14 ++++ .../DynamicPortGeneration/Namer.cs.meta} | 0 .../Nodes/DynamicPortGeneration/NamerNode.cs} | 5 +- .../DynamicPortGeneration/NamerNode.cs.meta} | 0 Assets/Testing/BaseIsConditional.cs | 35 ---------- Assets/Testing/BaseIsConditional.cs.meta | 11 ---- Assets/Testing/BoolVariable.cs | 12 ---- Assets/Testing/BoolVariable.cs.meta | 11 ---- .../DelegateEvent.VarObjectEventArgs.cs | 16 ----- .../DelegateEvent.VarObjectEventArgs.cs.meta | 11 ---- Assets/Testing/DelegateEvent.cs | 25 -------- Assets/Testing/ExpandableSOAttribute.cs | 15 ----- Assets/Testing/ExpandableSOAttribute.cs.meta | 11 ---- Assets/Testing/MyInputAttribute.cs | 22 ------- Assets/Testing/MyInputAttribute.cs.meta | 11 ---- Assets/Testing/PortUpdaterNode.cs | 12 ---- Assets/Testing/PortUpdaterNode.cs.meta | 11 ---- Assets/Testing/SequenceData.cs | 64 ------------------- Assets/Testing/SharedVariable.cs | 33 ---------- Assets/Testing/SharedVariable.cs.meta | 11 ---- .../Testing/ValueChangedCallbackAttribute.cs | 23 ------- .../ValueChangedCallbackAttribute.cs.meta | 11 ---- .../Runtime/Utils/FieldInfoExtension.cs | 2 - .../Runtime/Utils/SerializedEdgeExtension.cs | 28 ++++++++ .../Utils/SerializedEdgeExtension.cs.meta} | 2 +- 30 files changed, 48 insertions(+), 376 deletions(-) rename Assets/{Testing.meta => Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta} (77%) rename Assets/{Testing => Examples/DefaultNodes/Nodes/DynamicPortGeneration}/DynamicNode.cs (80%) rename Assets/{Testing => Examples/DefaultNodes/Nodes/DynamicPortGeneration}/DynamicNode.cs.meta (100%) rename Assets/{Testing => Examples/DefaultNodes/Nodes/DynamicPortGeneration}/DynamicNodeWithOutput.cs (100%) rename Assets/{Testing => Examples/DefaultNodes/Nodes/DynamicPortGeneration}/DynamicNodeWithOutput.cs.meta (100%) create mode 100644 Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs rename Assets/{Testing/SequenceData.cs.meta => Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs.meta} (100%) rename Assets/{Testing/ConditionalNameNode.cs => Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs} (52%) rename Assets/{Testing/ConditionalNameNode.cs.meta => Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs.meta} (100%) delete mode 100644 Assets/Testing/BaseIsConditional.cs delete mode 100644 Assets/Testing/BaseIsConditional.cs.meta delete mode 100644 Assets/Testing/BoolVariable.cs delete mode 100644 Assets/Testing/BoolVariable.cs.meta delete mode 100644 Assets/Testing/DelegateEvent.VarObjectEventArgs.cs delete mode 100644 Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta delete mode 100644 Assets/Testing/DelegateEvent.cs delete mode 100644 Assets/Testing/ExpandableSOAttribute.cs delete mode 100644 Assets/Testing/ExpandableSOAttribute.cs.meta delete mode 100644 Assets/Testing/MyInputAttribute.cs delete mode 100644 Assets/Testing/MyInputAttribute.cs.meta delete mode 100644 Assets/Testing/PortUpdaterNode.cs delete mode 100644 Assets/Testing/PortUpdaterNode.cs.meta delete mode 100644 Assets/Testing/SequenceData.cs delete mode 100644 Assets/Testing/SharedVariable.cs delete mode 100644 Assets/Testing/SharedVariable.cs.meta delete mode 100644 Assets/Testing/ValueChangedCallbackAttribute.cs delete mode 100644 Assets/Testing/ValueChangedCallbackAttribute.cs.meta create mode 100644 Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs rename Assets/{Testing/DelegateEvent.cs.meta => com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta} (83%) diff --git a/Assets/Testing.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta similarity index 77% rename from Assets/Testing.meta rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta index 56738015..0c62d05a 100644 --- a/Assets/Testing.meta +++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: db98db52a808b6a11bc0439ea50f7d72 +guid: d1aa8d1481699370f82eb69770a82239 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Testing/DynamicNode.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs similarity index 80% rename from Assets/Testing/DynamicNode.cs rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs index 38ea4318..a1141f5c 100644 --- a/Assets/Testing/DynamicNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs @@ -7,12 +7,11 @@ using System; [System.Serializable] -public abstract class DynamicNode : PortUpdaterNode +public abstract class DynamicNode : BaseNode { [Input("Action Data", true)] public Dictionary> actionData = new Dictionary>(); - [ExpandableSO, ValueChangedCallback(nameof(actionData), nameof(OnDataChanged))] public T data; public override bool needsInspector => true; @@ -38,20 +37,6 @@ protected virtual void UpdateActionWithCustomPortData() continue; } - if (field.inputAttribute is MyInputAttribute) - { - MyInputAttribute inputAttribute = field.inputAttribute as MyInputAttribute; - if (inputAttribute.InputType != null && field.fieldInfo.FieldType.GetInterfaces().Any(x => x == typeof(IList))) - { - IList list = Activator.CreateInstance(field.fieldInfo.FieldType) as IList; - foreach (var value in actionDataClone[field.fieldInfo.Name]) - list.Add(value); - - field.fieldInfo.SetValue(data, list); - continue; - } - } - field.fieldInfo.SetValue(data, actionDataClone[field.fieldInfo.Name][0]); } @@ -97,7 +82,7 @@ protected void PullInputs(List connectedEdges) FieldPortInfo field = GetFieldPortInfo(connectedEdges.ElementAt(0).inputPortIdentifier); if (actionData == null) actionData = new Dictionary>(); - foreach (var edge in connectedEdges.GetNonRelayEdges().OrderByInputAttribute(field.inputAttribute)) + foreach (var edge in connectedEdges) { if (!actionData.ContainsKey(field.fieldInfo.Name)) actionData.Add(field.fieldInfo.Name, new List()); @@ -113,13 +98,6 @@ protected IEnumerable ActionDataBehaviour(List edges { Type displayType = field.fieldInfo.FieldType; - if (field.inputAttribute is MyInputAttribute) - { - MyInputAttribute inputAttribute = field.inputAttribute as MyInputAttribute; - if (inputAttribute.InputType != null) - displayType = inputAttribute.InputType; - } - yield return new PortData { displayName = field.inputAttribute.name, diff --git a/Assets/Testing/DynamicNode.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs.meta similarity index 100% rename from Assets/Testing/DynamicNode.cs.meta rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs.meta diff --git a/Assets/Testing/DynamicNodeWithOutput.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs similarity index 100% rename from Assets/Testing/DynamicNodeWithOutput.cs rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs diff --git a/Assets/Testing/DynamicNodeWithOutput.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs.meta similarity index 100% rename from Assets/Testing/DynamicNodeWithOutput.cs.meta rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNodeWithOutput.cs.meta diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs new file mode 100644 index 00000000..6b2b4820 --- /dev/null +++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using GraphProcessor; +using UnityEngine; + +[Serializable] +public class Namer +{ + [SerializeField, Input("Name"), ShowAsDrawer] string name; + [SerializeField, Input("Bool")] bool value; +} \ No newline at end of file diff --git a/Assets/Testing/SequenceData.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs.meta similarity index 100% rename from Assets/Testing/SequenceData.cs.meta rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/Namer.cs.meta diff --git a/Assets/Testing/ConditionalNameNode.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs similarity index 52% rename from Assets/Testing/ConditionalNameNode.cs rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs index 3eb7cc5f..32a86ea3 100644 --- a/Assets/Testing/ConditionalNameNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs @@ -3,10 +3,9 @@ using UnityEngine; using GraphProcessor; using System.Linq; -using static SequenceName; -[System.Serializable, NodeMenuItem("Custom/ConditionalNameNode")] -public class ConditionalNameNode : DynamicNodeWithOutput +[System.Serializable, NodeMenuItem("Custom/ProxiedInputsNode")] +public class NamerNode : DynamicNodeWithOutput { public override string name => "ConditionalNameNode"; } diff --git a/Assets/Testing/ConditionalNameNode.cs.meta b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs.meta similarity index 100% rename from Assets/Testing/ConditionalNameNode.cs.meta rename to Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/NamerNode.cs.meta diff --git a/Assets/Testing/BaseIsConditional.cs b/Assets/Testing/BaseIsConditional.cs deleted file mode 100644 index 5ae6efb3..00000000 --- a/Assets/Testing/BaseIsConditional.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using GraphProcessor; -using UnityEngine; - -[System.Serializable] -public class BaseIsConditional -{ - [SerializeField, Input("True Checks", false, true)] protected BoolVariable[] trueChecks; - public BoolVariable[] TrueChecks { get => trueChecks; } - - [SerializeField, Input("False Checks", false, true)] protected BoolVariable[] falseChecks; - public BoolVariable[] FalseChecks { get => falseChecks; } - - public bool IsAvailable() - { - foreach (BoolVariable check in trueChecks) - { - if (!check.RuntimeValue) return false; - } - - foreach (BoolVariable check in falseChecks) - { - if (check.RuntimeValue) return false; - } - return true; - } -} - -public interface IIsConditional -{ - List TrueChecks { get; } - List FalseChecks { get; } - - bool IsAvailable(); -} \ No newline at end of file diff --git a/Assets/Testing/BaseIsConditional.cs.meta b/Assets/Testing/BaseIsConditional.cs.meta deleted file mode 100644 index 7f07e8bd..00000000 --- a/Assets/Testing/BaseIsConditional.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 9947347622c831651bc3a306795104fa -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/BoolVariable.cs b/Assets/Testing/BoolVariable.cs deleted file mode 100644 index 01b8eb90..00000000 --- a/Assets/Testing/BoolVariable.cs +++ /dev/null @@ -1,12 +0,0 @@ -using UnityEngine; - -[CreateAssetMenu(fileName = "BoolVariable", menuName = "Teletext/Variables/Bool")] -public class BoolVariable : SharedVariable -{ - public override bool RuntimeValue { get => GetEvaluation(); set => base.RuntimeValue = value; } - - protected virtual bool GetEvaluation() - { - return base.RuntimeValue; - } -} diff --git a/Assets/Testing/BoolVariable.cs.meta b/Assets/Testing/BoolVariable.cs.meta deleted file mode 100644 index 0d4d6ccf..00000000 --- a/Assets/Testing/BoolVariable.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c2ed7d6a9e49992bca5a969a1db59655 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs deleted file mode 100644 index 7a5d32ec..00000000 --- a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -public partial class DelegateEvent -{ - public class VarObjectEventArgs : EventArgs - { - private T value; - public T Value => value; - - public VarObjectEventArgs(T value) - { - this.value = value; - } - } -} - diff --git a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta b/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta deleted file mode 100644 index dfe97910..00000000 --- a/Assets/Testing/DelegateEvent.VarObjectEventArgs.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 1d0825e47caa8faf0b509d6413c06002 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/DelegateEvent.cs b/Assets/Testing/DelegateEvent.cs deleted file mode 100644 index 392b04e9..00000000 --- a/Assets/Testing/DelegateEvent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -public partial class DelegateEvent : ScriptableObject -{ - public delegate void EventHandler(object sender, VarObjectEventArgs args); - public event EventHandler Event; - - public void AddListener(EventHandler listener) - { - Event += listener; - } - - public void RemoveListener(EventHandler listener) - { - Event -= listener; - } - - public void Raise(T argument) - { - if (Event != null) - Event.Invoke(this, new VarObjectEventArgs(argument)); - } -} diff --git a/Assets/Testing/ExpandableSOAttribute.cs b/Assets/Testing/ExpandableSOAttribute.cs deleted file mode 100644 index 00c2b219..00000000 --- a/Assets/Testing/ExpandableSOAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using UnityEditor; -using UnityEngine; - -/// -/// Attribute takes the insides of a set ScriptableObject and display it. -/// -public class ExpandableSOAttribute : PropertyAttribute -{ - - /// - /// Required implementation of the interface. - /// - public ExpandableSOAttribute() { } -} diff --git a/Assets/Testing/ExpandableSOAttribute.cs.meta b/Assets/Testing/ExpandableSOAttribute.cs.meta deleted file mode 100644 index 198eb13f..00000000 --- a/Assets/Testing/ExpandableSOAttribute.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: bf7bd1d3b461c28ca869d43343391f92 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/MyInputAttribute.cs b/Assets/Testing/MyInputAttribute.cs deleted file mode 100644 index d9b488d4..00000000 --- a/Assets/Testing/MyInputAttribute.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using GraphProcessor; -using UnityEngine; - -[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] -public class MyInputAttribute : InputAttribute -{ - public readonly Type InputType; - public readonly InputSortType SortType; - - public MyInputAttribute(string name = null, bool allowMultiple = false, InputSortType sortType = InputSortType.FIRST_IN, Type inputType = null) - { - this.name = name; - this.allowMultiple = allowMultiple; - this.SortType = sortType; - this.InputType = inputType; - } -} - -public enum InputSortType { FIRST_IN, POSITION_Y } diff --git a/Assets/Testing/MyInputAttribute.cs.meta b/Assets/Testing/MyInputAttribute.cs.meta deleted file mode 100644 index 0da6a56d..00000000 --- a/Assets/Testing/MyInputAttribute.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 0189acdd3b39929d6a8b0a57c3e507bd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/PortUpdaterNode.cs b/Assets/Testing/PortUpdaterNode.cs deleted file mode 100644 index 5e4cb1ce..00000000 --- a/Assets/Testing/PortUpdaterNode.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Diagnostics; -using System.Reflection; -using GraphProcessor; -using UnityEngine; - -public abstract class PortUpdaterNode : BaseNode -{ - protected virtual void OnDataChanged(FieldInfo originField, UnityEditor.SerializedProperty serializedProperty) - { - UpdatePortsForFieldLocal(originField.Name); - } -} diff --git a/Assets/Testing/PortUpdaterNode.cs.meta b/Assets/Testing/PortUpdaterNode.cs.meta deleted file mode 100644 index 6aaa9538..00000000 --- a/Assets/Testing/PortUpdaterNode.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 67b3cddba608059109efbb060f6504fe -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/SequenceData.cs b/Assets/Testing/SequenceData.cs deleted file mode 100644 index 2130a6e5..00000000 --- a/Assets/Testing/SequenceData.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Reflection; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using GraphProcessor; -using UnityEngine; - - -[Serializable] -public class SequenceName : List -{ - public string CurrentName => FindLast(x => x.IsAvailable()).Name; - - [Serializable] - public class ConditionalName : BaseIsConditional - { - [SerializeField, Input("Name"), ShowAsDrawer] string name; - public string Name => name; - } -} - -public static class ListHelpers -{ - public static string GetCurrentName(this List list) - { - return list.FindLast(x => x.IsAvailable()).Name; - } - - public static IList OrderByInputAttribute(this IList edges, InputAttribute inputAttribute) - { - if (inputAttribute is MyInputAttribute) - { - switch ((inputAttribute as MyInputAttribute).SortType) - { - case InputSortType.POSITION_Y: - edges = edges.OrderBy(x => x.outputNode.position.y).ToList(); - break; - } - } - return edges; - } - - public static IList GetNonRelayEdges(this IList edges) - { - List nonrelayEdges = new List(); - foreach (var edge in edges) - { - if (edge.outputNode is RelayNode) - { - RelayNode relay = edge.outputNode as RelayNode; - foreach (var relayEdge in relay.GetNonRelayEdges()) - { - nonrelayEdges.Add(relayEdge); - } - } - else - { - nonrelayEdges.Add(edge); - } - } - return nonrelayEdges; - } -} \ No newline at end of file diff --git a/Assets/Testing/SharedVariable.cs b/Assets/Testing/SharedVariable.cs deleted file mode 100644 index 47d04810..00000000 --- a/Assets/Testing/SharedVariable.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Data.SqlTypes; -using UnityEngine; - -/// -/// -/// CallbackObject callback is used as a OnValueChanged event. -/// -/// -public abstract class SharedVariable : DelegateEvent -{ - [SerializeField] protected T InitialValue; - - protected T runtimeValue; - public virtual T RuntimeValue - { - get => runtimeValue; - set - { - if (runtimeValue == null && value == null) return; - else if (runtimeValue != null && runtimeValue.Equals(value)) return; - else if (value != null && value.Equals(runtimeValue)) return; - - runtimeValue = value; - Raise(value); - } - } - - // Initialize runtime value with editor's value - protected virtual void OnEnable() - { - RuntimeValue = InitialValue; - } -} \ No newline at end of file diff --git a/Assets/Testing/SharedVariable.cs.meta b/Assets/Testing/SharedVariable.cs.meta deleted file mode 100644 index a4acfa26..00000000 --- a/Assets/Testing/SharedVariable.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4ace1829c0b27489fa70482f81ddbed7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Testing/ValueChangedCallbackAttribute.cs b/Assets/Testing/ValueChangedCallbackAttribute.cs deleted file mode 100644 index 792f2d0e..00000000 --- a/Assets/Testing/ValueChangedCallbackAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using UnityEditor; -using UnityEngine; -/// -/// Check if a given field has changed and if it has calls a method. -/// -/// -public class ValueChangedCallbackAttribute : PropertyAttribute -{ - string fieldName; - public string FieldName => fieldName; - - string methodName; - public string MethodName => methodName; - - /// - /// - public ValueChangedCallbackAttribute(string fieldName, string methodName) - { - this.fieldName = fieldName; - this.methodName = methodName; - } -} \ No newline at end of file diff --git a/Assets/Testing/ValueChangedCallbackAttribute.cs.meta b/Assets/Testing/ValueChangedCallbackAttribute.cs.meta deleted file mode 100644 index f59e492c..00000000 --- a/Assets/Testing/ValueChangedCallbackAttribute.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e9b5389fba495b15191a31e041bfedba -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs index 4b423dcc..b9e975de 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs @@ -1,9 +1,7 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; -using UnityEngine; namespace GraphProcessor { diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs new file mode 100644 index 00000000..4892283a --- /dev/null +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace GraphProcessor +{ + public static class SerializedEdgeExtension + { + public static IList GetNonRelayEdges(this IList edges) + { + List nonrelayEdges = new List(); + foreach (var edge in edges) + { + if (edge.outputNode is RelayNode) + { + RelayNode relay = edge.outputNode as RelayNode; + foreach (var relayEdge in relay.GetNonRelayEdges()) + { + nonrelayEdges.Add(relayEdge); + } + } + else + { + nonrelayEdges.Add(edge); + } + } + return nonrelayEdges; + } + } +} diff --git a/Assets/Testing/DelegateEvent.cs.meta b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta similarity index 83% rename from Assets/Testing/DelegateEvent.cs.meta rename to Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta index 61429f8c..13097a7b 100644 --- a/Assets/Testing/DelegateEvent.cs.meta +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/SerializedEdgeExtension.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b336ffaa1cc79bc929414981fe23fab5 +guid: 1b6986467dd851f8b8153d3bf6b93994 MonoImporter: externalObjects: {} serializedVersion: 2 From e91597b59a4c2e4066abaf53d0859a61b0ff9659 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Thu, 3 Feb 2022 20:36:17 +0000 Subject: [PATCH 25/31] Added IsProxied property to portdata --- .../Editor/Views/BaseNodeView.cs | 10 ++++------ .../Runtime/Elements/NodePort.cs | 2 ++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index a5b1a80e..7519bd5b 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -689,12 +689,10 @@ protected virtual void DrawDefaultInspector(bool fromInspector = false) FieldInfo field = fields[i]; if (field.HasCustomAttribute() && portsPerFieldName.ContainsKey(field.Name)) { - // IF FIELD IS MULTI_PORT DO THIS LOOP OVER THIS WITH RELATED PORTS AND IGNORE THE BASE PORT foreach (var port in portsPerFieldName[field.Name]) { - bool isProxied = !String.IsNullOrEmpty(port.portData.proxiedFieldPath); - string fieldPath = isProxied ? port.portData.proxiedFieldPath : port.fieldName; - DrawField(GetFieldInfoPath(fieldPath), fromInspector, isProxied); + string fieldPath = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName; + DrawField(GetFieldInfoPath(fieldPath), fromInspector, port.portData.IsProxied); } } else @@ -1004,7 +1002,7 @@ protected void AddSettingField(FieldInfo field) internal void OnPortConnected(PortView port) { - string fieldName = !String.IsNullOrEmpty(port.portData.proxiedFieldPath) ? port.portData.proxiedFieldPath : port.fieldName; + string fieldName = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName; if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null) inputContainerElement.Q(fieldName).AddToClassList("empty"); @@ -1017,7 +1015,7 @@ internal void OnPortConnected(PortView port) internal void OnPortDisconnected(PortView port) // { - string fieldName = !String.IsNullOrEmpty(port.portData.proxiedFieldPath) ? port.portData.proxiedFieldPath : port.fieldName; + string fieldName = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName; if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null) { diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs index c852fa46..0dff9976 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/NodePort.cs @@ -52,6 +52,8 @@ public class PortData : IEquatable /// public bool vertical; + public bool IsProxied => !String.IsNullOrEmpty(proxiedFieldPath); + public bool Equals(PortData other) { return identifier == other.identifier From 9ea07fed65464b5b514bc41f8e2f6286eff14056 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Fri, 4 Feb 2022 01:57:30 +0000 Subject: [PATCH 26/31] Fixed couple bugs --- .../DynamicPortGeneration/DynamicNode.cs | 2 +- .../DefaultNodes/Nodes/FloatToStringNode.cs | 37 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs index a1141f5c..dd9a2e0d 100644 --- a/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/DynamicPortGeneration/DynamicNode.cs @@ -30,7 +30,7 @@ protected virtual void UpdateActionWithCustomPortData() { if (!actionDataClone.ContainsKey(field.fieldInfo.Name)) { - if (field.inputAttribute.showAsDrawer) + if (field.inputAttribute.showAsDrawer || field.fieldInfo.HasCustomAttribute()) continue; field.fieldInfo.SetValue(data, default); diff --git a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs index 7613d680..d4a401b9 100644 --- a/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs +++ b/Assets/Examples/DefaultNodes/Nodes/FloatToStringNode.cs @@ -6,29 +6,28 @@ [Serializable, NodeMenuItem("Convert/Float to String"), ConverterNode(typeof(float), typeof(string))] public class FloatToStringsNode : BaseNode, IConversionNode { - [Input("In")] - public float input; + [Input("In")] + public float input; - public int decimalPlaces = 2; + public int decimalPlaces = 2; - [Output("Out")] - public string output; + [Output("Out")] + public string output; - public override string name => "To String"; + public override string name => "To String"; - public string GetConversionInput() - { - return nameof(input); - } + public string GetConversionInput() + { + return nameof(input); + } - public string GetConversionOutput() - { - return nameof(output); - } + public string GetConversionOutput() + { + return nameof(output); + } - protected override void Process() - { - output = input.ToString("F" + decimalPlace, CultureInfo.InvariantCulture); - output = val.ToString(CultureInfo.InvariantCulture); - } + protected override void Process() + { + output = input.ToString("F" + decimalPlaces, CultureInfo.InvariantCulture); + } } From 017e65255d884dd5d276da440e4561aab70307d5 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Fri, 4 Feb 2022 18:28:05 +0000 Subject: [PATCH 27/31] Flip Attribute Drawer for back compat --- Assets/Examples/BasicExample.asset | 159 ++++++++++-------- .../Runtime/Graph/Attributes.cs | 4 +- 2 files changed, 93 insertions(+), 70 deletions(-) diff --git a/Assets/Examples/BasicExample.asset b/Assets/Examples/BasicExample.asset index df80ce1f..5342cefc 100644 --- a/Assets/Examples/BasicExample.asset +++ b/Assets/Examples/BasicExample.asset @@ -14,30 +14,31 @@ MonoBehaviour: m_EditorClassIdentifier: serializedNodes: [] nodes: - - id: 0 - - id: 1 - - id: 2 - - id: 3 - - id: 4 - - id: 5 - - id: 6 - - id: 7 - - id: 8 - - id: 9 - - id: 10 - - id: 11 - - id: 12 - - id: 13 - - id: 14 - - id: 15 - - id: 16 - - id: 17 - - id: 18 - - id: 19 - - id: 20 - - id: 21 - - id: 22 - - id: 23 + - rid: 0 + - rid: 1 + - rid: 2 + - rid: 3 + - rid: 4 + - rid: 5 + - rid: 6 + - rid: 7 + - rid: 8 + - rid: 9 + - rid: 10 + - rid: 11 + - rid: 12 + - rid: 13 + - rid: 14 + - rid: 15 + - rid: 16 + - rid: 17 + - rid: 18 + - rid: 19 + - rid: 20 + - rid: 21 + - rid: 22 + - rid: 23 + - rid: 3708072270747926535 edges: - GUID: 04cee6c7-b233-40e1-b41a-31f6093f1482 owner: {fileID: 11400000} @@ -165,7 +166,7 @@ MonoBehaviour: size: {x: 300, y: 100} innerNodeGUIDs: [] stackNodes: - - id: 24 + - rid: 24 pinnedElements: - position: serializedVersion: 2 @@ -208,7 +209,7 @@ MonoBehaviour: serializedType: GraphProcessor.ProcessorView, com.alelievr.NodeGraphProcessor.Editor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null exposedParameters: - - id: 25 + - rid: 25 serializedParameterList: - guid: name: @@ -219,7 +220,7 @@ MonoBehaviour: serializedValue: input: 1 settings: - id: 26 + rid: -2 - guid: name: type: @@ -229,7 +230,7 @@ MonoBehaviour: serializedValue: input: 1 settings: - id: 26 + rid: -2 - guid: name: type: @@ -239,7 +240,7 @@ MonoBehaviour: serializedValue: input: 1 settings: - id: 26 + rid: -2 - guid: name: type: @@ -249,7 +250,7 @@ MonoBehaviour: serializedValue: input: 1 settings: - id: 26 + rid: -2 - guid: name: type: @@ -259,7 +260,7 @@ MonoBehaviour: serializedValue: input: 1 settings: - id: 26 + rid: -2 - guid: name: type: @@ -269,7 +270,7 @@ MonoBehaviour: serializedValue: input: 1 settings: - id: 26 + rid: -2 - guid: name: type: @@ -279,7 +280,7 @@ MonoBehaviour: serializedValue: input: 1 settings: - id: 26 + rid: -2 - guid: name: type: @@ -289,7 +290,7 @@ MonoBehaviour: serializedValue: input: 1 settings: - id: 26 + rid: -2 - guid: name: type: @@ -299,7 +300,7 @@ MonoBehaviour: serializedValue: input: 1 settings: - id: 26 + rid: -2 stickyNotes: - position: serializedVersion: 2 @@ -318,11 +319,14 @@ MonoBehaviour: title: New Sticky Note content: Write your text here nodeInspectorReference: {fileID: 0} - position: {x: 781, y: -9.999998, z: 0} - scale: {x: 0.7561437, y: 0.7561437, z: 1} + position: {x: 854, y: -5, z: 0} + scale: {x: 0.65751624, y: 0.65751624, z: 1} references: - version: 1 - 00000000: + version: 2 + RefIds: + - rid: -2 + type: {class: , ns: , asm: } + - rid: 0 type: {class: ColorNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -338,7 +342,7 @@ MonoBehaviour: debug: 0 nodeLock: 0 color: {r: 1, g: 0, b: 0.25098038, a: 1} - 00000001: + - rid: 1 type: {class: FloatNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -355,7 +359,7 @@ MonoBehaviour: nodeLock: 0 output: 10 input: 10 - 00000002: + - rid: 2 type: {class: MultiAddNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -371,7 +375,7 @@ MonoBehaviour: debug: 1 nodeLock: 0 output: 10 - 00000003: + - rid: 3 type: {class: SubNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -389,7 +393,7 @@ MonoBehaviour: inputA: 0 inputB: 140 output: -140 - 00000004: + - rid: 4 type: {class: PrintNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -404,7 +408,7 @@ MonoBehaviour: expanded: 0 debug: 1 nodeLock: 0 - 00000005: + - rid: 5 type: {class: PrintNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -419,7 +423,7 @@ MonoBehaviour: expanded: 0 debug: 0 nodeLock: 0 - 00000006: + - rid: 6 type: {class: PrefabNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -436,7 +440,7 @@ MonoBehaviour: nodeLock: 0 output: {fileID: 1636575971871760, guid: f78111bdbdeaf6644806fc49fcaf1d30, type: 3} - 00000007: + - rid: 7 type: {class: FloatNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -453,7 +457,7 @@ MonoBehaviour: nodeLock: 0 output: 140 input: 140 - 00000008: + - rid: 8 type: {class: TextNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -469,7 +473,7 @@ MonoBehaviour: debug: 0 nodeLock: 0 output: Hello World - 00000009: + - rid: 9 type: {class: PrintNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -484,7 +488,7 @@ MonoBehaviour: expanded: 0 debug: 0 nodeLock: 0 - 0000000A: + - rid: 10 type: {class: FloatNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -501,7 +505,7 @@ MonoBehaviour: nodeLock: 0 output: 140 input: 140 - 0000000B: + - rid: 11 type: {class: SettingsNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -519,7 +523,7 @@ MonoBehaviour: setting: 0 input: 0 output: 0 - 0000000C: + - rid: 12 type: {class: PrintNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -534,7 +538,7 @@ MonoBehaviour: expanded: 0 debug: 0 nodeLock: 0 - 0000000D: + - rid: 13 type: {class: FloatNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -551,7 +555,7 @@ MonoBehaviour: nodeLock: 0 output: 0 input: 0 - 0000000E: + - rid: 14 type: {class: CustomPortData, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -567,7 +571,7 @@ MonoBehaviour: debug: 0 nodeLock: 0 output: 0 - 0000000F: + - rid: 15 type: {class: PrintNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -582,7 +586,7 @@ MonoBehaviour: expanded: 0 debug: 0 nodeLock: 0 - 00000010: + - rid: 16 type: {class: PortConnectionTests, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -598,7 +602,7 @@ MonoBehaviour: debug: 0 nodeLock: 0 padding: 0 - 00000011: + - rid: 17 type: {class: PortConnectionTests, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -614,7 +618,7 @@ MonoBehaviour: debug: 0 nodeLock: 0 padding: 0 - 00000012: + - rid: 18 type: {class: DrawerFieldTestNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -650,7 +654,7 @@ MonoBehaviour: layerMask: serializedVersion: 2 m_Bits: 0 - 00000013: + - rid: 19 type: {class: InspectorNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -669,7 +673,7 @@ MonoBehaviour: output: 0 additionalSettings: 0 additionalParam: - 00000014: + - rid: 20 type: {class: DrawerFieldTestNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -705,7 +709,7 @@ MonoBehaviour: layerMask: serializedVersion: 2 m_Bits: 0 - 00000015: + - rid: 21 type: {class: FloatNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -722,7 +726,7 @@ MonoBehaviour: nodeLock: 0 output: 42 input: 42 - 00000016: + - rid: 22 type: {class: ColorNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -738,7 +742,7 @@ MonoBehaviour: debug: 0 nodeLock: 0 color: {r: 0.47539377, g: 0, b: 1, a: 0} - 00000017: + - rid: 23 type: {class: MultiAddNode, ns: , asm: Assembly-CSharp} data: nodeCustomName: @@ -754,7 +758,7 @@ MonoBehaviour: debug: 0 nodeLock: 0 output: 0 - 00000018: + - rid: 24 type: {class: BaseStackNode, ns: GraphProcessor, asm: com.alelievr.NodeGraphProcessor.Runtime} data: position: {x: 914.0975, y: 6.5799994} @@ -765,7 +769,7 @@ MonoBehaviour: - ffd2cf4b-87c3-42a6-9822-04bae7a5700b - 42eb43b8-ac7e-4f38-b49b-26ba1bc42732 - e99da4fb-6a11-4b19-8594-f37f55d96114 - 00000019: + - rid: 25 type: {class: FloatParameter, ns: GraphProcessor, asm: com.alelievr.NodeGraphProcessor.Runtime} data: guid: eb80df62-f248-4ec9-afd4-f9ed08bfaa16 @@ -777,11 +781,9 @@ MonoBehaviour: serializedValue: input: 1 settings: - id: 27 + rid: 27 val: 0 - 0000001A: - type: {class: , ns: , asm: } - 0000001B: + - rid: 27 type: {class: FloatParameter/FloatSettings, ns: GraphProcessor, asm: com.alelievr.NodeGraphProcessor.Runtime} data: isHidden: 0 @@ -790,3 +792,24 @@ MonoBehaviour: mode: 0 min: 0 max: 1 + - rid: 3708072270747926535 + type: {class: NamerNode, ns: , asm: Assembly-CSharp} + data: + nodeCustomName: + GUID: c8d89035-6a0f-4046-8735-16c760df589b + computeOrder: 24 + position: + serializedVersion: 2 + x: -507.4715 + y: 334.4306 + width: 180 + height: 156 + expanded: 0 + debug: 0 + nodeLock: 0 + data: + name: esfesfesf + value: 0 + dataOutput: + name: esfesfesf + value: 0 diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs index 339d7a1b..4dae226b 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Graph/Attributes.cs @@ -20,11 +20,11 @@ public class InputAttribute : Attribute /// /// display name /// is connecting multiple edges allowed - public InputAttribute(string name = null, bool showAsDrawer = false, bool allowMultiple = false) + public InputAttribute(string name = null, bool allowMultiple = false, bool showAsDrawer = false) { this.name = name; - this.showAsDrawer = showAsDrawer; this.allowMultiple = allowMultiple; + this.showAsDrawer = showAsDrawer; } } From 9ccc91e9a607cfe009454d7b08c717dc6670167c Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Fri, 4 Feb 2022 19:05:50 +0000 Subject: [PATCH 28/31] Added Undo Functionality Added FieldInfoWithPath as it easier to work with --- .../Editor/Views/BaseNodeView.cs | 74 ++++++++++++++----- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 9a1dceee..259129b7 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -664,7 +664,7 @@ public virtual void Disable() { } Dictionary> visibleConditions = new Dictionary>(); Dictionary hideElementIfConnected = new Dictionary(); - Dictionary> fieldControlsMap = new Dictionary>(); + Dictionary> fieldControlsMap = new Dictionary>(); protected void AddInputContainer() { @@ -692,20 +692,20 @@ protected virtual void DrawDefaultInspector(bool fromInspector = false) foreach (var port in portsPerFieldName[field.Name]) { string fieldPath = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName; - DrawField(GetFieldInfoPath(fieldPath), fromInspector, port.portData.IsProxied); + DrawField(new FieldInfoWithPath(GetFieldInfoPath(fieldPath)), fromInspector, port.portData.IsProxied); } } else { - DrawField(new List { field }, fromInspector); + DrawField(new FieldInfoWithPath(field), fromInspector); } } } - protected virtual void DrawField(List fieldInfoList, bool fromInspector, bool isProxied = false) + protected virtual void DrawField(FieldInfoWithPath fieldInfoWithPath, bool fromInspector, bool isProxied = false) { - FieldInfo field = fieldInfoList.Last(); - string fieldPath = fieldInfoList.GetPath(); + FieldInfo field = fieldInfoWithPath.Field; + string fieldPath = fieldInfoWithPath.Path; //skip if the field is a node setting if (field.HasCustomAttribute()) @@ -825,7 +825,7 @@ void UpdateFieldVisibility(string fieldName, object newValue) } } - void UpdateOtherFieldValueSpecific(FieldInfo field, object newValue) + void UpdateOtherFieldValueSpecific(FieldInfoWithPath field, object newValue) { foreach (var inputField in fieldControlsMap[field]) { @@ -836,16 +836,16 @@ void UpdateOtherFieldValueSpecific(FieldInfo field, object newValue) } static MethodInfo specificUpdateOtherFieldValue = typeof(BaseNodeView).GetMethod(nameof(UpdateOtherFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance); - void UpdateOtherFieldValue(FieldInfo info, object newValue) + void UpdateOtherFieldValue(FieldInfoWithPath info, object newValue) { // Warning: Keep in sync with FieldFactory CreateField - var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType; + var fieldType = info.Field.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.Field.FieldType; var genericUpdate = specificUpdateOtherFieldValue.MakeGenericMethod(fieldType); genericUpdate.Invoke(this, new object[] { info, newValue }); } - object GetInputFieldValueSpecific(FieldInfo field) + object GetInputFieldValueSpecific(FieldInfoWithPath field) { if (fieldControlsMap.TryGetValue(field, out var list)) { @@ -871,7 +871,7 @@ object GetInputFieldValue(FieldInfo info) protected VisualElement AddControlField(string fieldPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) { List fieldInfoPath = GetFieldInfoPath(fieldPath); - return AddControlField(fieldInfoPath, label, showInputDrawer, valueChangedCallback); + return AddControlField(new FieldInfoWithPath(fieldInfoPath.Last(), fieldPath), label, showInputDrawer, valueChangedCallback); } Regex s_ReplaceNodeIndexPropertyPath = new Regex(@"(^nodes.Array.data\[)(\d+)(\])"); internal void SyncSerializedPropertyPathes() @@ -902,14 +902,15 @@ protected SerializedProperty FindSerializedProperty(string fieldName) return owner.serializedGraph.FindProperty("nodes").GetArrayElementAtIndex(i).FindPropertyRelative(fieldName); } - protected VisualElement AddControlField(List fieldInfoPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) + protected VisualElement AddControlField(FieldInfoWithPath fieldInfoWithPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) { - var field = fieldInfoPath.Last(); // + var field = fieldInfoWithPath.Field; + var fieldPath = fieldInfoWithPath.Path; - if (fieldInfoPath == null && fieldInfoPath.IsValid()) + if (field == null) return null; - var element = new PropertyField(FindSerializedProperty(fieldInfoPath.GetPath()), showInputDrawer ? "" : label); + var element = new PropertyField(FindSerializedProperty(fieldPath), showInputDrawer ? "" : label); element.Bind(owner.serializedGraph); #if UNITY_2020_3 // In Unity 2020.3 the empty label on property field doesn't hide it, so we do it manually @@ -922,7 +923,7 @@ protected VisualElement AddControlField(List fieldInfoPath, string la element.RegisterValueChangeCallback(e => { - UpdateFieldVisibility(field.Name, fieldInfoPath.GetFinalValue(nodeTarget)); + UpdateFieldVisibility(field.Name, GetFieldInfoPath(fieldPath).GetFinalValue(nodeTarget)); valueChangedCallback?.Invoke(); NotifyNodeChanged(); }); @@ -935,8 +936,8 @@ protected VisualElement AddControlField(List fieldInfoPath, string la objectField.allowSceneObjects = false; } - if (!fieldControlsMap.TryGetValue(field, out var inputFieldList)) - inputFieldList = fieldControlsMap[field] = new List(); + if (!fieldControlsMap.TryGetValue(fieldInfoWithPath, out var inputFieldList)) + inputFieldList = fieldControlsMap[fieldInfoWithPath] = new List(); inputFieldList.Add(element); if (element != null) @@ -983,7 +984,7 @@ protected VisualElement AddControlField(List fieldInfoPath, string la void UpdateFieldValues() { foreach (var kp in fieldControlsMap) - UpdateOtherFieldValue(kp.Key, kp.Key.GetValue(nodeTarget)); + UpdateOtherFieldValue(kp.Key, GetFieldInfoPath(kp.Key.Path).GetFinalValue(nodeTarget)); } protected void AddSettingField(FieldInfo field) @@ -1237,4 +1238,37 @@ void UpdatePortsForField(string fieldName) #endregion } -} \ No newline at end of file + + public class FieldInfoWithPath : IEquatable + { + private FieldInfo field; + public FieldInfo Field => field; + private string path; + public string Path => path; + + public FieldInfoWithPath(FieldInfo field, string path) + { + this.field = field; + this.path = path; + } + + public FieldInfoWithPath(List fieldInfos) + { + this.field = fieldInfos.Last(); + this.path = fieldInfos.GetPath(); + } + + public FieldInfoWithPath(FieldInfo field) + { + this.field = field; + this.path = field.Name; + } + + public bool Equals(FieldInfoWithPath other) + { + return field == other.field + && path == other.path; + } + } +} + From 3af59fea219b50aaa1371e32676194c40d7f8742 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Sat, 5 Feb 2022 00:33:54 +0000 Subject: [PATCH 29/31] Fixed port disconnect --- .../Editor/Views/BaseNodeView.cs | 72 +++++++++++-------- .../Runtime/Utils/FieldInfoExtension.cs | 14 ++++ 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs index 259129b7..551e4601 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseNodeView.cs @@ -692,7 +692,7 @@ protected virtual void DrawDefaultInspector(bool fromInspector = false) foreach (var port in portsPerFieldName[field.Name]) { string fieldPath = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName; - DrawField(new FieldInfoWithPath(GetFieldInfoPath(fieldPath)), fromInspector, port.portData.IsProxied); + DrawField(new FieldInfoWithPath(FieldInfoWithPath.GetFieldInfoPath(fieldPath, nodeTarget)), fromInspector, port.portData.IsProxied); } } else @@ -772,23 +772,6 @@ protected virtual void DrawField(FieldInfoWithPath fieldInfoWithPath, bool fromI } } - private List GetFieldInfoPath(string path) - { - string[] pathArray = path.Split('.'); - List fieldInfoPath = new List(); - object value = nodeTarget; - for (int i = 0; i < pathArray.Length; i++) - { - // Debug.Log(pathArray[i]); - fieldInfoPath.Add(value.GetType().GetField(pathArray[i], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)); - if (i + 1 < pathArray.Length) - { - value = fieldInfoPath[i].GetValue(value); - } - } - return fieldInfoPath; - } - protected virtual void SetNodeColor(Color color) { titleContainer.style.borderBottomColor = new StyleColor(color); @@ -859,10 +842,10 @@ object GetInputFieldValueSpecific(FieldInfoWithPath field) } static MethodInfo specificGetValue = typeof(BaseNodeView).GetMethod(nameof(GetInputFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance); - object GetInputFieldValue(FieldInfo info) + object GetInputFieldValue(FieldInfoWithPath info) { // Warning: Keep in sync with FieldFactory CreateField - var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType; + var fieldType = info.Field.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.Field.FieldType; var genericUpdate = specificGetValue.MakeGenericMethod(fieldType); return genericUpdate.Invoke(this, new object[] { info }); @@ -870,7 +853,7 @@ object GetInputFieldValue(FieldInfo info) protected VisualElement AddControlField(string fieldPath, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null) { - List fieldInfoPath = GetFieldInfoPath(fieldPath); + List fieldInfoPath = FieldInfoWithPath.GetFieldInfoPath(fieldPath, nodeTarget); return AddControlField(new FieldInfoWithPath(fieldInfoPath.Last(), fieldPath), label, showInputDrawer, valueChangedCallback); } Regex s_ReplaceNodeIndexPropertyPath = new Regex(@"(^nodes.Array.data\[)(\d+)(\])"); @@ -923,7 +906,7 @@ protected VisualElement AddControlField(FieldInfoWithPath fieldInfoWithPath, str element.RegisterValueChangeCallback(e => { - UpdateFieldVisibility(field.Name, GetFieldInfoPath(fieldPath).GetFinalValue(nodeTarget)); + UpdateFieldVisibility(field.Name, FieldInfoWithPath.GetFieldInfoPath(fieldPath, nodeTarget).GetFinalValue(nodeTarget)); valueChangedCallback?.Invoke(); NotifyNodeChanged(); }); @@ -984,7 +967,7 @@ protected VisualElement AddControlField(FieldInfoWithPath fieldInfoWithPath, str void UpdateFieldValues() { foreach (var kp in fieldControlsMap) - UpdateOtherFieldValue(kp.Key, GetFieldInfoPath(kp.Key.Path).GetFinalValue(nodeTarget)); + UpdateOtherFieldValue(kp.Key, FieldInfoWithPath.GetFieldInfoPath(kp.Key.Path, nodeTarget).GetFinalValue(nodeTarget)); } protected void AddSettingField(FieldInfo field) @@ -1019,20 +1002,19 @@ internal void OnPortConnected(PortView port) internal void OnPortDisconnected(PortView port) // { - string fieldName = port.portData.IsProxied ? port.portData.proxiedFieldPath : port.fieldName; + bool isProxied = port.portData.IsProxied; + string fieldName = isProxied ? port.portData.proxiedFieldPath : port.fieldName; if (port.direction == Direction.Input && inputContainerElement?.Q(fieldName) != null) { inputContainerElement.Q(fieldName).RemoveFromClassList("empty"); + var fieldInfoWithPath = new FieldInfoWithPath(fieldName, nodeTarget); - if (nodeTarget.nodeFields.TryGetValue(fieldName, out var fieldInfo)) - { - var valueBeforeConnection = GetInputFieldValue(fieldInfo.info); + var valueBeforeConnection = GetInputFieldValue(fieldInfoWithPath); - if (valueBeforeConnection != null) - { - fieldInfo.info.SetValue(nodeTarget, valueBeforeConnection); - } + if (valueBeforeConnection != null) + { + fieldInfoWithPath.SetValue(nodeTarget, valueBeforeConnection); } } @@ -1252,6 +1234,12 @@ public FieldInfoWithPath(FieldInfo field, string path) this.path = path; } + public FieldInfoWithPath(string path, object startingValue) + { + this.field = GetFieldInfoPath(path, startingValue).Last(); + this.path = path; + } + public FieldInfoWithPath(List fieldInfos) { this.field = fieldInfos.Last(); @@ -1269,6 +1257,28 @@ public bool Equals(FieldInfoWithPath other) return field == other.field && path == other.path; } + + public void SetValue(object startingValue, object finalValue) + { + GetFieldInfoPath(path, startingValue).SetValue(startingValue, finalValue); + } + + public static List GetFieldInfoPath(string path, object startValue) + { + string[] pathArray = path.Split('.'); + List fieldInfoPath = new List(); + object value = startValue; + for (int i = 0; i < pathArray.Length; i++) + { + // Debug.Log(pathArray[i]); + fieldInfoPath.Add(value.GetType().GetField(pathArray[i], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)); + if (i + 1 < pathArray.Length) + { + value = fieldInfoPath[i].GetValue(value); + } + } + return fieldInfoPath; + } } } diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs index b9e975de..888225e1 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Utils/FieldInfoExtension.cs @@ -36,7 +36,21 @@ public static object GetFinalValue(this IList list, object startingVa currentValue = list[i].GetValue(currentValue); } return currentValue; + } + + public static void SetValue(this IList list, object startingValue, object finalValue) + { + object currentValue = startingValue; + for (int i = 0; i < list.Count; i++) + { + if (i + 1 == list.Count) + { + list[i].SetValue(currentValue, finalValue); + break; + } + currentValue = list[i].GetValue(currentValue); + } } public static string GetPath(this IList list) From dff74ad7459daea2e3fb0c7725136af1e43ea773 Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Sat, 5 Feb 2022 13:37:10 +0000 Subject: [PATCH 30/31] Added Property to ExposedParameter for CustomNodeType BaseGraphView uses Param CustomNodeType to create Node on drop --- .../Nodes/CustomParameterNodes.meta | 8 + .../CustomParameterNode.cs | 14 + .../CustomParameterNode.cs.meta | 11 + .../CustomParameterNodes/MyFloatParam.cs | 11 + .../CustomParameterNodes/MyFloatParam.cs.meta | 11 + .../Editor/Views/BaseGraphView.cs | 2554 +++++++++-------- .../Runtime/Elements/ExposedParameter.cs | 59 +- .../Runtime/Elements/ParameterNode.cs | 212 +- 8 files changed, 1474 insertions(+), 1406 deletions(-) create mode 100644 Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes.meta create mode 100644 Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs create mode 100644 Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs.meta create mode 100644 Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs create mode 100644 Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs.meta diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes.meta b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes.meta new file mode 100644 index 00000000..b706f84e --- /dev/null +++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d88b06b27152b997c9a1ce2e708582d8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs new file mode 100644 index 00000000..fe874c29 --- /dev/null +++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs @@ -0,0 +1,14 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using GraphProcessor; +using System.Linq; + +[System.Serializable, NodeMenuItem("Custom/CustomParameterNode")] +public class CustomParameterNode : ParameterNode +{ + protected override IEnumerable GetOutputPort(List edges) + { + return new List(); + } +} diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs.meta b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs.meta new file mode 100644 index 00000000..22073daa --- /dev/null +++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/CustomParameterNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e0eb73353d256c38fb33d5cb258c6802 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs new file mode 100644 index 00000000..f3290375 --- /dev/null +++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using GraphProcessor; +using UnityEngine; + +[System.Serializable] +public class MyFloatParam : FloatParameter +{ + public override Type ParameterNodeType => typeof(CustomParameterNode); +} diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs.meta b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs.meta new file mode 100644 index 00000000..eff06fa9 --- /dev/null +++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cbf4b2e7f74fbc94b89db62700f7d7f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs index dd1239d1..44ef8495 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs @@ -15,408 +15,413 @@ namespace GraphProcessor { - /// - /// Base class to write a custom view for a node - /// - public class BaseGraphView : GraphView, IDisposable - { - public delegate void ComputeOrderUpdatedDelegate(); - public delegate void NodeDuplicatedDelegate(BaseNode duplicatedNode, BaseNode newNode); - - /// - /// Graph that owns of the node - /// - public BaseGraph graph; - - /// - /// Connector listener that will create the edges between ports - /// - public BaseEdgeConnectorListener connectorListener; - - /// - /// List of all node views in the graph - /// - /// - /// - public List< BaseNodeView > nodeViews = new List< BaseNodeView >(); - - /// - /// Dictionary of the node views accessed view the node instance, faster than a Find in the node view list - /// - /// - /// - /// - public Dictionary< BaseNode, BaseNodeView > nodeViewsPerNode = new Dictionary< BaseNode, BaseNodeView >(); - - /// - /// List of all edge views in the graph - /// - /// - /// - public List< EdgeView > edgeViews = new List< EdgeView >(); - - /// - /// List of all group views in the graph - /// - /// - /// - public List< GroupView > groupViews = new List< GroupView >(); + /// + /// Base class to write a custom view for a node + /// + public class BaseGraphView : GraphView, IDisposable + { + public delegate void ComputeOrderUpdatedDelegate(); + public delegate void NodeDuplicatedDelegate(BaseNode duplicatedNode, BaseNode newNode); + + /// + /// Graph that owns of the node + /// + public BaseGraph graph; + + /// + /// Connector listener that will create the edges between ports + /// + public BaseEdgeConnectorListener connectorListener; + + /// + /// List of all node views in the graph + /// + /// + /// + public List nodeViews = new List(); + + /// + /// Dictionary of the node views accessed view the node instance, faster than a Find in the node view list + /// + /// + /// + /// + public Dictionary nodeViewsPerNode = new Dictionary(); + + /// + /// List of all edge views in the graph + /// + /// + /// + public List edgeViews = new List(); + + /// + /// List of all group views in the graph + /// + /// + /// + public List groupViews = new List(); #if UNITY_2020_1_OR_NEWER - /// - /// List of all sticky note views in the graph - /// - /// - /// - public List< StickyNoteView > stickyNoteViews = new List(); + /// + /// List of all sticky note views in the graph + /// + /// + /// + public List stickyNoteViews = new List(); #endif - /// - /// List of all stack node views in the graph - /// - /// - /// - public List< BaseStackNodeView > stackNodeViews = new List< BaseStackNodeView >(); - - Dictionary< Type, PinnedElementView > pinnedElements = new Dictionary< Type, PinnedElementView >(); - - CreateNodeMenuWindow createNodeMenu; - - /// - /// Triggered just after the graph is initialized - /// - public event Action initialized; - - /// - /// Triggered just after the compute order of the graph is updated - /// - public event ComputeOrderUpdatedDelegate computeOrderUpdated; - - // Safe event relay from BaseGraph (safe because you are sure to always point on a valid BaseGraph - // when one of these events is called), a graph switch can occur between two call tho - /// - /// Same event than BaseGraph.onExposedParameterListChanged - /// Safe event (not triggered in case the graph is null). - /// - public event Action onExposedParameterListChanged; - - /// - /// Same event than BaseGraph.onExposedParameterModified - /// Safe event (not triggered in case the graph is null). - /// - public event Action< ExposedParameter > onExposedParameterModified; - - /// - /// Triggered when a node is duplicated (crt-d) or copy-pasted (crtl-c/crtl-v) - /// - public event NodeDuplicatedDelegate nodeDuplicated; - - /// - /// Object to handle nodes that shows their UI in the inspector. - /// - [SerializeField] - protected NodeInspectorObject nodeInspector - { - get - { - - if (graph.nodeInspectorReference == null) - graph.nodeInspectorReference = CreateNodeInspectorObject(); - return graph.nodeInspectorReference as NodeInspectorObject; - } - } - - /// - /// Workaround object for creating exposed parameter property fields. - /// - public ExposedParameterFieldFactory exposedParameterFactory { get; private set; } - - public SerializedObject serializedGraph { get; private set; } - - Dictionary nodeTypePerCreateAssetType = new Dictionary(); - - public BaseGraphView(EditorWindow window) - { - serializeGraphElements = SerializeGraphElementsCallback; - canPasteSerializedData = CanPasteSerializedDataCallback; - unserializeAndPaste = UnserializeAndPasteCallback; + /// + /// List of all stack node views in the graph + /// + /// + /// + public List stackNodeViews = new List(); + + Dictionary pinnedElements = new Dictionary(); + + CreateNodeMenuWindow createNodeMenu; + + /// + /// Triggered just after the graph is initialized + /// + public event Action initialized; + + /// + /// Triggered just after the compute order of the graph is updated + /// + public event ComputeOrderUpdatedDelegate computeOrderUpdated; + + // Safe event relay from BaseGraph (safe because you are sure to always point on a valid BaseGraph + // when one of these events is called), a graph switch can occur between two call tho + /// + /// Same event than BaseGraph.onExposedParameterListChanged + /// Safe event (not triggered in case the graph is null). + /// + public event Action onExposedParameterListChanged; + + /// + /// Same event than BaseGraph.onExposedParameterModified + /// Safe event (not triggered in case the graph is null). + /// + public event Action onExposedParameterModified; + + /// + /// Triggered when a node is duplicated (crt-d) or copy-pasted (crtl-c/crtl-v) + /// + public event NodeDuplicatedDelegate nodeDuplicated; + + /// + /// Object to handle nodes that shows their UI in the inspector. + /// + [SerializeField] + protected NodeInspectorObject nodeInspector + { + get + { + + if (graph.nodeInspectorReference == null) + graph.nodeInspectorReference = CreateNodeInspectorObject(); + return graph.nodeInspectorReference as NodeInspectorObject; + } + } + + /// + /// Workaround object for creating exposed parameter property fields. + /// + public ExposedParameterFieldFactory exposedParameterFactory { get; private set; } + + public SerializedObject serializedGraph { get; private set; } + + Dictionary nodeTypePerCreateAssetType = new Dictionary(); + + public BaseGraphView(EditorWindow window) + { + serializeGraphElements = SerializeGraphElementsCallback; + canPasteSerializedData = CanPasteSerializedDataCallback; + unserializeAndPaste = UnserializeAndPasteCallback; graphViewChanged = GraphViewChangedCallback; - viewTransformChanged = ViewTransformChangedCallback; + viewTransformChanged = ViewTransformChangedCallback; elementResized = ElementResizedCallback; - RegisterCallback< KeyDownEvent >(KeyDownCallback); - RegisterCallback< DragPerformEvent >(DragPerformedCallback); - RegisterCallback< DragUpdatedEvent >(DragUpdatedCallback); - RegisterCallback< MouseDownEvent >(MouseDownCallback); - RegisterCallback< MouseUpEvent >(MouseUpCallback); + RegisterCallback(KeyDownCallback); + RegisterCallback(DragPerformedCallback); + RegisterCallback(DragUpdatedCallback); + RegisterCallback(MouseDownCallback); + RegisterCallback(MouseUpCallback); - InitializeManipulators(); + InitializeManipulators(); - SetupZoom(0.05f, 2f); + SetupZoom(0.05f, 2f); - Undo.undoRedoPerformed += ReloadView; + Undo.undoRedoPerformed += ReloadView; - createNodeMenu = ScriptableObject.CreateInstance< CreateNodeMenuWindow >(); - createNodeMenu.Initialize(this, window); + createNodeMenu = ScriptableObject.CreateInstance(); + createNodeMenu.Initialize(this, window); - this.StretchToParentSize(); - } + this.StretchToParentSize(); + } - protected virtual NodeInspectorObject CreateNodeInspectorObject() - { - var inspector = ScriptableObject.CreateInstance(); - inspector.name = "Node Inspector"; - inspector.hideFlags = HideFlags.HideAndDontSave ^ HideFlags.NotEditable; + protected virtual NodeInspectorObject CreateNodeInspectorObject() + { + var inspector = ScriptableObject.CreateInstance(); + inspector.name = "Node Inspector"; + inspector.hideFlags = HideFlags.HideAndDontSave ^ HideFlags.NotEditable; - return inspector; - } + return inspector; + } - #region Callbacks + #region Callbacks - protected override bool canCopySelection - { + protected override bool canCopySelection + { get { return selection.Any(e => e is BaseNodeView || e is GroupView); } - } + } - protected override bool canCutSelection - { + protected override bool canCutSelection + { get { return selection.Any(e => e is BaseNodeView || e is GroupView); } - } - - string SerializeGraphElementsCallback(IEnumerable elements) - { - var data = new CopyPasteHelper(); - - foreach (BaseNodeView nodeView in elements.Where(e => e is BaseNodeView)) - { - data.copiedNodes.Add(JsonSerializer.SerializeNode(nodeView.nodeTarget)); - foreach (var port in nodeView.nodeTarget.GetAllPorts()) - { - if (port.portData.vertical) - { - foreach (var edge in port.GetEdges()) - data.copiedEdges.Add(JsonSerializer.Serialize(edge)); - } - } - } - - foreach (GroupView groupView in elements.Where(e => e is GroupView)) - data.copiedGroups.Add(JsonSerializer.Serialize(groupView.group)); - - foreach (EdgeView edgeView in elements.Where(e => e is EdgeView)) - data.copiedEdges.Add(JsonSerializer.Serialize(edgeView.serializedEdge)); - - ClearSelection(); - - return JsonUtility.ToJson(data, true); - } - - bool CanPasteSerializedDataCallback(string serializedData) - { - try { - return JsonUtility.FromJson(serializedData, typeof(CopyPasteHelper)) != null; - } catch { - return false; - } - } - - void UnserializeAndPasteCallback(string operationName, string serializedData) - { - var data = JsonUtility.FromJson< CopyPasteHelper >(serializedData); + } + + string SerializeGraphElementsCallback(IEnumerable elements) + { + var data = new CopyPasteHelper(); + + foreach (BaseNodeView nodeView in elements.Where(e => e is BaseNodeView)) + { + data.copiedNodes.Add(JsonSerializer.SerializeNode(nodeView.nodeTarget)); + foreach (var port in nodeView.nodeTarget.GetAllPorts()) + { + if (port.portData.vertical) + { + foreach (var edge in port.GetEdges()) + data.copiedEdges.Add(JsonSerializer.Serialize(edge)); + } + } + } + + foreach (GroupView groupView in elements.Where(e => e is GroupView)) + data.copiedGroups.Add(JsonSerializer.Serialize(groupView.group)); + + foreach (EdgeView edgeView in elements.Where(e => e is EdgeView)) + data.copiedEdges.Add(JsonSerializer.Serialize(edgeView.serializedEdge)); + + ClearSelection(); + + return JsonUtility.ToJson(data, true); + } + + bool CanPasteSerializedDataCallback(string serializedData) + { + try + { + return JsonUtility.FromJson(serializedData, typeof(CopyPasteHelper)) != null; + } + catch + { + return false; + } + } + + void UnserializeAndPasteCallback(string operationName, string serializedData) + { + var data = JsonUtility.FromJson(serializedData); RegisterCompleteObjectUndo(operationName); - Dictionary copiedNodesMap = new Dictionary(); + Dictionary copiedNodesMap = new Dictionary(); - var unserializedGroups = data.copiedGroups.Select(g => JsonSerializer.Deserialize(g)).ToList(); + var unserializedGroups = data.copiedGroups.Select(g => JsonSerializer.Deserialize(g)).ToList(); - foreach (var serializedNode in data.copiedNodes) - { - var node = JsonSerializer.DeserializeNode(serializedNode); + foreach (var serializedNode in data.copiedNodes) + { + var node = JsonSerializer.DeserializeNode(serializedNode); - if (node == null) - continue ; + if (node == null) + continue; - string sourceGUID = node.GUID; - graph.nodesPerGUID.TryGetValue(sourceGUID, out var sourceNode); - //Call OnNodeCreated on the new fresh copied node - node.createdFromDuplication = true; - node.createdWithinGroup = unserializedGroups.Any(g => g.innerNodeGUIDs.Contains(sourceGUID)); - node.OnNodeCreated(); - //And move a bit the new node - node.position.position += new Vector2(20, 20); + string sourceGUID = node.GUID; + graph.nodesPerGUID.TryGetValue(sourceGUID, out var sourceNode); + //Call OnNodeCreated on the new fresh copied node + node.createdFromDuplication = true; + node.createdWithinGroup = unserializedGroups.Any(g => g.innerNodeGUIDs.Contains(sourceGUID)); + node.OnNodeCreated(); + //And move a bit the new node + node.position.position += new Vector2(20, 20); - var newNodeView = AddNode(node); + var newNodeView = AddNode(node); - // If the nodes were copied from another graph, then the source is null - if (sourceNode != null) - nodeDuplicated?.Invoke(sourceNode, node); - copiedNodesMap[sourceGUID] = node; + // If the nodes were copied from another graph, then the source is null + if (sourceNode != null) + nodeDuplicated?.Invoke(sourceNode, node); + copiedNodesMap[sourceGUID] = node; - //Select the new node - AddToSelection(nodeViewsPerNode[node]); - } + //Select the new node + AddToSelection(nodeViewsPerNode[node]); + } foreach (var group in unserializedGroups) { //Same than for node group.OnCreated(); - // try to centre the created node in the screen + // try to centre the created node in the screen group.position.position += new Vector2(20, 20); - var oldGUIDList = group.innerNodeGUIDs.ToList(); - group.innerNodeGUIDs.Clear(); - foreach (var guid in oldGUIDList) - { - graph.nodesPerGUID.TryGetValue(guid, out var node); - - // In case group was copied from another graph - if (node == null) - { - copiedNodesMap.TryGetValue(guid, out node); - group.innerNodeGUIDs.Add(node.GUID); - } - else - { - group.innerNodeGUIDs.Add(copiedNodesMap[guid].GUID); - } - } + var oldGUIDList = group.innerNodeGUIDs.ToList(); + group.innerNodeGUIDs.Clear(); + foreach (var guid in oldGUIDList) + { + graph.nodesPerGUID.TryGetValue(guid, out var node); + + // In case group was copied from another graph + if (node == null) + { + copiedNodesMap.TryGetValue(guid, out node); + group.innerNodeGUIDs.Add(node.GUID); + } + else + { + group.innerNodeGUIDs.Add(copiedNodesMap[guid].GUID); + } + } AddGroup(group); } foreach (var serializedEdge in data.copiedEdges) - { - var edge = JsonSerializer.Deserialize(serializedEdge); + { + var edge = JsonSerializer.Deserialize(serializedEdge); - edge.Deserialize(); + edge.Deserialize(); - // Find port of new nodes: - copiedNodesMap.TryGetValue(edge.inputNode.GUID, out var oldInputNode); - copiedNodesMap.TryGetValue(edge.outputNode.GUID, out var oldOutputNode); + // Find port of new nodes: + copiedNodesMap.TryGetValue(edge.inputNode.GUID, out var oldInputNode); + copiedNodesMap.TryGetValue(edge.outputNode.GUID, out var oldOutputNode); - // We avoid to break the graph by replacing unique connections: - if (oldInputNode == null && !edge.inputPort.portData.acceptMultipleEdges || !edge.outputPort.portData.acceptMultipleEdges) - continue; + // We avoid to break the graph by replacing unique connections: + if (oldInputNode == null && !edge.inputPort.portData.acceptMultipleEdges || !edge.outputPort.portData.acceptMultipleEdges) + continue; - oldInputNode = oldInputNode ?? edge.inputNode; - oldOutputNode = oldOutputNode ?? edge.outputNode; + oldInputNode = oldInputNode ?? edge.inputNode; + oldOutputNode = oldOutputNode ?? edge.outputNode; - var inputPort = oldInputNode.GetPort(edge.inputPort.fieldName, edge.inputPortIdentifier); - var outputPort = oldOutputNode.GetPort(edge.outputPort.fieldName, edge.outputPortIdentifier); + var inputPort = oldInputNode.GetPort(edge.inputPort.fieldName, edge.inputPortIdentifier); + var outputPort = oldOutputNode.GetPort(edge.outputPort.fieldName, edge.outputPortIdentifier); - var newEdge = SerializableEdge.CreateNewEdge(graph, inputPort, outputPort); + var newEdge = SerializableEdge.CreateNewEdge(graph, inputPort, outputPort); - if (nodeViewsPerNode.ContainsKey(oldInputNode) && nodeViewsPerNode.ContainsKey(oldOutputNode)) - { - var edgeView = CreateEdgeView(); - edgeView.userData = newEdge; - edgeView.input = nodeViewsPerNode[oldInputNode].GetPortViewFromFieldName(newEdge.inputFieldName, newEdge.inputPortIdentifier); - edgeView.output = nodeViewsPerNode[oldOutputNode].GetPortViewFromFieldName(newEdge.outputFieldName, newEdge.outputPortIdentifier); + if (nodeViewsPerNode.ContainsKey(oldInputNode) && nodeViewsPerNode.ContainsKey(oldOutputNode)) + { + var edgeView = CreateEdgeView(); + edgeView.userData = newEdge; + edgeView.input = nodeViewsPerNode[oldInputNode].GetPortViewFromFieldName(newEdge.inputFieldName, newEdge.inputPortIdentifier); + edgeView.output = nodeViewsPerNode[oldOutputNode].GetPortViewFromFieldName(newEdge.outputFieldName, newEdge.outputPortIdentifier); - Connect(edgeView); - } - } - } + Connect(edgeView); + } + } + } public virtual EdgeView CreateEdgeView() { - return new EdgeView(); + return new EdgeView(); } GraphViewChange GraphViewChangedCallback(GraphViewChange changes) - { - if (changes.elementsToRemove != null) - { - RegisterCompleteObjectUndo("Remove Graph Elements"); - - // Destroy priority of objects - // We need nodes to be destroyed first because we can have a destroy operation that uses node connections - changes.elementsToRemove.Sort((e1, e2) => { - int GetPriority(GraphElement e) - { - if (e is BaseNodeView) - return 0; - else - return 1; - } - return GetPriority(e1).CompareTo(GetPriority(e2)); - }); - - //Handle ourselves the edge and node remove - changes.elementsToRemove.RemoveAll(e => { - - switch (e) - { - case EdgeView edge: - Disconnect(edge); - return true; - case BaseNodeView nodeView: - // For vertical nodes, we need to delete them ourselves as it's not handled by GraphView - foreach (var pv in nodeView.inputPortViews.Concat(nodeView.outputPortViews)) - if (pv.orientation == Orientation.Vertical) - foreach (var edge in pv.GetEdges().ToList()) - Disconnect(edge); - - nodeInspector.NodeViewRemoved(nodeView); - ExceptionToLog.Call(() => nodeView.OnRemoved()); - graph.RemoveNode(nodeView.nodeTarget); - UpdateSerializedProperties(); - RemoveElement(nodeView); - if (Selection.activeObject == nodeInspector) - UpdateNodeInspectorSelection(); - - SyncSerializedPropertyPathes(); - return true; - case GroupView group: - graph.RemoveGroup(group.group); - UpdateSerializedProperties(); - RemoveElement(group); - return true; - case ExposedParameterFieldView blackboardField: - graph.RemoveExposedParameter(blackboardField.parameter); - UpdateSerializedProperties(); - return true; - case BaseStackNodeView stackNodeView: - graph.RemoveStackNode(stackNodeView.stackNode); - UpdateSerializedProperties(); - RemoveElement(stackNodeView); - return true; + { + if (changes.elementsToRemove != null) + { + RegisterCompleteObjectUndo("Remove Graph Elements"); + + // Destroy priority of objects + // We need nodes to be destroyed first because we can have a destroy operation that uses node connections + changes.elementsToRemove.Sort((e1, e2) => + { + int GetPriority(GraphElement e) + { + if (e is BaseNodeView) + return 0; + else + return 1; + } + return GetPriority(e1).CompareTo(GetPriority(e2)); + }); + + //Handle ourselves the edge and node remove + changes.elementsToRemove.RemoveAll(e => + { + + switch (e) + { + case EdgeView edge: + Disconnect(edge); + return true; + case BaseNodeView nodeView: + // For vertical nodes, we need to delete them ourselves as it's not handled by GraphView + foreach (var pv in nodeView.inputPortViews.Concat(nodeView.outputPortViews)) + if (pv.orientation == Orientation.Vertical) + foreach (var edge in pv.GetEdges().ToList()) + Disconnect(edge); + + nodeInspector.NodeViewRemoved(nodeView); + ExceptionToLog.Call(() => nodeView.OnRemoved()); + graph.RemoveNode(nodeView.nodeTarget); + UpdateSerializedProperties(); + RemoveElement(nodeView); + if (Selection.activeObject == nodeInspector) + UpdateNodeInspectorSelection(); + + SyncSerializedPropertyPathes(); + return true; + case GroupView group: + graph.RemoveGroup(group.group); + UpdateSerializedProperties(); + RemoveElement(group); + return true; + case ExposedParameterFieldView blackboardField: + graph.RemoveExposedParameter(blackboardField.parameter); + UpdateSerializedProperties(); + return true; + case BaseStackNodeView stackNodeView: + graph.RemoveStackNode(stackNodeView.stackNode); + UpdateSerializedProperties(); + RemoveElement(stackNodeView); + return true; #if UNITY_2020_1_OR_NEWER - case StickyNoteView stickyNoteView: - graph.RemoveStickyNote(stickyNoteView.note); - UpdateSerializedProperties(); - RemoveElement(stickyNoteView); - return true; + case StickyNoteView stickyNoteView: + graph.RemoveStickyNote(stickyNoteView.note); + UpdateSerializedProperties(); + RemoveElement(stickyNoteView); + return true; #endif - } - - return false; - }); - } - - return changes; - } - - void GraphChangesCallback(GraphChanges changes) - { - if (changes.removedEdge != null) - { - var edge = edgeViews.FirstOrDefault(e => e.serializedEdge == changes.removedEdge); - - DisconnectView(edge); - } - } - - void ViewTransformChangedCallback(GraphView view) - { - if (graph != null) - { - graph.position = viewTransform.position; - graph.scale = viewTransform.scale; - } - } + } + + return false; + }); + } + + return changes; + } + + void GraphChangesCallback(GraphChanges changes) + { + if (changes.removedEdge != null) + { + var edge = edgeViews.FirstOrDefault(e => e.serializedEdge == changes.removedEdge); + + DisconnectView(edge); + } + } + + void ViewTransformChangedCallback(GraphView view) + { + if (graph != null) + { + graph.position = viewTransform.position; + graph.scale = viewTransform.scale; + } + } void ElementResizedCallback(VisualElement elem) { @@ -426,459 +431,464 @@ void ElementResizedCallback(VisualElement elem) groupView.group.size = groupView.GetPosition().size; } - public override List< Port > GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) - { - var compatiblePorts = new List< Port >(); - - compatiblePorts.AddRange(ports.Where(p => { - var portView = p as PortView; - - if (portView.owner == (startPort as PortView).owner) - return false; - - if (p.direction == startPort.direction) - return false; - - //Check for type assignability - if (!BaseGraph.TypesAreConnectable(startPort.portType, p.portType)) - return false; - - //Check if the edge already exists - if (portView.GetEdges().Any(e => e.input == startPort || e.output == startPort)) - return false; - - return true; - })); - - return compatiblePorts; - } - - /// - /// Build the contextual menu shown when right clicking inside the graph view - /// - /// - public override void BuildContextualMenu(ContextualMenuPopulateEvent evt) - { - base.BuildContextualMenu(evt); - BuildGroupContextualMenu(evt, 1); - BuildStickyNoteContextualMenu(evt, 2); - BuildViewContextualMenu(evt); - BuildSelectAssetContextualMenu(evt); - BuildSaveAssetContextualMenu(evt); - BuildHelpContextualMenu(evt); - } - - /// - /// Add the New Group entry to the context menu - /// - /// - protected virtual void BuildGroupContextualMenu(ContextualMenuPopulateEvent evt, int menuPosition = -1) - { - if (menuPosition == -1) - menuPosition = evt.menu.MenuItems().Count; - Vector2 position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition); + public override List GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) + { + var compatiblePorts = new List(); + + compatiblePorts.AddRange(ports.Where(p => + { + var portView = p as PortView; + + if (portView.owner == (startPort as PortView).owner) + return false; + + if (p.direction == startPort.direction) + return false; + + //Check for type assignability + if (!BaseGraph.TypesAreConnectable(startPort.portType, p.portType)) + return false; + + //Check if the edge already exists + if (portView.GetEdges().Any(e => e.input == startPort || e.output == startPort)) + return false; + + return true; + })); + + return compatiblePorts; + } + + /// + /// Build the contextual menu shown when right clicking inside the graph view + /// + /// + public override void BuildContextualMenu(ContextualMenuPopulateEvent evt) + { + base.BuildContextualMenu(evt); + BuildGroupContextualMenu(evt, 1); + BuildStickyNoteContextualMenu(evt, 2); + BuildViewContextualMenu(evt); + BuildSelectAssetContextualMenu(evt); + BuildSaveAssetContextualMenu(evt); + BuildHelpContextualMenu(evt); + } + + /// + /// Add the New Group entry to the context menu + /// + /// + protected virtual void BuildGroupContextualMenu(ContextualMenuPopulateEvent evt, int menuPosition = -1) + { + if (menuPosition == -1) + menuPosition = evt.menu.MenuItems().Count; + Vector2 position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition); evt.menu.InsertAction(menuPosition, "Create Group", (e) => AddSelectionsToGroup(AddGroup(new Group("Create Group", position))), DropdownMenuAction.AlwaysEnabled); - } - - /// - /// -Add the New Sticky Note entry to the context menu - /// - /// - protected virtual void BuildStickyNoteContextualMenu(ContextualMenuPopulateEvent evt, int menuPosition = -1) - { - if (menuPosition == -1) - menuPosition = evt.menu.MenuItems().Count; + } + + /// + /// -Add the New Sticky Note entry to the context menu + /// + /// + protected virtual void BuildStickyNoteContextualMenu(ContextualMenuPopulateEvent evt, int menuPosition = -1) + { + if (menuPosition == -1) + menuPosition = evt.menu.MenuItems().Count; #if UNITY_2020_1_OR_NEWER - Vector2 position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition); + Vector2 position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition); evt.menu.InsertAction(menuPosition, "Create Sticky Note", (e) => AddStickyNote(new StickyNote("Create Note", position)), DropdownMenuAction.AlwaysEnabled); #endif - } - - /// - /// Add the View entry to the context menu - /// - /// - protected virtual void BuildViewContextualMenu(ContextualMenuPopulateEvent evt) - { - evt.menu.AppendAction("View/Processor", (e) => ToggleView< ProcessorView >(), (e) => GetPinnedElementStatus< ProcessorView >()); - } - - /// - /// Add the Select Asset entry to the context menu - /// - /// - protected virtual void BuildSelectAssetContextualMenu(ContextualMenuPopulateEvent evt) - { - evt.menu.AppendAction("Select Asset", (e) => EditorGUIUtility.PingObject(graph), DropdownMenuAction.AlwaysEnabled); - } - - /// - /// Add the Save Asset entry to the context menu - /// - /// - protected virtual void BuildSaveAssetContextualMenu(ContextualMenuPopulateEvent evt) - { - evt.menu.AppendAction("Save Asset", (e) => { - EditorUtility.SetDirty(graph); - AssetDatabase.SaveAssets(); - }, DropdownMenuAction.AlwaysEnabled); - } - - /// - /// Add the Help entry to the context menu - /// - /// - protected void BuildHelpContextualMenu(ContextualMenuPopulateEvent evt) - { - evt.menu.AppendAction("Help/Reset Pinned Windows", e => { - foreach (var kp in pinnedElements) - kp.Value.ResetPosition(); - }); - } - - protected virtual void KeyDownCallback(KeyDownEvent e) - { - if (e.keyCode == KeyCode.S && e.commandKey) - { - SaveGraphToDisk(); - e.StopPropagation(); - } - else if(nodeViews.Count > 0 && e.commandKey && e.altKey) - { - // Node Aligning shortcuts - switch(e.keyCode) - { - case KeyCode.LeftArrow: - nodeViews[0].AlignToLeft(); - e.StopPropagation(); - break; - case KeyCode.RightArrow: - nodeViews[0].AlignToRight(); - e.StopPropagation(); - break; - case KeyCode.UpArrow: - nodeViews[0].AlignToTop(); - e.StopPropagation(); - break; - case KeyCode.DownArrow: - nodeViews[0].AlignToBottom(); - e.StopPropagation(); - break; - case KeyCode.C: - nodeViews[0].AlignToCenter(); - e.StopPropagation(); - break; - case KeyCode.M: - nodeViews[0].AlignToMiddle(); - e.StopPropagation(); - break; - } - } - } - - void MouseUpCallback(MouseUpEvent e) - { - schedule.Execute(() => { - if (DoesSelectionContainsInspectorNodes()) - UpdateNodeInspectorSelection(); - }).ExecuteLater(1); - } - - void MouseDownCallback(MouseDownEvent e) - { - // When left clicking on the graph (not a node or something else) - if (e.button == 0) - { - // Close all settings windows: - nodeViews.ForEach(v => v.CloseSettings()); - } - - if (DoesSelectionContainsInspectorNodes()) - UpdateNodeInspectorSelection(); - } - - bool DoesSelectionContainsInspectorNodes() - { - var selectedNodes = selection.Where(s => s is BaseNodeView).ToList(); - var selectedNodesNotInInspector = selectedNodes.Except(nodeInspector.selectedNodes).ToList(); - var nodeInInspectorWithoutSelectedNodes = nodeInspector.selectedNodes.Except(selectedNodes).ToList(); - - return selectedNodesNotInInspector.Any() || nodeInInspectorWithoutSelectedNodes.Any(); - } - - void DragPerformedCallback(DragPerformEvent e) - { - var mousePos = (e.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, e.localMousePosition); - var dragData = DragAndDrop.GetGenericData("DragSelection") as List< ISelectable >; - - // Drag and Drop for elements inside the graph - if (dragData != null) - { - var exposedParameterFieldViews = dragData.OfType(); - if (exposedParameterFieldViews.Any()) - { - foreach (var paramFieldView in exposedParameterFieldViews) - { - RegisterCompleteObjectUndo("Create Parameter Node"); - var paramNode = BaseNode.CreateFromType< ParameterNode >(mousePos); - paramNode.parameterGUID = paramFieldView.parameter.guid; - AddNode(paramNode); - } - } - } - - // External objects drag and drop - if (DragAndDrop.objectReferences.Length > 0) - { - RegisterCompleteObjectUndo("Create Node From Object(s)"); - foreach (var obj in DragAndDrop.objectReferences) - { - var objectType = obj.GetType(); - - foreach (var kp in nodeTypePerCreateAssetType) - { - if (kp.Key.IsAssignableFrom(objectType)) - { - try - { - var node = BaseNode.CreateFromType(kp.Value.nodeType, mousePos); - if ((bool)kp.Value.initalizeNodeFromObject.Invoke(node, new []{obj})) - { - AddNode(node); - break; - } - } - catch (Exception exception) - { - Debug.LogException(exception); - } - } - } - } - } - } - - void DragUpdatedCallback(DragUpdatedEvent e) + } + + /// + /// Add the View entry to the context menu + /// + /// + protected virtual void BuildViewContextualMenu(ContextualMenuPopulateEvent evt) + { + evt.menu.AppendAction("View/Processor", (e) => ToggleView(), (e) => GetPinnedElementStatus()); + } + + /// + /// Add the Select Asset entry to the context menu + /// + /// + protected virtual void BuildSelectAssetContextualMenu(ContextualMenuPopulateEvent evt) + { + evt.menu.AppendAction("Select Asset", (e) => EditorGUIUtility.PingObject(graph), DropdownMenuAction.AlwaysEnabled); + } + + /// + /// Add the Save Asset entry to the context menu + /// + /// + protected virtual void BuildSaveAssetContextualMenu(ContextualMenuPopulateEvent evt) + { + evt.menu.AppendAction("Save Asset", (e) => + { + EditorUtility.SetDirty(graph); + AssetDatabase.SaveAssets(); + }, DropdownMenuAction.AlwaysEnabled); + } + + /// + /// Add the Help entry to the context menu + /// + /// + protected void BuildHelpContextualMenu(ContextualMenuPopulateEvent evt) + { + evt.menu.AppendAction("Help/Reset Pinned Windows", e => + { + foreach (var kp in pinnedElements) + kp.Value.ResetPosition(); + }); + } + + protected virtual void KeyDownCallback(KeyDownEvent e) + { + if (e.keyCode == KeyCode.S && e.commandKey) + { + SaveGraphToDisk(); + e.StopPropagation(); + } + else if (nodeViews.Count > 0 && e.commandKey && e.altKey) + { + // Node Aligning shortcuts + switch (e.keyCode) + { + case KeyCode.LeftArrow: + nodeViews[0].AlignToLeft(); + e.StopPropagation(); + break; + case KeyCode.RightArrow: + nodeViews[0].AlignToRight(); + e.StopPropagation(); + break; + case KeyCode.UpArrow: + nodeViews[0].AlignToTop(); + e.StopPropagation(); + break; + case KeyCode.DownArrow: + nodeViews[0].AlignToBottom(); + e.StopPropagation(); + break; + case KeyCode.C: + nodeViews[0].AlignToCenter(); + e.StopPropagation(); + break; + case KeyCode.M: + nodeViews[0].AlignToMiddle(); + e.StopPropagation(); + break; + } + } + } + + void MouseUpCallback(MouseUpEvent e) + { + schedule.Execute(() => + { + if (DoesSelectionContainsInspectorNodes()) + UpdateNodeInspectorSelection(); + }).ExecuteLater(1); + } + + void MouseDownCallback(MouseDownEvent e) + { + // When left clicking on the graph (not a node or something else) + if (e.button == 0) + { + // Close all settings windows: + nodeViews.ForEach(v => v.CloseSettings()); + } + + if (DoesSelectionContainsInspectorNodes()) + UpdateNodeInspectorSelection(); + } + + bool DoesSelectionContainsInspectorNodes() + { + var selectedNodes = selection.Where(s => s is BaseNodeView).ToList(); + var selectedNodesNotInInspector = selectedNodes.Except(nodeInspector.selectedNodes).ToList(); + var nodeInInspectorWithoutSelectedNodes = nodeInspector.selectedNodes.Except(selectedNodes).ToList(); + + return selectedNodesNotInInspector.Any() || nodeInInspectorWithoutSelectedNodes.Any(); + } + + void DragPerformedCallback(DragPerformEvent e) + { + var mousePos = (e.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, e.localMousePosition); + var dragData = DragAndDrop.GetGenericData("DragSelection") as List; + + // Drag and Drop for elements inside the graph + if (dragData != null) + { + var exposedParameterFieldViews = dragData.OfType(); + if (exposedParameterFieldViews.Any()) + { + foreach (var paramFieldView in exposedParameterFieldViews) + { + RegisterCompleteObjectUndo("Create Parameter Node"); + var paramNode = BaseNode.CreateFromType(paramFieldView.parameter.ParameterNodeType, mousePos) as ParameterNode; + paramNode.parameterGUID = paramFieldView.parameter.guid; + AddNode(paramNode); + } + } + } + + // External objects drag and drop + if (DragAndDrop.objectReferences.Length > 0) + { + RegisterCompleteObjectUndo("Create Node From Object(s)"); + foreach (var obj in DragAndDrop.objectReferences) + { + var objectType = obj.GetType(); + + foreach (var kp in nodeTypePerCreateAssetType) + { + if (kp.Key.IsAssignableFrom(objectType)) + { + try + { + var node = BaseNode.CreateFromType(kp.Value.nodeType, mousePos); + if ((bool)kp.Value.initalizeNodeFromObject.Invoke(node, new[] { obj })) + { + AddNode(node); + break; + } + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + } + } + } + } + + void DragUpdatedCallback(DragUpdatedEvent e) { var dragData = DragAndDrop.GetGenericData("DragSelection") as List; - var dragObjects = DragAndDrop.objectReferences; + var dragObjects = DragAndDrop.objectReferences; bool dragging = false; if (dragData != null) { // Handle drag from exposed parameter view if (dragData.OfType().Any()) - { + { dragging = true; - } + } } - if (dragObjects.Length > 0) - dragging = true; + if (dragObjects.Length > 0) + dragging = true; if (dragging) DragAndDrop.visualMode = DragAndDropVisualMode.Generic; - UpdateNodeInspectorSelection(); + UpdateNodeInspectorSelection(); } - #endregion + #endregion - #region Initialization + #region Initialization - void ReloadView() - { - // Force the graph to reload his data (Undo have updated the serialized properties of the graph - // so the one that are not serialized need to be synchronized) - graph.Deserialize(); + void ReloadView() + { + // Force the graph to reload his data (Undo have updated the serialized properties of the graph + // so the one that are not serialized need to be synchronized) + graph.Deserialize(); - // Get selected nodes - var selectedNodeGUIDs = new List(); - foreach (var e in selection) - { - if (e is BaseNodeView v && this.Contains(v)) - selectedNodeGUIDs.Add(v.nodeTarget.GUID); - } + // Get selected nodes + var selectedNodeGUIDs = new List(); + foreach (var e in selection) + { + if (e is BaseNodeView v && this.Contains(v)) + selectedNodeGUIDs.Add(v.nodeTarget.GUID); + } - // Remove everything - RemoveNodeViews(); - RemoveEdges(); - RemoveGroups(); + // Remove everything + RemoveNodeViews(); + RemoveEdges(); + RemoveGroups(); #if UNITY_2020_1_OR_NEWER - RemoveStrickyNotes(); + RemoveStrickyNotes(); #endif - RemoveStackNodeViews(); + RemoveStackNodeViews(); - UpdateSerializedProperties(); + UpdateSerializedProperties(); - // And re-add with new up to date datas - InitializeNodeViews(); - InitializeEdgeViews(); + // And re-add with new up to date datas + InitializeNodeViews(); + InitializeEdgeViews(); InitializeGroups(); - InitializeStickyNotes(); - InitializeStackNodes(); + InitializeStickyNotes(); + InitializeStackNodes(); - Reload(); + Reload(); - UpdateComputeOrder(); + UpdateComputeOrder(); - // Restore selection after re-creating all views - // selection = nodeViews.Where(v => selectedNodeGUIDs.Contains(v.nodeTarget.GUID)).Select(v => v as ISelectable).ToList(); - foreach (var guid in selectedNodeGUIDs) - { - AddToSelection(nodeViews.FirstOrDefault(n => n.nodeTarget.GUID == guid)); - } + // Restore selection after re-creating all views + // selection = nodeViews.Where(v => selectedNodeGUIDs.Contains(v.nodeTarget.GUID)).Select(v => v as ISelectable).ToList(); + foreach (var guid in selectedNodeGUIDs) + { + AddToSelection(nodeViews.FirstOrDefault(n => n.nodeTarget.GUID == guid)); + } - UpdateNodeInspectorSelection(); - } + UpdateNodeInspectorSelection(); + } - public void Initialize(BaseGraph graph) - { - if (this.graph != null) - { - SaveGraphToDisk(); - // Close pinned windows from old graph: - ClearGraphElements(); - NodeProvider.UnloadGraph(graph); - } + public void Initialize(BaseGraph graph) + { + if (this.graph != null) + { + SaveGraphToDisk(); + // Close pinned windows from old graph: + ClearGraphElements(); + NodeProvider.UnloadGraph(graph); + } - this.graph = graph; + this.graph = graph; - exposedParameterFactory = new ExposedParameterFieldFactory(graph); + exposedParameterFactory = new ExposedParameterFieldFactory(graph); - UpdateSerializedProperties(); + UpdateSerializedProperties(); connectorListener = CreateEdgeConnectorListener(); - // When pressing ctrl-s, we save the graph - EditorSceneManager.sceneSaved += _ => SaveGraphToDisk(); - RegisterCallback(e => { - if (e.keyCode == KeyCode.S && e.actionKey) - SaveGraphToDisk(); - }); + // When pressing ctrl-s, we save the graph + EditorSceneManager.sceneSaved += _ => SaveGraphToDisk(); + RegisterCallback(e => + { + if (e.keyCode == KeyCode.S && e.actionKey) + SaveGraphToDisk(); + }); - ClearGraphElements(); + ClearGraphElements(); - InitializeGraphView(); - InitializeNodeViews(); - InitializeEdgeViews(); - InitializeViews(); + InitializeGraphView(); + InitializeNodeViews(); + InitializeEdgeViews(); + InitializeViews(); InitializeGroups(); - InitializeStickyNotes(); - InitializeStackNodes(); + InitializeStickyNotes(); + InitializeStackNodes(); - initialized?.Invoke(); - UpdateComputeOrder(); + initialized?.Invoke(); + UpdateComputeOrder(); - InitializeView(); + InitializeView(); - NodeProvider.LoadGraph(graph); + NodeProvider.LoadGraph(graph); - // Register the nodes that can be created from assets - foreach (var nodeInfo in NodeProvider.GetNodeMenuEntries(graph)) - { - var interfaces = nodeInfo.type.GetInterfaces(); + // Register the nodes that can be created from assets + foreach (var nodeInfo in NodeProvider.GetNodeMenuEntries(graph)) + { + var interfaces = nodeInfo.type.GetInterfaces(); var exceptInheritedInterfaces = interfaces.Except(interfaces.SelectMany(t => t.GetInterfaces())); - foreach (var i in exceptInheritedInterfaces) - { - if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICreateNodeFrom<>)) - { - var genericArgumentType = i.GetGenericArguments()[0]; - var initializeFunction = nodeInfo.type.GetMethod( - nameof(ICreateNodeFrom.InitializeNodeFromObject), - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, - null, new Type[]{ genericArgumentType}, null - ); - - // We only add the type that implements the interface, not it's children - if (initializeFunction.DeclaringType == nodeInfo.type) - nodeTypePerCreateAssetType[genericArgumentType] = (nodeInfo.type, initializeFunction); - } - } - } - } - - public void ClearGraphElements() - { - RemoveGroups(); - RemoveNodeViews(); - RemoveEdges(); - RemoveStackNodeViews(); - RemovePinnedElementViews(); + foreach (var i in exceptInheritedInterfaces) + { + if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICreateNodeFrom<>)) + { + var genericArgumentType = i.GetGenericArguments()[0]; + var initializeFunction = nodeInfo.type.GetMethod( + nameof(ICreateNodeFrom.InitializeNodeFromObject), + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + null, new Type[] { genericArgumentType }, null + ); + + // We only add the type that implements the interface, not it's children + if (initializeFunction.DeclaringType == nodeInfo.type) + nodeTypePerCreateAssetType[genericArgumentType] = (nodeInfo.type, initializeFunction); + } + } + } + } + + public void ClearGraphElements() + { + RemoveGroups(); + RemoveNodeViews(); + RemoveEdges(); + RemoveStackNodeViews(); + RemovePinnedElementViews(); #if UNITY_2020_1_OR_NEWER - RemoveStrickyNotes(); + RemoveStrickyNotes(); #endif - } - - void UpdateSerializedProperties() - { - if(graph != null) - serializedGraph = new SerializedObject(graph); - } - - /// - /// Allow you to create your own edge connector listener - /// - /// - protected virtual BaseEdgeConnectorListener CreateEdgeConnectorListener() - => new BaseEdgeConnectorListener(this); - - void InitializeGraphView() - { - graph.onExposedParameterListChanged += OnExposedParameterListChanged; - graph.onExposedParameterModified += (s) => onExposedParameterModified?.Invoke(s); - graph.onGraphChanges += GraphChangesCallback; - viewTransform.position = graph.position; - viewTransform.scale = graph.scale; - nodeCreationRequest = (c) => SearchWindow.Open(new SearchWindowContext(c.screenMousePosition), createNodeMenu); - } - - void OnExposedParameterListChanged() - { - UpdateSerializedProperties(); - onExposedParameterListChanged?.Invoke(); - } - - void InitializeNodeViews() - { - graph.nodes.RemoveAll(n => n == null); - - foreach (var node in graph.nodes) - { - var v = AddNodeView(node); - } - } - - void InitializeEdgeViews() - { - // Sanitize edges in case a node broke something while loading - graph.edges.RemoveAll(edge => edge == null || edge.inputNode == null || edge.outputNode == null); - - foreach (var serializedEdge in graph.edges) - { - nodeViewsPerNode.TryGetValue(serializedEdge.inputNode, out var inputNodeView); - nodeViewsPerNode.TryGetValue(serializedEdge.outputNode, out var outputNodeView); - if (inputNodeView == null || outputNodeView == null) - continue; - - var edgeView = CreateEdgeView(); - edgeView.userData = serializedEdge; - edgeView.input = inputNodeView.GetPortViewFromFieldName(serializedEdge.inputFieldName, serializedEdge.inputPortIdentifier); - edgeView.output = outputNodeView.GetPortViewFromFieldName(serializedEdge.outputFieldName, serializedEdge.outputPortIdentifier); - - - ConnectView(edgeView); - } - } - - void InitializeViews() - { - foreach (var pinnedElement in graph.pinnedElements) - { - if (pinnedElement.opened) - OpenPinned(pinnedElement.editorType.type); - } - } + } + + void UpdateSerializedProperties() + { + if (graph != null) + serializedGraph = new SerializedObject(graph); + } + + /// + /// Allow you to create your own edge connector listener + /// + /// + protected virtual BaseEdgeConnectorListener CreateEdgeConnectorListener() + => new BaseEdgeConnectorListener(this); + + void InitializeGraphView() + { + graph.onExposedParameterListChanged += OnExposedParameterListChanged; + graph.onExposedParameterModified += (s) => onExposedParameterModified?.Invoke(s); + graph.onGraphChanges += GraphChangesCallback; + viewTransform.position = graph.position; + viewTransform.scale = graph.scale; + nodeCreationRequest = (c) => SearchWindow.Open(new SearchWindowContext(c.screenMousePosition), createNodeMenu); + } + + void OnExposedParameterListChanged() + { + UpdateSerializedProperties(); + onExposedParameterListChanged?.Invoke(); + } + + void InitializeNodeViews() + { + graph.nodes.RemoveAll(n => n == null); + + foreach (var node in graph.nodes) + { + var v = AddNodeView(node); + } + } + + void InitializeEdgeViews() + { + // Sanitize edges in case a node broke something while loading + graph.edges.RemoveAll(edge => edge == null || edge.inputNode == null || edge.outputNode == null); + + foreach (var serializedEdge in graph.edges) + { + nodeViewsPerNode.TryGetValue(serializedEdge.inputNode, out var inputNodeView); + nodeViewsPerNode.TryGetValue(serializedEdge.outputNode, out var outputNodeView); + if (inputNodeView == null || outputNodeView == null) + continue; + + var edgeView = CreateEdgeView(); + edgeView.userData = serializedEdge; + edgeView.input = inputNodeView.GetPortViewFromFieldName(serializedEdge.inputFieldName, serializedEdge.inputPortIdentifier); + edgeView.output = outputNodeView.GetPortViewFromFieldName(serializedEdge.outputFieldName, serializedEdge.outputPortIdentifier); + + + ConnectView(edgeView); + } + } + + void InitializeViews() + { + foreach (var pinnedElement in graph.pinnedElements) + { + if (pinnedElement.opened) + OpenPinned(pinnedElement.editorType.type); + } + } void InitializeGroups() { @@ -886,123 +896,123 @@ void InitializeGroups() AddGroupView(group); } - void InitializeStickyNotes() - { + void InitializeStickyNotes() + { #if UNITY_2020_1_OR_NEWER foreach (var group in graph.stickyNotes) AddStickyNoteView(group); #endif - } - - void InitializeStackNodes() - { - foreach (var stackNode in graph.stackNodes) - AddStackNodeView(stackNode); - } - - protected virtual void InitializeManipulators() - { - this.AddManipulator(new ContentDragger()); - this.AddManipulator(new SelectionDragger()); - this.AddManipulator(new RectangleSelector()); - } - - protected virtual void Reload() {} - - #endregion - - #region Graph content modification - - public void UpdateNodeInspectorSelection() - { - if (nodeInspector.previouslySelectedObject != Selection.activeObject) - nodeInspector.previouslySelectedObject = Selection.activeObject; - - HashSet selectedNodeViews = new HashSet(); - nodeInspector.selectedNodes.Clear(); - foreach (var e in selection) - { - if (e is BaseNodeView v && this.Contains(v) && v.nodeTarget.needsInspector) - selectedNodeViews.Add(v); - } - - nodeInspector.UpdateSelectedNodes(selectedNodeViews); - if (Selection.activeObject != nodeInspector && selectedNodeViews.Count > 0) - Selection.activeObject = nodeInspector; - } - - public BaseNodeView AddNode(BaseNode node) - { - // This will initialize the node using the graph instance - graph.AddNode(node); - - UpdateSerializedProperties(); - - var view = AddNodeView(node); - - // Call create after the node have been initialized - ExceptionToLog.Call(() => view.OnCreated()); - - UpdateComputeOrder(); - - return view; - } - - public BaseNodeView AddNodeView(BaseNode node) - { - var viewType = NodeProvider.GetNodeViewTypeFromType(node.GetType()); - - if (viewType == null) - viewType = typeof(BaseNodeView); - - var baseNodeView = Activator.CreateInstance(viewType) as BaseNodeView; - baseNodeView.Initialize(this, node); - AddElement(baseNodeView); - - nodeViews.Add(baseNodeView); - nodeViewsPerNode[node] = baseNodeView; - - return baseNodeView; - } - - public void RemoveNode(BaseNode node) - { - var view = nodeViewsPerNode[node]; - RemoveNodeView(view); - graph.RemoveNode(node); - } - - public void RemoveNodeView(BaseNodeView nodeView) - { - RemoveElement(nodeView); - nodeViews.Remove(nodeView); - nodeViewsPerNode.Remove(nodeView.nodeTarget); - } - - void RemoveNodeViews() - { - foreach (var nodeView in nodeViews) - RemoveElement(nodeView); - nodeViews.Clear(); - nodeViewsPerNode.Clear(); - } - - void RemoveStackNodeViews() - { - foreach (var stackView in stackNodeViews) - RemoveElement(stackView); - stackNodeViews.Clear(); - } - - void RemovePinnedElementViews() - { - foreach (var pinnedView in pinnedElements.Values) - { - if (Contains(pinnedView)) - Remove(pinnedView); - } - pinnedElements.Clear(); - } + } + + void InitializeStackNodes() + { + foreach (var stackNode in graph.stackNodes) + AddStackNodeView(stackNode); + } + + protected virtual void InitializeManipulators() + { + this.AddManipulator(new ContentDragger()); + this.AddManipulator(new SelectionDragger()); + this.AddManipulator(new RectangleSelector()); + } + + protected virtual void Reload() { } + + #endregion + + #region Graph content modification + + public void UpdateNodeInspectorSelection() + { + if (nodeInspector.previouslySelectedObject != Selection.activeObject) + nodeInspector.previouslySelectedObject = Selection.activeObject; + + HashSet selectedNodeViews = new HashSet(); + nodeInspector.selectedNodes.Clear(); + foreach (var e in selection) + { + if (e is BaseNodeView v && this.Contains(v) && v.nodeTarget.needsInspector) + selectedNodeViews.Add(v); + } + + nodeInspector.UpdateSelectedNodes(selectedNodeViews); + if (Selection.activeObject != nodeInspector && selectedNodeViews.Count > 0) + Selection.activeObject = nodeInspector; + } + + public BaseNodeView AddNode(BaseNode node) + { + // This will initialize the node using the graph instance + graph.AddNode(node); + + UpdateSerializedProperties(); + + var view = AddNodeView(node); + + // Call create after the node have been initialized + ExceptionToLog.Call(() => view.OnCreated()); + + UpdateComputeOrder(); + + return view; + } + + public BaseNodeView AddNodeView(BaseNode node) + { + var viewType = NodeProvider.GetNodeViewTypeFromType(node.GetType()); + + if (viewType == null) + viewType = typeof(BaseNodeView); + + var baseNodeView = Activator.CreateInstance(viewType) as BaseNodeView; + baseNodeView.Initialize(this, node); + AddElement(baseNodeView); + + nodeViews.Add(baseNodeView); + nodeViewsPerNode[node] = baseNodeView; + + return baseNodeView; + } + + public void RemoveNode(BaseNode node) + { + var view = nodeViewsPerNode[node]; + RemoveNodeView(view); + graph.RemoveNode(node); + } + + public void RemoveNodeView(BaseNodeView nodeView) + { + RemoveElement(nodeView); + nodeViews.Remove(nodeView); + nodeViewsPerNode.Remove(nodeView.nodeTarget); + } + + void RemoveNodeViews() + { + foreach (var nodeView in nodeViews) + RemoveElement(nodeView); + nodeViews.Clear(); + nodeViewsPerNode.Clear(); + } + + void RemoveStackNodeViews() + { + foreach (var stackView in stackNodeViews) + RemoveElement(stackView); + stackNodeViews.Clear(); + } + + void RemovePinnedElementViews() + { + foreach (var pinnedView in pinnedElements.Values) + { + if (Contains(pinnedView)) + Remove(pinnedView); + } + pinnedElements.Clear(); + } public GroupView AddGroup(Group block) { @@ -1011,42 +1021,42 @@ public GroupView AddGroup(Group block) return AddGroupView(block); } - public GroupView AddGroupView(Group block) - { - var c = new GroupView(); + public GroupView AddGroupView(Group block) + { + var c = new GroupView(); - c.Initialize(this, block); + c.Initialize(this, block); - AddElement(c); + AddElement(c); groupViews.Add(c); return c; - } + } - public BaseStackNodeView AddStackNode(BaseStackNode stackNode) - { - graph.AddStackNode(stackNode); - return AddStackNodeView(stackNode); - } + public BaseStackNodeView AddStackNode(BaseStackNode stackNode) + { + graph.AddStackNode(stackNode); + return AddStackNodeView(stackNode); + } - public BaseStackNodeView AddStackNodeView(BaseStackNode stackNode) - { - var viewType = StackNodeViewProvider.GetStackNodeCustomViewType(stackNode.GetType()) ?? typeof(BaseStackNodeView); - var stackView = Activator.CreateInstance(viewType, stackNode) as BaseStackNodeView; + public BaseStackNodeView AddStackNodeView(BaseStackNode stackNode) + { + var viewType = StackNodeViewProvider.GetStackNodeCustomViewType(stackNode.GetType()) ?? typeof(BaseStackNodeView); + var stackView = Activator.CreateInstance(viewType, stackNode) as BaseStackNodeView; - AddElement(stackView); - stackNodeViews.Add(stackView); + AddElement(stackView); + stackNodeViews.Add(stackView); - stackView.Initialize(this); + stackView.Initialize(this); - return stackView; - } + return stackView; + } - public void RemoveStackNodeView(BaseStackNodeView stackNodeView) - { - stackNodeViews.Remove(stackNodeView); - RemoveElement(stackNodeView); - } + public void RemoveStackNodeView(BaseStackNodeView stackNodeView) + { + stackNodeViews.Remove(stackNodeView); + RemoveElement(stackNodeView); + } #if UNITY_2020_1_OR_NEWER public StickyNoteView AddStickyNote(StickyNote note) @@ -1055,30 +1065,30 @@ public StickyNoteView AddStickyNote(StickyNote note) return AddStickyNoteView(note); } - public StickyNoteView AddStickyNoteView(StickyNote note) - { - var c = new StickyNoteView(); + public StickyNoteView AddStickyNoteView(StickyNote note) + { + var c = new StickyNoteView(); - c.Initialize(this, note); + c.Initialize(this, note); - AddElement(c); + AddElement(c); stickyNoteViews.Add(c); return c; - } - - public void RemoveStickyNoteView(StickyNoteView view) - { - stickyNoteViews.Remove(view); - RemoveElement(view); - } - - public void RemoveStrickyNotes() - { - foreach (var stickyNodeView in stickyNoteViews) - RemoveElement(stickyNodeView); - stickyNoteViews.Clear(); - } + } + + public void RemoveStickyNoteView(StickyNoteView view) + { + stickyNoteViews.Remove(view); + RemoveElement(view); + } + + public void RemoveStrickyNotes() + { + foreach (var stickyNodeView in stickyNoteViews) + RemoveElement(stickyNodeView); + stickyNoteViews.Clear(); + } #endif public void AddSelectionsToGroup(GroupView view) @@ -1095,376 +1105,378 @@ public void AddSelectionsToGroup(GroupView view) } } - public void RemoveGroups() - { - foreach (var groupView in groupViews) - RemoveElement(groupView); - groupViews.Clear(); - } - - public bool CanConnectEdge(EdgeView e, bool autoDisconnectInputs = true) - { - if (e.input == null || e.output == null) - return false; - - var inputPortView = e.input as PortView; - var outputPortView = e.output as PortView; - var inputNodeView = inputPortView.node as BaseNodeView; - var outputNodeView = outputPortView.node as BaseNodeView; - - if (inputNodeView == null || outputNodeView == null) - { - Debug.LogError("Connect aborted !"); - return false; - } - - return true; - } - - public bool ConnectView(EdgeView e, bool autoDisconnectInputs = true) - { - if (!CanConnectEdge(e, autoDisconnectInputs)) - return false; - - var inputPortView = e.input as PortView; - var outputPortView = e.output as PortView; - var inputNodeView = inputPortView.node as BaseNodeView; - var outputNodeView = outputPortView.node as BaseNodeView; - - //If the input port does not support multi-connection, we remove them - if (autoDisconnectInputs && !(e.input as PortView).portData.acceptMultipleEdges) - { - foreach (var edge in edgeViews.Where(ev => ev.input == e.input).ToList()) - { - // TODO: do not disconnect them if the connected port is the same than the old connected - DisconnectView(edge); - } - } - // same for the output port: - if (autoDisconnectInputs && !(e.output as PortView).portData.acceptMultipleEdges) - { - foreach (var edge in edgeViews.Where(ev => ev.output == e.output).ToList()) - { - // TODO: do not disconnect them if the connected port is the same than the old connected - DisconnectView(edge); - } - } - - AddElement(e); - - e.input.Connect(e); - e.output.Connect(e); - - // If the input port have been removed by the custom port behavior - // we try to find if it's still here - if (e.input == null) - e.input = inputNodeView.GetPortViewFromFieldName(inputPortView.fieldName, inputPortView.portData.identifier); - if (e.output == null) - e.output = inputNodeView.GetPortViewFromFieldName(outputPortView.fieldName, outputPortView.portData.identifier); - - edgeViews.Add(e); - - inputNodeView.RefreshPorts(); - outputNodeView.RefreshPorts(); - - // In certain cases the edge color is wrong so we patch it - schedule.Execute(() => { - e.UpdateEdgeControl(); - }).ExecuteLater(1); - - e.isConnected = true; - - return true; - } - - public bool Connect(PortView inputPortView, PortView outputPortView, bool autoDisconnectInputs = true) - { - var inputPort = inputPortView.owner.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier); - var outputPort = outputPortView.owner.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier); - - // Checks that the node we are connecting still exists - if (inputPortView.owner.parent == null || outputPortView.owner.parent == null) - return false; - - var newEdge = SerializableEdge.CreateNewEdge(graph, inputPort, outputPort); - - var edgeView = CreateEdgeView(); - edgeView.userData = newEdge; - edgeView.input = inputPortView; - edgeView.output = outputPortView; - - if (ConversionNodeAdapter.AreAssignable(outputPort.portData.displayType, inputPort.portData.displayType)) - { - return ConnectConvertable(edgeView, autoDisconnectInputs); - } else - { - return Connect(edgeView); - } - } - - /// - /// Same as connect, but also adds custom conversion nodes inbetween the edges input/output, if neccessary - /// - /// - /// - /// - public bool ConnectConvertable(EdgeView e, bool autoDisconnectInputs = true) - { - if (!CanConnectEdge(e, autoDisconnectInputs)) - return false; - - var inputPortView = e.input as PortView; - var outputPortView = e.output as PortView; - var inputNodeView = inputPortView.node as BaseNodeView; - var outputNodeView = outputPortView.node as BaseNodeView; - var inputPort = inputNodeView.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier); - var outputPort = outputNodeView.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier); - - Type conversionNodeType = ConversionNodeAdapter.GetConversionNode(outputPort.portData.displayType, inputPort.portData.displayType); - if (conversionNodeType != null) - { - var nodePosition = (inputPort.owner.position.center + outputPort.owner.position.center) / 2.0f; - BaseNode converterNode = BaseNode.CreateFromType(conversionNodeType, nodePosition); - IConversionNode conversion = (IConversionNode)converterNode; - var converterView = AddNode(converterNode); - - // set nodes center position to be in the middle of the input/output ports - converterNode.position.center = nodePosition - new Vector2(converterNode.position.width / 2.0f,0); - converterView.SetPosition(converterNode.position); - - - var conversionInputName = conversion.GetConversionInput(); - var converterInput = converterView.inputPortViews.Find(view => view.fieldName == conversionInputName); - var conversionOutputName = conversion.GetConversionOutput(); - var converterOutput = converterView.outputPortViews.Find(view => view.fieldName == conversionOutputName); - - Connect(inputPortView, converterOutput, autoDisconnectInputs); - - e.input = converterInput; // change from original input to use the converter node - return Connect(e, autoDisconnectInputs); - } - else - { - return Connect(e, autoDisconnectInputs); - } - } - - public bool Connect(EdgeView e, bool autoDisconnectInputs = true) - { - if (!CanConnectEdge(e, autoDisconnectInputs)) - return false; - - var inputPortView = e.input as PortView; - var outputPortView = e.output as PortView; - var inputNodeView = inputPortView.node as BaseNodeView; - var outputNodeView = outputPortView.node as BaseNodeView; - var inputPort = inputNodeView.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier); - var outputPort = outputNodeView.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier); - - e.userData = graph.Connect(inputPort, outputPort, autoDisconnectInputs); - - ConnectView(e, autoDisconnectInputs); - - UpdateComputeOrder(); - - return true; - } - - public void DisconnectView(EdgeView e, bool refreshPorts = true) - { - if (e == null) - return ; - - RemoveElement(e); - - if (e?.input?.node is BaseNodeView inputNodeView) - { - e.input.Disconnect(e); - if (refreshPorts) - inputNodeView.RefreshPorts(); - } - if (e?.output?.node is BaseNodeView outputNodeView) - { - e.output.Disconnect(e); - if (refreshPorts) - outputNodeView.RefreshPorts(); - } - - edgeViews.Remove(e); - } - - public void Disconnect(EdgeView e, bool refreshPorts = true) - { - // Remove the serialized edge if there is one - if (e.userData is SerializableEdge serializableEdge) - graph.Disconnect(serializableEdge.GUID); - - DisconnectView(e, refreshPorts); - - UpdateComputeOrder(); - } - - public void RemoveEdges() - { - foreach (var edge in edgeViews) - RemoveElement(edge); - edgeViews.Clear(); - } - - public void UpdateComputeOrder() - { - graph.UpdateComputeOrder(); - - computeOrderUpdated?.Invoke(); - } - - public void RegisterCompleteObjectUndo(string name) - { - Undo.RegisterCompleteObjectUndo(graph, name); - } - - public void SaveGraphToDisk() - { - if (graph == null) - return ; - - EditorUtility.SetDirty(graph); - } - - public void ToggleView< T >() where T : PinnedElementView - { - ToggleView(typeof(T)); - } - - public void ToggleView(Type type) - { - PinnedElementView view; - pinnedElements.TryGetValue(type, out view); - - if (view == null) - OpenPinned(type); - else - ClosePinned(type, view); - } - - public void OpenPinned< T >() where T : PinnedElementView - { - OpenPinned(typeof(T)); - } - - public void OpenPinned(Type type) - { - PinnedElementView view; - - if (type == null) - return ; - - PinnedElement elem = graph.OpenPinned(type); - - if (!pinnedElements.ContainsKey(type)) - { - view = Activator.CreateInstance(type) as PinnedElementView; - if (view == null) - return ; - pinnedElements[type] = view; - view.InitializeGraphView(elem, this); - } - view = pinnedElements[type]; - - if (!Contains(view)) - Add(view); - } - - public void ClosePinned< T >(PinnedElementView view) where T : PinnedElementView - { - ClosePinned(typeof(T), view); - } - - public void ClosePinned(Type type, PinnedElementView elem) - { - pinnedElements.Remove(type); - Remove(elem); - graph.ClosePinned(type); - } - - public Status GetPinnedElementStatus< T >() where T : PinnedElementView - { - return GetPinnedElementStatus(typeof(T)); - } - - public Status GetPinnedElementStatus(Type type) - { - var pinned = graph.pinnedElements.Find(p => p.editorType.type == type); - - if (pinned != null && pinned.opened) - return Status.Normal; - else - return Status.Hidden; - } - - public void ResetPositionAndZoom() - { - graph.position = Vector3.zero; - graph.scale = Vector3.one; - - UpdateViewTransform(graph.position, graph.scale); - } - - /// - /// Deletes the selected content, can be called form an IMGUI container - /// - public void DelayedDeleteSelection() => this.schedule.Execute(() => DeleteSelectionOperation("Delete", AskUser.DontAskUser)).ExecuteLater(0); - - protected virtual void InitializeView() {} - - public virtual IEnumerable<(string path, Type type)> FilterCreateNodeMenuEntries() - { - // By default we don't filter anything - foreach (var nodeMenuItem in NodeProvider.GetNodeMenuEntries(graph)) - yield return nodeMenuItem; - - // TODO: add exposed properties to this list - } - - public RelayNodeView AddRelayNode(PortView inputPort, PortView outputPort, Vector2 position) - { - var relayNode = BaseNode.CreateFromType(position); - var view = AddNode(relayNode) as RelayNodeView; - - if (outputPort != null) - Connect(view.inputPortViews[0], outputPort); - if (inputPort != null) - Connect(inputPort, view.outputPortViews[0]); - - return view; - } - - /// - /// Update all the serialized property bindings (in case a node was deleted / added, the property pathes needs to be updated) - /// - public void SyncSerializedPropertyPathes() - { - foreach (var nodeView in nodeViews) - nodeView.SyncSerializedPropertyPathes(); - nodeInspector.RefreshNodes(); - } - - /// - /// Call this function when you want to remove this view - /// + public void RemoveGroups() + { + foreach (var groupView in groupViews) + RemoveElement(groupView); + groupViews.Clear(); + } + + public bool CanConnectEdge(EdgeView e, bool autoDisconnectInputs = true) + { + if (e.input == null || e.output == null) + return false; + + var inputPortView = e.input as PortView; + var outputPortView = e.output as PortView; + var inputNodeView = inputPortView.node as BaseNodeView; + var outputNodeView = outputPortView.node as BaseNodeView; + + if (inputNodeView == null || outputNodeView == null) + { + Debug.LogError("Connect aborted !"); + return false; + } + + return true; + } + + public bool ConnectView(EdgeView e, bool autoDisconnectInputs = true) + { + if (!CanConnectEdge(e, autoDisconnectInputs)) + return false; + + var inputPortView = e.input as PortView; + var outputPortView = e.output as PortView; + var inputNodeView = inputPortView.node as BaseNodeView; + var outputNodeView = outputPortView.node as BaseNodeView; + + //If the input port does not support multi-connection, we remove them + if (autoDisconnectInputs && !(e.input as PortView).portData.acceptMultipleEdges) + { + foreach (var edge in edgeViews.Where(ev => ev.input == e.input).ToList()) + { + // TODO: do not disconnect them if the connected port is the same than the old connected + DisconnectView(edge); + } + } + // same for the output port: + if (autoDisconnectInputs && !(e.output as PortView).portData.acceptMultipleEdges) + { + foreach (var edge in edgeViews.Where(ev => ev.output == e.output).ToList()) + { + // TODO: do not disconnect them if the connected port is the same than the old connected + DisconnectView(edge); + } + } + + AddElement(e); + + e.input.Connect(e); + e.output.Connect(e); + + // If the input port have been removed by the custom port behavior + // we try to find if it's still here + if (e.input == null) + e.input = inputNodeView.GetPortViewFromFieldName(inputPortView.fieldName, inputPortView.portData.identifier); + if (e.output == null) + e.output = inputNodeView.GetPortViewFromFieldName(outputPortView.fieldName, outputPortView.portData.identifier); + + edgeViews.Add(e); + + inputNodeView.RefreshPorts(); + outputNodeView.RefreshPorts(); + + // In certain cases the edge color is wrong so we patch it + schedule.Execute(() => + { + e.UpdateEdgeControl(); + }).ExecuteLater(1); + + e.isConnected = true; + + return true; + } + + public bool Connect(PortView inputPortView, PortView outputPortView, bool autoDisconnectInputs = true) + { + var inputPort = inputPortView.owner.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier); + var outputPort = outputPortView.owner.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier); + + // Checks that the node we are connecting still exists + if (inputPortView.owner.parent == null || outputPortView.owner.parent == null) + return false; + + var newEdge = SerializableEdge.CreateNewEdge(graph, inputPort, outputPort); + + var edgeView = CreateEdgeView(); + edgeView.userData = newEdge; + edgeView.input = inputPortView; + edgeView.output = outputPortView; + + if (ConversionNodeAdapter.AreAssignable(outputPort.portData.displayType, inputPort.portData.displayType)) + { + return ConnectConvertable(edgeView, autoDisconnectInputs); + } + else + { + return Connect(edgeView); + } + } + + /// + /// Same as connect, but also adds custom conversion nodes inbetween the edges input/output, if neccessary + /// + /// + /// + /// + public bool ConnectConvertable(EdgeView e, bool autoDisconnectInputs = true) + { + if (!CanConnectEdge(e, autoDisconnectInputs)) + return false; + + var inputPortView = e.input as PortView; + var outputPortView = e.output as PortView; + var inputNodeView = inputPortView.node as BaseNodeView; + var outputNodeView = outputPortView.node as BaseNodeView; + var inputPort = inputNodeView.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier); + var outputPort = outputNodeView.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier); + + Type conversionNodeType = ConversionNodeAdapter.GetConversionNode(outputPort.portData.displayType, inputPort.portData.displayType); + if (conversionNodeType != null) + { + var nodePosition = (inputPort.owner.position.center + outputPort.owner.position.center) / 2.0f; + BaseNode converterNode = BaseNode.CreateFromType(conversionNodeType, nodePosition); + IConversionNode conversion = (IConversionNode)converterNode; + var converterView = AddNode(converterNode); + + // set nodes center position to be in the middle of the input/output ports + converterNode.position.center = nodePosition - new Vector2(converterNode.position.width / 2.0f, 0); + converterView.SetPosition(converterNode.position); + + + var conversionInputName = conversion.GetConversionInput(); + var converterInput = converterView.inputPortViews.Find(view => view.fieldName == conversionInputName); + var conversionOutputName = conversion.GetConversionOutput(); + var converterOutput = converterView.outputPortViews.Find(view => view.fieldName == conversionOutputName); + + Connect(inputPortView, converterOutput, autoDisconnectInputs); + + e.input = converterInput; // change from original input to use the converter node + return Connect(e, autoDisconnectInputs); + } + else + { + return Connect(e, autoDisconnectInputs); + } + } + + public bool Connect(EdgeView e, bool autoDisconnectInputs = true) + { + if (!CanConnectEdge(e, autoDisconnectInputs)) + return false; + + var inputPortView = e.input as PortView; + var outputPortView = e.output as PortView; + var inputNodeView = inputPortView.node as BaseNodeView; + var outputNodeView = outputPortView.node as BaseNodeView; + var inputPort = inputNodeView.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier); + var outputPort = outputNodeView.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier); + + e.userData = graph.Connect(inputPort, outputPort, autoDisconnectInputs); + + ConnectView(e, autoDisconnectInputs); + + UpdateComputeOrder(); + + return true; + } + + public void DisconnectView(EdgeView e, bool refreshPorts = true) + { + if (e == null) + return; + + RemoveElement(e); + + if (e?.input?.node is BaseNodeView inputNodeView) + { + e.input.Disconnect(e); + if (refreshPorts) + inputNodeView.RefreshPorts(); + } + if (e?.output?.node is BaseNodeView outputNodeView) + { + e.output.Disconnect(e); + if (refreshPorts) + outputNodeView.RefreshPorts(); + } + + edgeViews.Remove(e); + } + + public void Disconnect(EdgeView e, bool refreshPorts = true) + { + // Remove the serialized edge if there is one + if (e.userData is SerializableEdge serializableEdge) + graph.Disconnect(serializableEdge.GUID); + + DisconnectView(e, refreshPorts); + + UpdateComputeOrder(); + } + + public void RemoveEdges() + { + foreach (var edge in edgeViews) + RemoveElement(edge); + edgeViews.Clear(); + } + + public void UpdateComputeOrder() + { + graph.UpdateComputeOrder(); + + computeOrderUpdated?.Invoke(); + } + + public void RegisterCompleteObjectUndo(string name) + { + Undo.RegisterCompleteObjectUndo(graph, name); + } + + public void SaveGraphToDisk() + { + if (graph == null) + return; + + EditorUtility.SetDirty(graph); + } + + public void ToggleView() where T : PinnedElementView + { + ToggleView(typeof(T)); + } + + public void ToggleView(Type type) + { + PinnedElementView view; + pinnedElements.TryGetValue(type, out view); + + if (view == null) + OpenPinned(type); + else + ClosePinned(type, view); + } + + public void OpenPinned() where T : PinnedElementView + { + OpenPinned(typeof(T)); + } + + public void OpenPinned(Type type) + { + PinnedElementView view; + + if (type == null) + return; + + PinnedElement elem = graph.OpenPinned(type); + + if (!pinnedElements.ContainsKey(type)) + { + view = Activator.CreateInstance(type) as PinnedElementView; + if (view == null) + return; + pinnedElements[type] = view; + view.InitializeGraphView(elem, this); + } + view = pinnedElements[type]; + + if (!Contains(view)) + Add(view); + } + + public void ClosePinned(PinnedElementView view) where T : PinnedElementView + { + ClosePinned(typeof(T), view); + } + + public void ClosePinned(Type type, PinnedElementView elem) + { + pinnedElements.Remove(type); + Remove(elem); + graph.ClosePinned(type); + } + + public Status GetPinnedElementStatus() where T : PinnedElementView + { + return GetPinnedElementStatus(typeof(T)); + } + + public Status GetPinnedElementStatus(Type type) + { + var pinned = graph.pinnedElements.Find(p => p.editorType.type == type); + + if (pinned != null && pinned.opened) + return Status.Normal; + else + return Status.Hidden; + } + + public void ResetPositionAndZoom() + { + graph.position = Vector3.zero; + graph.scale = Vector3.one; + + UpdateViewTransform(graph.position, graph.scale); + } + + /// + /// Deletes the selected content, can be called form an IMGUI container + /// + public void DelayedDeleteSelection() => this.schedule.Execute(() => DeleteSelectionOperation("Delete", AskUser.DontAskUser)).ExecuteLater(0); + + protected virtual void InitializeView() { } + + public virtual IEnumerable<(string path, Type type)> FilterCreateNodeMenuEntries() + { + // By default we don't filter anything + foreach (var nodeMenuItem in NodeProvider.GetNodeMenuEntries(graph)) + yield return nodeMenuItem; + + // TODO: add exposed properties to this list + } + + public RelayNodeView AddRelayNode(PortView inputPort, PortView outputPort, Vector2 position) + { + var relayNode = BaseNode.CreateFromType(position); + var view = AddNode(relayNode) as RelayNodeView; + + if (outputPort != null) + Connect(view.inputPortViews[0], outputPort); + if (inputPort != null) + Connect(inputPort, view.outputPortViews[0]); + + return view; + } + + /// + /// Update all the serialized property bindings (in case a node was deleted / added, the property pathes needs to be updated) + /// + public void SyncSerializedPropertyPathes() + { + foreach (var nodeView in nodeViews) + nodeView.SyncSerializedPropertyPathes(); + nodeInspector.RefreshNodes(); + } + + /// + /// Call this function when you want to remove this view + /// public void Dispose() { - ClearGraphElements(); - RemoveFromHierarchy(); - Undo.undoRedoPerformed -= ReloadView; - Object.DestroyImmediate(nodeInspector); - NodeProvider.UnloadGraph(graph); - exposedParameterFactory.Dispose(); - exposedParameterFactory = null; - - graph.onExposedParameterListChanged -= OnExposedParameterListChanged; - graph.onExposedParameterModified += (s) => onExposedParameterModified?.Invoke(s); - graph.onGraphChanges -= GraphChangesCallback; + ClearGraphElements(); + RemoveFromHierarchy(); + Undo.undoRedoPerformed -= ReloadView; + Object.DestroyImmediate(nodeInspector); + NodeProvider.UnloadGraph(graph); + exposedParameterFactory.Dispose(); + exposedParameterFactory = null; + + graph.onExposedParameterListChanged -= OnExposedParameterListChanged; + graph.onExposedParameterModified += (s) => onExposedParameterModified?.Invoke(s); + graph.onGraphChanges -= GraphChangesCallback; } #endregion diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs index 75adaa6e..11e4c444 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs @@ -4,9 +4,9 @@ namespace GraphProcessor { - [Serializable] - public class ExposedParameter : ISerializationCallbackReceiver - { + [Serializable] + public class ExposedParameter : ISerializationCallbackReceiver + { [Serializable] public class Settings { @@ -30,43 +30,44 @@ public virtual bool Equals(Settings param) public override int GetHashCode() => base.GetHashCode(); } - public string guid; // unique id to keep track of the parameter - public string name; - [Obsolete("Use GetValueType()")] - public string type; - [Obsolete("Use value instead")] - public SerializableObject serializedValue; - public bool input = true; + public string guid; // unique id to keep track of the parameter + public string name; + [Obsolete("Use GetValueType()")] + public string type; + [Obsolete("Use value instead")] + public SerializableObject serializedValue; + public bool input = true; [SerializeReference] - public Settings settings; - public string shortType => GetValueType()?.Name; + public Settings settings; + public string shortType => GetValueType()?.Name; public void Initialize(string name, object value) { - guid = Guid.NewGuid().ToString(); // Generated once and unique per parameter + guid = Guid.NewGuid().ToString(); // Generated once and unique per parameter settings = CreateSettings(); settings.guid = guid; - this.name = name; - this.value = value; + this.name = name; + this.value = value; } - void ISerializationCallbackReceiver.OnAfterDeserialize() - { - // SerializeReference migration step: + void ISerializationCallbackReceiver.OnAfterDeserialize() + { + // SerializeReference migration step: #pragma warning disable CS0618 - if (serializedValue?.value != null) // old serialization system can't serialize null values - { - value = serializedValue.value; - Debug.Log("Migrated: " + serializedValue.value + " | " + serializedValue.serializedName); - serializedValue.value = null; - } + if (serializedValue?.value != null) // old serialization system can't serialize null values + { + value = serializedValue.value; + Debug.Log("Migrated: " + serializedValue.value + " | " + serializedValue.serializedName); + serializedValue.value = null; + } #pragma warning restore CS0618 - } + } - void ISerializationCallbackReceiver.OnBeforeSerialize() {} + void ISerializationCallbackReceiver.OnBeforeSerialize() { } protected virtual Settings CreateSettings() => new Settings(); + public virtual Type ParameterNodeType => typeof(ParameterNode); public virtual object value { get; set; } public virtual Type GetValueType() => value == null ? typeof(object) : value.GetType(); @@ -89,7 +90,7 @@ internal ExposedParameter Migrate() #pragma warning restore CS0618 if (oldType == null || !exposedParameterTypeCache.TryGetValue(oldType, out var newParamType)) return null; - + var newParam = Activator.CreateInstance(newParamType) as ExposedParameter; newParam.guid = guid; @@ -99,7 +100,7 @@ internal ExposedParameter Migrate() newParam.settings.guid = guid; return newParam; - + } public static bool operator ==(ExposedParameter param1, ExposedParameter param2) @@ -142,7 +143,7 @@ public ExposedParameter Clone() return clonedParam; } - } + } // Due to polymorphic constraints with [SerializeReference] we need to explicitly create a class for // every parameter type available in the graph (i.e. templating doesn't work) diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ParameterNode.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ParameterNode.cs index d86856e2..77b5e7e9 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ParameterNode.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ParameterNode.cs @@ -7,113 +7,113 @@ namespace GraphProcessor { - [System.Serializable] - public class ParameterNode : BaseNode - { - [Input] - public object input; - - [Output] - public object output; - - public override string name => "Parameter"; - - // We serialize the GUID of the exposed parameter in the graph so we can retrieve the true ExposedParameter from the graph - [SerializeField, HideInInspector] - public string parameterGUID; - - public ExposedParameter parameter { get; private set; } - - public event Action onParameterChanged; - - public ParameterAccessor accessor; - - protected override void Enable() - { - // load the parameter - LoadExposedParameter(); - - graph.onExposedParameterModified += OnParamChanged; - if (onParameterChanged != null) - onParameterChanged?.Invoke(); - } - - void LoadExposedParameter() - { - parameter = graph.GetExposedParameterFromGUID(parameterGUID); - - if (parameter == null) - { - Debug.Log("Property \"" + parameterGUID + "\" Can't be found !"); - - // Delete this node as the property can't be found - graph.RemoveNode(this); - return; - } - - output = parameter.value; - } - - void OnParamChanged(ExposedParameter modifiedParam) - { - if (parameter == modifiedParam) - { - onParameterChanged?.Invoke(); - } - } - - [CustomPortBehavior(nameof(output))] - IEnumerable GetOutputPort(List edges) - { - if (accessor == ParameterAccessor.Get) - { - yield return new PortData - { - identifier = "output", - displayName = "Value", - displayType = (parameter == null) ? typeof(object) : parameter.GetValueType(), - acceptMultipleEdges = true - }; - } - } - - [CustomPortBehavior(nameof(input))] - IEnumerable GetInputPort(List edges) - { - if (accessor == ParameterAccessor.Set) - { - yield return new PortData - { - identifier = "input", - displayName = "Value", - displayType = (parameter == null) ? typeof(object) : parameter.GetValueType(), - }; - } - } - - protected override void Process() - { + [System.Serializable] + public class ParameterNode : BaseNode + { + [Input] + public object input; + + [Output] + public object output; + + public override string name => "Parameter"; + + // We serialize the GUID of the exposed parameter in the graph so we can retrieve the true ExposedParameter from the graph + [SerializeField, HideInInspector] + public string parameterGUID; + + public ExposedParameter parameter { get; private set; } + + public event Action onParameterChanged; + + public ParameterAccessor accessor; + + protected override void Enable() + { + // load the parameter + LoadExposedParameter(); + + graph.onExposedParameterModified += OnParamChanged; + if (onParameterChanged != null) + onParameterChanged?.Invoke(); + } + + void LoadExposedParameter() + { + parameter = graph.GetExposedParameterFromGUID(parameterGUID); + + if (parameter == null) + { + Debug.Log("Property \"" + parameterGUID + "\" Can't be found !"); + + // Delete this node as the property can't be found + graph.RemoveNode(this); + return; + } + + output = parameter.value; + } + + void OnParamChanged(ExposedParameter modifiedParam) + { + if (parameter == modifiedParam) + { + onParameterChanged?.Invoke(); + } + } + + [CustomPortBehavior(nameof(output))] + protected virtual IEnumerable GetOutputPort(List edges) + { + if (accessor == ParameterAccessor.Get) + { + yield return new PortData + { + identifier = "output", + displayName = "Value", + displayType = (parameter == null) ? typeof(object) : parameter.GetValueType(), + acceptMultipleEdges = true + }; + } + } + + [CustomPortBehavior(nameof(input))] + protected virtual IEnumerable GetInputPort(List edges) + { + if (accessor == ParameterAccessor.Set) + { + yield return new PortData + { + identifier = "input", + displayName = "Value", + displayType = (parameter == null) ? typeof(object) : parameter.GetValueType(), + }; + } + } + + protected override void Process() + { #if UNITY_EDITOR // In the editor, an undo/redo can change the parameter instance in the graph, in this case the field in this class will point to the wrong parameter - parameter = graph.GetExposedParameterFromGUID(parameterGUID); + parameter = graph.GetExposedParameterFromGUID(parameterGUID); #endif - ClearMessages(); - if (parameter == null) - { - AddMessage($"Parameter not found: {parameterGUID}", NodeMessageType.Error); - return; - } - - if (accessor == ParameterAccessor.Get) - output = parameter.value; - else - graph.UpdateExposedParameter(parameter.guid, input); - } - } - - public enum ParameterAccessor - { - Get, - Set - } + ClearMessages(); + if (parameter == null) + { + AddMessage($"Parameter not found: {parameterGUID}", NodeMessageType.Error); + return; + } + + if (accessor == ParameterAccessor.Get) + output = parameter.value; + else + graph.UpdateExposedParameter(parameter.guid, input); + } + } + + public enum ParameterAccessor + { + Get, + Set + } } From 3fe63509016c54d5ed7205c03ddade9799e4e54b Mon Sep 17 00:00:00 2001 From: Daniel Burgess Date: Sat, 5 Feb 2022 13:54:33 +0000 Subject: [PATCH 31/31] Put default set of ParameterNodeType in GraphView Prepended Custom to ParamNodeType in ExposedParam. Defaults to null --- .../Nodes/CustomParameterNodes/MyFloatParam.cs | 2 +- .../Editor/Views/BaseGraphView.cs | 8 +++++++- .../Runtime/Elements/ExposedParameter.cs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs index f3290375..0e0a7513 100644 --- a/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs +++ b/Assets/Examples/DefaultNodes/Nodes/CustomParameterNodes/MyFloatParam.cs @@ -7,5 +7,5 @@ [System.Serializable] public class MyFloatParam : FloatParameter { - public override Type ParameterNodeType => typeof(CustomParameterNode); + public override Type CustomParameterNodeType => typeof(CustomParameterNode); } diff --git a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs index 44ef8495..2dc69c85 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Editor/Views/BaseGraphView.cs @@ -126,6 +126,11 @@ protected NodeInspectorObject nodeInspector } } + /// + /// Property that can be overridden to change the Node created when Drag&Drop a Parameter into the Graph. + /// + protected virtual Type DefaultParameterNode => typeof(ParameterNode); + /// /// Workaround object for creating exposed parameter property fields. /// @@ -629,7 +634,8 @@ void DragPerformedCallback(DragPerformEvent e) foreach (var paramFieldView in exposedParameterFieldViews) { RegisterCompleteObjectUndo("Create Parameter Node"); - var paramNode = BaseNode.CreateFromType(paramFieldView.parameter.ParameterNodeType, mousePos) as ParameterNode; + Type parameterNodeType = paramFieldView.parameter.CustomParameterNodeType ?? DefaultParameterNode; + var paramNode = BaseNode.CreateFromType(parameterNodeType, mousePos) as ParameterNode; paramNode.parameterGUID = paramFieldView.parameter.guid; AddNode(paramNode); } diff --git a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs index 11e4c444..861ccdad 100644 --- a/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs +++ b/Assets/com.alelievr.NodeGraphProcessor/Runtime/Elements/ExposedParameter.cs @@ -67,7 +67,7 @@ void ISerializationCallbackReceiver.OnBeforeSerialize() { } protected virtual Settings CreateSettings() => new Settings(); - public virtual Type ParameterNodeType => typeof(ParameterNode); + public virtual Type CustomParameterNodeType => null; public virtual object value { get; set; } public virtual Type GetValueType() => value == null ? typeof(object) : value.GetType();