From 432ca092749ac8ea73a5a9671d5f1456a716d945 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 11 Jan 2019 18:38:54 +0100 Subject: [PATCH 01/69] Removed debug info --- Scripts/Editor/NodeEditorGUILayout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 7c5800e..dcb7099 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -318,7 +318,7 @@ namespace XNodeEditor { }; list.drawHeaderCallback = (Rect rect) => { - EditorGUI.LabelField(rect, label + "(" + serializedObject.targetObject.name + ")"); + EditorGUI.LabelField(rect, label); }; list.onSelectCallback = (ReorderableList rl) => { From 5ee63d3ee59f804d2de43a49409d2ac5c6b8d848 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 11 Jan 2019 18:54:44 +0100 Subject: [PATCH 02/69] Fixed #96 InstancePortLists no longer need to point to a serialized property --- Scripts/Editor/NodeEditorGUILayout.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index dcb7099..d021655 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -262,7 +262,6 @@ namespace XNodeEditor { /// Called on the list on creation. Use this if you want to customize the created ReorderableList public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, Action onCreation = null) { XNode.Node node = serializedObject.targetObject as XNode.Node; - SerializedProperty arrayData = serializedObject.FindProperty(fieldName); Predicate isMatchingInstancePort = x => { @@ -279,8 +278,8 @@ namespace XNodeEditor { } // If a ReorderableList isn't cached for this array, do so. if (list == null) { - string label = serializedObject.FindProperty(fieldName).displayName; - list = CreateReorderableList(instancePorts, arrayData, type, serializedObject, io, label, connectionType, onCreation); + SerializedProperty arrayData = serializedObject.FindProperty(fieldName); + list = CreateReorderableList(fieldName, instancePorts, arrayData, type, serializedObject, io, connectionType, onCreation); if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list); else reorderableListCache.Add(serializedObject.targetObject, new Dictionary() { { fieldName, list } }); } @@ -288,15 +287,16 @@ namespace XNodeEditor { list.DoLayoutList(); } - private static ReorderableList CreateReorderableList(List instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, string label, XNode.Node.ConnectionType connectionType, Action onCreation) { + private static ReorderableList CreateReorderableList(string fieldName, List instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, Action onCreation) { bool hasArrayData = arrayData != null && arrayData.isArray; int arraySize = hasArrayData ? arrayData.arraySize : 0; XNode.Node node = serializedObject.targetObject as XNode.Node; ReorderableList list = new ReorderableList(instancePorts, null, true, true, true, true); + string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName); list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { - XNode.NodePort port = node.GetPort(arrayData.name + " " + index); + XNode.NodePort port = node.GetPort(fieldName + " " + index); if (hasArrayData) { if (arrayData.arraySize <= index) { EditorGUI.LabelField(rect, "Invalid element " + index); @@ -330,8 +330,8 @@ namespace XNodeEditor { // Move up if (rl.index > reorderableListIndex) { for (int i = reorderableListIndex; i < rl.index; ++i) { - XNode.NodePort port = node.GetPort(arrayData.name + " " + i); - XNode.NodePort nextPort = node.GetPort(arrayData.name + " " + (i + 1)); + XNode.NodePort port = node.GetPort(fieldName + " " + i); + XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i + 1)); port.SwapConnections(nextPort); // Swap cached positions to mitigate twitching @@ -343,8 +343,8 @@ namespace XNodeEditor { // Move down else { for (int i = reorderableListIndex; i > rl.index; --i) { - XNode.NodePort port = node.GetPort(arrayData.name + " " + i); - XNode.NodePort nextPort = node.GetPort(arrayData.name + " " + (i - 1)); + XNode.NodePort port = node.GetPort(fieldName + " " + i); + XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i - 1)); port.SwapConnections(nextPort); // Swap cached positions to mitigate twitching @@ -371,9 +371,9 @@ namespace XNodeEditor { list.onAddCallback = (ReorderableList rl) => { // Add instance port postfixed with an index number - string newName = arrayData.name + " 0"; + string newName = fieldName + " 0"; int i = 0; - while (node.HasPort(newName)) newName = arrayData.name + " " + (++i); + while (node.HasPort(newName)) newName = fieldName + " " + (++i); if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, newName); else node.AddInstanceInput(type, connectionType, newName); From c9a4a81c31fc4595d200b0c6e609523bebb93541 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 12 Jan 2019 02:23:00 +0100 Subject: [PATCH 03/69] InstandePortLists draw element include children --- Scripts/Editor/NodeEditorGUILayout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index d021655..629c594 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -303,7 +303,7 @@ namespace XNodeEditor { return; } SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); - EditorGUI.PropertyField(rect, itemData); + EditorGUI.PropertyField(rect, itemData, true); } else EditorGUI.LabelField(rect, port.fieldName); Vector2 pos = rect.position + (port.IsOutput?new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); NodeEditorGUILayout.PortField(pos, port); From 4c9264fed5e02754ecf221c78b033a7ea60bd366 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 20 Jan 2019 22:01:26 +0100 Subject: [PATCH 04/69] Fixed #100 --- Scripts/Editor/NodeEditorUtilities.cs | 2 +- Scripts/NodeDataCache.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index ebc7bd4..e85631b 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -35,7 +35,7 @@ namespace XNodeEditor { } public static bool GetAttrib(Type classType, string fieldName, out T attribOut) where T : Attribute { - object[] attribs = classType.GetField(fieldName).GetCustomAttributes(typeof(T), false); + object[] attribs = classType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetCustomAttributes(typeof(T), false); return GetAttrib(attribs, out attribOut); } diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index 283cc9d..415ac21 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -69,7 +69,7 @@ namespace XNode { } private static void CachePorts(System.Type nodeType) { - System.Reflection.FieldInfo[] fieldInfo = nodeType.GetFields(); + System.Reflection.FieldInfo[] fieldInfo = nodeType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); for (int i = 0; i < fieldInfo.Length; i++) { //Get InputAttribute and OutputAttribute From 8c731a99478760cbe126c91749c599d58a65081f Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 21 Jan 2019 21:07:07 +0100 Subject: [PATCH 05/69] Fixed #100 again --- Scripts/Editor/NodeEditorUtilities.cs | 12 +++++++++++- Scripts/NodeDataCache.cs | 18 +++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index e85631b..c0eeeb3 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -35,7 +35,17 @@ namespace XNodeEditor { } public static bool GetAttrib(Type classType, string fieldName, out T attribOut) where T : Attribute { - object[] attribs = classType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetCustomAttributes(typeof(T), false); + // If we can't find field in the first run, it's probably a private field in a base class. + FieldInfo field = classType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + // Search base classes for private fields only. Public fields are found above + while (field == null && (classType = classType.BaseType) != typeof(XNode.Node)) field = classType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + // This shouldn't happen. Ever. + if (field == null) { + Debug.LogWarning("Field " + fieldName + " couldnt be found"); + attribOut = null; + return false; + } + object[] attribs = field.GetCustomAttributes(typeof(T), false); return GetAttrib(attribs, out attribOut); } diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index 415ac21..8d96146 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -68,12 +68,24 @@ namespace XNode { } } + public static List GetNodeFields(System.Type nodeType) { + List fieldInfo = new List(nodeType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)); + + // GetFields doesnt return inherited private fields, so walk through base types and pick those up + System.Type tempType = nodeType; + while ((tempType = tempType.BaseType) != typeof(XNode.Node)) { + fieldInfo.AddRange(tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)); + } + return fieldInfo; + } + private static void CachePorts(System.Type nodeType) { - System.Reflection.FieldInfo[] fieldInfo = nodeType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - for (int i = 0; i < fieldInfo.Length; i++) { + List fieldInfo = GetNodeFields(nodeType); + + for (int i = 0; i < fieldInfo.Count; i++) { //Get InputAttribute and OutputAttribute - object[] attribs = fieldInfo[i].GetCustomAttributes(false); + object[] attribs = fieldInfo[i].GetCustomAttributes(true); Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute; Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute; From 3972ac5abf8996a6e72c34b59bc73667e24fc698 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 21 Jan 2019 22:25:05 +0100 Subject: [PATCH 06/69] Allow naming nodes in Init --- Scripts/Editor/NodeGraphEditor.cs | 2 +- Scripts/Node.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 20f5657..81a53c8 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -65,7 +65,7 @@ namespace XNodeEditor { public virtual void CreateNode(Type type, Vector2 position) { XNode.Node node = target.AddNode(type); node.position = position; - node.name = UnityEditor.ObjectNames.NicifyVariableName(type.Name); + if (string.IsNullOrEmpty(node.name)) node.name = UnityEditor.ObjectNames.NicifyVariableName(type.Name); AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); NodeEditorWindow.RepaintAll(); diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 881a297..4a9ace9 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -76,7 +76,7 @@ namespace XNode { NodeDataCache.UpdatePorts(this, ports); } - /// Initialize node. Called on creation. + /// Initialize node. Called on enable. protected virtual void Init() { } /// Checks all connections for invalid references, and removes them. From 50002a1f6a0abfe2df5810e32ecbbcfa77e597e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=2E=20Tar=C4=B1k=20=C3=87etin?= Date: Thu, 31 Jan 2019 12:40:44 +0300 Subject: [PATCH 07/69] Update README.md Appended qAI project at the end of "Projects using xNode" section of Readme. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 34bb854..904e146 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,4 @@ Feel free to also leave suggestions/requests in the [issues](https://github.com/ Projects using xNode: * [Graphmesh](https://github.com/Siccity/Graphmesh "Go to github page") * [Dialogue](https://github.com/Siccity/Dialogue "Go to github page") +* [qAI](https://github.com/jlreymendez/qAI "Go to github page") From 0f5539e077be5bc2895d14491dc1cd41b53daa82 Mon Sep 17 00:00:00 2001 From: NoiseCrime Date: Wed, 13 Feb 2019 16:17:25 +0000 Subject: [PATCH 08/69] Support for DoubleClick on node to center it. Added support for double clciking on Node header to center that node within the editor window. --- Scripts/Editor/NodeEditorAction.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index f3ebde4..5add067 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -22,11 +22,12 @@ namespace XNodeEditor { [NonSerialized] private List draggedOutputReroutes = new List(); private RerouteReference hoveredReroute = new RerouteReference(); private List selectedReroutes = new List(); - private Rect nodeRects; + private Rect nodeRects; // Is this used? private Vector2 dragBoxStart; private UnityEngine.Object[] preBoxSelection; private RerouteReference[] preBoxSelectionReroute; private Rect selectionBox; + private bool isDoubleClick = false; private struct RerouteReference { public XNode.NodePort port; @@ -172,6 +173,10 @@ namespace XNodeEditor { SelectNode(hoveredNode, e.control || e.shift); if (!e.control && !e.shift) selectedReroutes.Clear(); } else if (e.control || e.shift) DeselectNode(hoveredNode); + + // Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown. + isDoubleClick = ( e.clickCount == 2 ); + e.Use(); currentActivity = NodeActivity.HoldNode; } else if (IsHoveringReroute) { @@ -239,6 +244,13 @@ namespace XNodeEditor { if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift)) { selectedReroutes.Clear(); SelectNode(hoveredNode, false); + + // Double click to center node + if ( isDoubleClick ) + { + Vector2 nodeDimension = nodeSizes.ContainsKey( hoveredNode ) ? nodeSizes[ hoveredNode ] / 2 : new Vector2(0f, 0f); + panOffset = -hoveredNode.position - nodeDimension; + } } // If click reroute, select it. @@ -273,6 +285,8 @@ namespace XNodeEditor { } isPanning = false; } + // Reset DoubleClick + isDoubleClick = false; break; case EventType.KeyDown: if (EditorGUIUtility.editingTextField) break; From 2dfe6d3a91ad55d1c0ef055ab898df0bb1e7181c Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Wed, 13 Feb 2019 21:06:56 +0100 Subject: [PATCH 09/69] Fixed formatting --- Scripts/Editor/NodeEditorAction.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 5add067..dd49a06 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -22,12 +22,11 @@ namespace XNodeEditor { [NonSerialized] private List draggedOutputReroutes = new List(); private RerouteReference hoveredReroute = new RerouteReference(); private List selectedReroutes = new List(); - private Rect nodeRects; // Is this used? private Vector2 dragBoxStart; private UnityEngine.Object[] preBoxSelection; private RerouteReference[] preBoxSelectionReroute; private Rect selectionBox; - private bool isDoubleClick = false; + private bool isDoubleClick = false; private struct RerouteReference { public XNode.NodePort port; @@ -175,7 +174,7 @@ namespace XNodeEditor { } else if (e.control || e.shift) DeselectNode(hoveredNode); // Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown. - isDoubleClick = ( e.clickCount == 2 ); + isDoubleClick = (e.clickCount == 2); e.Use(); currentActivity = NodeActivity.HoldNode; @@ -246,9 +245,8 @@ namespace XNodeEditor { SelectNode(hoveredNode, false); // Double click to center node - if ( isDoubleClick ) - { - Vector2 nodeDimension = nodeSizes.ContainsKey( hoveredNode ) ? nodeSizes[ hoveredNode ] / 2 : new Vector2(0f, 0f); + if (isDoubleClick) { + Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero; panOffset = -hoveredNode.position - nodeDimension; } } From cda19862e544cedd5db2b9458bc7694c0bd76b6b Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 15 Feb 2019 08:22:35 +0100 Subject: [PATCH 10/69] Update CONTRIBUTING.md --- CONTRIBUTING.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b96a660..8638230 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,14 @@ If your feature aims to cover something not related to editing nodes, it general Skim through the code and you'll get the hang of it quickly. * Methods, Types and properties PascalCase * Variables camelCase -* Public methods XML commented +* Public methods XML commented. Params described if not obvious +* Explicit usage of brackets when doing multiple math operations on the same line + +## Formatting +I use VSCode with the C# FixFormat extension and the following setting overrides: +```json +"csharpfixformat.style.spaces.beforeParenthesis": false, +"csharpfixformat.style.indent.regionIgnored": true +``` * Open braces on same line as condition * 4 spaces for indentation. From d0104f24203526557f20d440339f193e9795340f Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 15 Feb 2019 09:05:33 +0100 Subject: [PATCH 11/69] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 904e146..16d51b4 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ With a minimal footprint, it is ideal as a base for custom state machines, dialo * No runtime reflection (unless you need to edit/build node graphs at runtime. In this case, all reflection is cached.) * Does not rely on any 3rd party plugins * Custom node inspector code is very similar to regular custom inspector code +* Supported from Unity 5.3 and up ### Wiki * [Getting started](https://github.com/Siccity/xNode/wiki/Getting%20Started) - create your very first node node and graph From 973f9beb1d33ffe1cb7177b1810f1b1f9a024ec9 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 16 Feb 2019 01:22:05 +0100 Subject: [PATCH 12/69] Added support for new SettingsProvider system #109 --- Scripts/Editor/NodeEditorPreferences.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index e4213ec..e896f34 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -81,7 +81,20 @@ namespace XNodeEditor { return settings[lastKey]; } +#if UNITY_2019_1_OR_NEWER + [SettingsProvider] + public static SettingsProvider CreateXNodeSettingsProvider() { + SettingsProvider provider = new SettingsProvider("Preferences/Node Editor", SettingsScope.User) { + guiHandler = (searchContext) => { XNodeEditor.NodeEditorPreferences.PreferencesGUI(); }, + keywords = new HashSet(new [] { "xNode", "node", "editor", "graph", "connections", "noodles", "ports" }) + }; + return provider; + } +#endif + +#if !UNITY_2019_1_OR_NEWER [PreferenceItem("Node Editor")] +#endif private static void PreferencesGUI() { VerifyLoaded(); Settings settings = NodeEditorPreferences.settings[lastKey]; From 84e2af7916303cbc205017ce5ca0af0ffe9f4c75 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 16 Feb 2019 01:32:52 +0100 Subject: [PATCH 13/69] Cleanup Postfixed attribute classes with Attribute Added Attributes region --- Scripts/Editor/NodeEditorReflection.cs | 8 ++++---- Scripts/Node.cs | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index 04c6484..21dbf7a 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -42,9 +42,9 @@ namespace XNodeEditor { public static Dictionary GetNodeTint() { Dictionary tints = new Dictionary(); for (int i = 0; i < nodeTypes.Length; i++) { - var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeTint), true); + var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeTintAttribute), true); if (attribs == null || attribs.Length == 0) continue; - XNode.Node.NodeTint attrib = attribs[0] as XNode.Node.NodeTint; + XNode.Node.NodeTintAttribute attrib = attribs[0] as XNode.Node.NodeTintAttribute; tints.Add(nodeTypes[i], attrib.color); } return tints; @@ -53,9 +53,9 @@ namespace XNodeEditor { public static Dictionary GetNodeWidth() { Dictionary widths = new Dictionary(); for (int i = 0; i < nodeTypes.Length; i++) { - var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeWidth), true); + var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeWidthAttribute), true); if (attribs == null || attribs.Length == 0) continue; - XNode.Node.NodeWidth attrib = attribs[0] as XNode.Node.NodeWidth; + XNode.Node.NodeWidthAttribute attrib = attribs[0] as XNode.Node.NodeWidthAttribute; widths.Add(nodeTypes[i], attrib.width); } return widths; diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 4a9ace9..43d716e 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -63,7 +63,6 @@ namespace XNode { /// Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable public static NodeGraph graphHotfix; - protected void OnEnable() { if (graphHotfix != null) graph = graphHotfix; graphHotfix = null; @@ -210,6 +209,7 @@ namespace XNode { return JsonUtility.ToJson(this).GetHashCode(); } +#region Attributes /// Mark a serializable field as an input port. You can access this through [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class InputAttribute : Attribute { @@ -255,19 +255,19 @@ namespace XNode { } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class NodeTint : Attribute { + public class NodeTintAttribute : Attribute { public Color color; /// Specify a color for this node type /// Red [0.0f .. 1.0f] /// Green [0.0f .. 1.0f] /// Blue [0.0f .. 1.0f] - public NodeTint(float r, float g, float b) { + public NodeTintAttribute(float r, float g, float b) { color = new Color(r, g, b); } /// Specify a color for this node type /// HEX color value - public NodeTint(string hex) { + public NodeTintAttribute(string hex) { ColorUtility.TryParseHtmlString(hex, out color); } @@ -275,20 +275,21 @@ namespace XNode { /// Red [0 .. 255] /// Green [0 .. 255] /// Blue [0 .. 255] - public NodeTint(byte r, byte g, byte b) { + public NodeTintAttribute(byte r, byte g, byte b) { color = new Color32(r, g, b, byte.MaxValue); } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class NodeWidth : Attribute { + public class NodeWidthAttribute : Attribute { public int width; /// Specify a width for this node type /// Width - public NodeWidth(int width) { + public NodeWidthAttribute(int width) { this.width = width; } } +#endregion [Serializable] private class NodePortDictionary : Dictionary, ISerializationCallbackReceiver { [SerializeField] private List keys = new List(); From 71defcbdd5b74996e2604676bdc2001b9143e191 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 16 Feb 2019 02:36:42 +0100 Subject: [PATCH 14/69] Implemented typeConstraint in InputAttribute --- Scripts/Editor/NodeEditorAction.cs | 4 ++-- Scripts/Editor/NodeEditorGUILayout.cs | 14 ++++++------- Scripts/Node.cs | 29 ++++++++++++++++++++------- Scripts/NodeDataCache.cs | 2 +- Scripts/NodePort.cs | 24 +++++++++++++++++++++- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index dd49a06..315f23b 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -60,7 +60,7 @@ namespace XNodeEditor { case EventType.MouseDrag: if (e.button == 0) { if (IsDraggingPort) { - if (IsHoveringPort && hoveredPort.IsInput) { + if (IsHoveringPort && hoveredPort.IsInput && draggedOutput.CanConnectTo(hoveredPort)) { if (!draggedOutput.IsConnectedTo(hoveredPort)) { draggedOutputTarget = hoveredPort; } @@ -422,7 +422,7 @@ namespace XNodeEditor { Rect fromRect; if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return; Vector2 from = fromRect.center; - col.a = 0.6f; + col.a = draggedOutputTarget != null ? 1.0f : 0.6f; Vector2 to = Vector2.zero; for (int i = 0; i < draggedOutputReroutes.Count; i++) { to = draggedOutputReroutes[i]; diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 629c594..b136dc9 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -260,7 +260,7 @@ namespace XNodeEditor { /// The serializedObject of the node /// Connection type of added instance ports /// Called on the list on creation. Use this if you want to customize the created ReorderableList - public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, Action onCreation = null) { + public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action onCreation = null) { XNode.Node node = serializedObject.targetObject as XNode.Node; Predicate isMatchingInstancePort = @@ -279,7 +279,7 @@ namespace XNodeEditor { // If a ReorderableList isn't cached for this array, do so. if (list == null) { SerializedProperty arrayData = serializedObject.FindProperty(fieldName); - list = CreateReorderableList(fieldName, instancePorts, arrayData, type, serializedObject, io, connectionType, onCreation); + list = CreateReorderableList(fieldName, instancePorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation); if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list); else reorderableListCache.Add(serializedObject.targetObject, new Dictionary() { { fieldName, list } }); } @@ -287,7 +287,7 @@ namespace XNodeEditor { list.DoLayoutList(); } - private static ReorderableList CreateReorderableList(string fieldName, List instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, Action onCreation) { + private static ReorderableList CreateReorderableList(string fieldName, List instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action onCreation) { bool hasArrayData = arrayData != null && arrayData.isArray; int arraySize = hasArrayData ? arrayData.arraySize : 0; XNode.Node node = serializedObject.targetObject as XNode.Node; @@ -375,8 +375,8 @@ namespace XNodeEditor { int i = 0; while (node.HasPort(newName)) newName = fieldName + " " + (++i); - if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, newName); - else node.AddInstanceInput(type, connectionType, newName); + if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName); + else node.AddInstanceInput(type, connectionType, typeConstraint, newName); serializedObject.Update(); EditorUtility.SetDirty(node); if (hasArrayData) arrayData.InsertArrayElementAtIndex(arraySize); @@ -422,8 +422,8 @@ namespace XNodeEditor { string newName = arrayData.name + " 0"; int i = 0; while (node.HasPort(newName)) newName = arrayData.name + " " + (++i); - if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, newName); - else node.AddInstanceInput(type, connectionType, newName); + if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, typeConstraint, newName); + else node.AddInstanceInput(type, connectionType, typeConstraint, newName); EditorUtility.SetDirty(node); instancePortCount++; } diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 43d716e..7c06f92 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -41,6 +41,16 @@ namespace XNode { Override, } + /// Tells which types of input to allow + public enum TypeConstraint { + /// Allow all types of input + None, + /// Allow similar and inherited types + Inherited, + /// Allow only similar types + Strict, + } + /// Iterate over all ports on this node. public IEnumerable Ports { get { foreach (NodePort port in ports.Values) yield return port; } } /// Iterate over all outputs on this node. @@ -87,21 +97,21 @@ namespace XNode { /// Convenience function. /// /// - public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) { - return AddInstancePort(type, NodePort.IO.Input, connectionType, fieldName); + public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddInstancePort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName); } /// Convenience function. /// /// - public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) { - return AddInstancePort(type, NodePort.IO.Output, connectionType, fieldName); + public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddInstancePort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName); } /// Add a dynamic, serialized port to this node. /// /// - private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) { + private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { if (fieldName == null) { fieldName = "instanceInput_0"; int i = 0; @@ -110,7 +120,7 @@ namespace XNode { Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this); return ports[fieldName]; } - NodePort port = new NodePort(fieldName, type, direction, connectionType, this); + NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this); ports.Add(fieldName, port); return port; } @@ -216,14 +226,18 @@ namespace XNode { public ShowBackingValue backingValue; public ConnectionType connectionType; public bool instancePortList; + public TypeConstraint typeConstraint; /// Mark a serializable field as an input port. You can access this through /// Should we display the backing value for this port as an editor field? /// Should we allow multiple connections? - public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, bool instancePortList = false) { + /// Constrains which input connections can be made to this port + /// If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays + public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool instancePortList = false) { this.backingValue = backingValue; this.connectionType = connectionType; this.instancePortList = instancePortList; + this.typeConstraint = typeConstraint; } } @@ -237,6 +251,7 @@ namespace XNode { /// Mark a serializable field as an output port. You can access this through /// Should we display the backing value for this port as an editor field? /// Should we allow multiple connections? + /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, bool instancePortList = false) { this.backingValue = backingValue; this.connectionType = connectionType; diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index 8d96146..434ffc5 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -30,7 +30,7 @@ namespace XNode { NodePort staticPort; if (staticPorts.TryGetValue(port.fieldName, out staticPort)) { // If port exists but with wrong settings, remove it. Re-add it later. - if (port.connectionType != staticPort.connectionType || port.IsDynamic || port.direction != staticPort.direction) ports.Remove(port.fieldName); + if (port.connectionType != staticPort.connectionType || port.IsDynamic || port.direction != staticPort.direction || port.typeConstraint != staticPort.typeConstraint) ports.Remove(port.fieldName); else port.ValueType = staticPort.ValueType; } // If port doesn't exist anymore, remove it diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index 3f55795..24e4941 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -21,6 +21,7 @@ namespace XNode { public IO direction { get { return _direction; } } public Node.ConnectionType connectionType { get { return _connectionType; } } + public Node.TypeConstraint typeConstraint { get { return _typeConstraint; } } /// Is this port connected to anytihng? public bool IsConnected { get { return connections.Count != 0; } } @@ -49,6 +50,7 @@ namespace XNode { [SerializeField] private List connections = new List(); [SerializeField] private IO _direction; [SerializeField] private Node.ConnectionType _connectionType; + [SerializeField] private Node.TypeConstraint _typeConstraint; [SerializeField] private bool _dynamic; /// Construct a static targetless nodeport. Used as a template. @@ -61,6 +63,7 @@ namespace XNode { if (attribs[i] is Node.InputAttribute) { _direction = IO.Input; _connectionType = (attribs[i] as Node.InputAttribute).connectionType; + _typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint; } else if (attribs[i] is Node.OutputAttribute) { _direction = IO.Output; _connectionType = (attribs[i] as Node.OutputAttribute).connectionType; @@ -75,17 +78,19 @@ namespace XNode { _direction = nodePort.direction; _dynamic = nodePort._dynamic; _connectionType = nodePort._connectionType; + _typeConstraint = nodePort._typeConstraint; _node = node; } /// Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. - public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node node) { + public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Node node) { _fieldName = fieldName; this.ValueType = type; _direction = direction; _node = node; _dynamic = true; _connectionType = connectionType; + _typeConstraint = typeConstraint; } /// Checks all connections for invalid references, and removes them. @@ -240,6 +245,23 @@ namespace XNode { return false; } + /// Returns true if this port can connect to specified port + public bool CanConnectTo(NodePort port) { + // Figure out which is input and which is output + NodePort input = null, output = null; + if (IsInput) input = this; + else output = this; + if (port.IsInput) input = port; + else output = port; + // If there isn't one of each, they can't connect + if (input == null || output == null) return false; + // Check type constraints + if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; + if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; + // Success + return true; + } + /// Disconnect this port from another port public void Disconnect(NodePort port) { // Remove this ports connection to the other From 3a8ae366f2eae2122c86df9e694e8c388b56d131 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 16 Feb 2019 03:06:25 +0100 Subject: [PATCH 15/69] Fixed #103 Similar issue as #100 GetField was not returning private fields. The method now not only looks for private fields, but also fields inside inherited classes --- Scripts/Editor/NodeEditorGUILayout.cs | 2 +- Scripts/Editor/NodeEditorReflection.cs | 9 +++++++++ Scripts/Editor/NodeEditorUtilities.cs | 4 +--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 629c594..3330caa 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -154,7 +154,7 @@ namespace XNodeEditor { private static System.Type GetType(SerializedProperty property) { System.Type parentType = property.serializedObject.targetObject.GetType(); - System.Reflection.FieldInfo fi = parentType.GetField(property.propertyPath); + System.Reflection.FieldInfo fi = NodeEditorWindow.GetFieldInfo(property.serializedObject.targetObject.GetType(), property.name); return fi.FieldType; } diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index 21dbf7a..a2be28e 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -61,6 +61,15 @@ namespace XNodeEditor { return widths; } + /// Get FieldInfo of a field, including those that are private and/or inherited + public static FieldInfo GetFieldInfo(Type type, string fieldName) { + // If we can't find field in the first run, it's probably a private field in a base class. + FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + // Search base classes for private fields only. Public fields are found above + while (field == null && (type = type.BaseType) != typeof(XNode.Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + return field; + } + /// Get all classes deriving from baseType via reflection public static Type[] GetDerivedTypes(Type baseType) { List types = new List(); diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index c0eeeb3..24dff06 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -36,9 +36,7 @@ namespace XNodeEditor { public static bool GetAttrib(Type classType, string fieldName, out T attribOut) where T : Attribute { // If we can't find field in the first run, it's probably a private field in a base class. - FieldInfo field = classType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - // Search base classes for private fields only. Public fields are found above - while (field == null && (classType = classType.BaseType) != typeof(XNode.Node)) field = classType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo field = NodeEditorWindow.GetFieldInfo(classType, fieldName); // This shouldn't happen. Ever. if (field == null) { Debug.LogWarning("Field " + fieldName + " couldnt be found"); From 57d3a03a91d38f722d16c988e72d5cd5dffe489c Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 16 Feb 2019 12:21:21 +0100 Subject: [PATCH 16/69] Removed GetHashCode override. This should improve performance, but has previously caused a slew of bugs. Bugtest thoroughly before merging --- Scripts/Editor/NodeEditorGUI.cs | 10 +++------- Scripts/Node.cs | 4 ---- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 9f48f0e..e22a0b4 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -241,12 +241,10 @@ namespace XNodeEditor { selectionCache = new List(Selection.objects); } - //Active node is hashed before and after node GUI to detect changes - int nodeHash = 0; System.Reflection.MethodInfo onValidate = null; if (Selection.activeObject != null && Selection.activeObject is XNode.Node) { onValidate = Selection.activeObject.GetType().GetMethod("OnValidate"); - if (onValidate != null) nodeHash = Selection.activeObject.GetHashCode(); + if (onValidate != null) EditorGUI.BeginChangeCheck(); } BeginZoomed(position, zoom, topPadding); @@ -383,12 +381,10 @@ namespace XNodeEditor { if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray(); EndZoomed(position, zoom, topPadding); - //If a change in hash is detected in the selected node, call OnValidate method. + //If a change in is detected in the selected node, call OnValidate method. //This is done through reflection because OnValidate is only relevant in editor, //and thus, the code should not be included in build. - if (nodeHash != 0) { - if (onValidate != null && nodeHash != Selection.activeObject.GetHashCode()) onValidate.Invoke(Selection.activeObject, null); - } + if (onValidate != null && EditorGUI.EndChangeCheck()) onValidate.Invoke(Selection.activeObject, null); } private bool ShouldBeCulled(XNode.Node node) { diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 43d716e..da04d86 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -205,10 +205,6 @@ namespace XNode { foreach (NodePort port in Ports) port.ClearConnections(); } - public override int GetHashCode() { - return JsonUtility.ToJson(this).GetHashCode(); - } - #region Attributes /// Mark a serializable field as an input port. You can access this through [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] From f6404e9d9df75592b5bee8bc607a28343a23589c Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 17 Feb 2019 03:18:49 +0100 Subject: [PATCH 17/69] Unity 5.3 support --- Scripts/Editor/Drawers/NodeEnumDrawer.cs | 9 +++++++++ Scripts/Editor/NodeEditorAction.cs | 12 ++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Scripts/Editor/Drawers/NodeEnumDrawer.cs b/Scripts/Editor/Drawers/NodeEnumDrawer.cs index 3e770f2..7478f94 100644 --- a/Scripts/Editor/Drawers/NodeEnumDrawer.cs +++ b/Scripts/Editor/Drawers/NodeEnumDrawer.cs @@ -24,12 +24,21 @@ namespace XNodeEditor { string enumName = ""; if (property.enumValueIndex >= 0 && property.enumValueIndex < property.enumDisplayNames.Length) enumName = property.enumDisplayNames[property.enumValueIndex]; +#if UNITY_2017_1_OR_NEWER // Display dropdown if (EditorGUI.DropdownButton(position, new GUIContent(enumName), FocusType.Passive)) { // Position is all wrong if we show the dropdown during the node draw phase. // Instead, add it to onLateGUI to display it later. NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property); } +#else + // Display dropdown + if (GUI.Button(position, new GUIContent(enumName), "MiniPopup")) { + // Position is all wrong if we show the dropdown during the node draw phase. + // Instead, add it to onLateGUI to display it later. + NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property); + } +#endif EditorGUI.EndProperty(); } diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 315f23b..c436d33 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -289,7 +289,7 @@ namespace XNodeEditor { case EventType.KeyDown: if (EditorGUIUtility.editingTextField) break; else if (e.keyCode == KeyCode.F) Home(); - if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX) { + if (IsMac()) { if (e.keyCode == KeyCode.Return) RenameSelectedNode(); } else { if (e.keyCode == KeyCode.F2) RenameSelectedNode(); @@ -300,7 +300,7 @@ namespace XNodeEditor { if (e.commandName == "SoftDelete") { if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); e.Use(); - } else if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX && e.commandName == "Delete") { + } else if (IsMac() && e.commandName == "Delete") { if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); e.Use(); } else if (e.commandName == "Duplicate") { @@ -319,6 +319,14 @@ namespace XNodeEditor { } } + public bool IsMac() { +#if UNITY_2017_1_OR_NEWER + return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX; +#else + return SystemInfo.operatingSystem.StartsWith("Mac"); +#endif + } + private void RecalculateDragOffsets(Event current) { dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count]; // Selected nodes From 662e919aaabf205d825515ed976a093d5353b110 Mon Sep 17 00:00:00 2001 From: Simon Rodriguez Date: Sun, 17 Feb 2019 11:57:17 +0100 Subject: [PATCH 18/69] Renaming node to nothing (#112) When the name is empty or just whitespaces, it reverts to the original node type name --- Scripts/Editor/NodeEditor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index f38b510..44594f0 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -106,6 +106,7 @@ namespace XNodeEditor { } public void Rename(string newName) { + if (string.IsNullOrWhiteSpace(newName)) newName = UnityEditor.ObjectNames.NicifyVariableName(target.GetType().Name); target.name = newName; AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); } From 8cb647734a7959d9f91317bdb669db4da7b448b6 Mon Sep 17 00:00:00 2001 From: Simon Rodriguez Date: Sun, 17 Feb 2019 12:00:11 +0100 Subject: [PATCH 19/69] EditorGUI.FocusTextInControl(null) sets editingTextField to true even if null is being sent in. (#113) Then the editor won't listen to keyboard input. --- Scripts/Editor/NodeEditorAction.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index c436d33..efc1cb6 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -235,6 +235,7 @@ namespace XNodeEditor { // If click outside node, release field focus if (!isPanning) { EditorGUI.FocusTextInControl(null); + EditorGUIUtility.editingTextField = false; } if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } From c3067018535dbe228c2c374fc1f0911f0bbdb3cb Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 17 Feb 2019 12:24:58 +0100 Subject: [PATCH 20/69] .Net 2.0 compatability --- Scripts/Editor/NodeEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 44594f0..f131f66 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -106,7 +106,7 @@ namespace XNodeEditor { } public void Rename(string newName) { - if (string.IsNullOrWhiteSpace(newName)) newName = UnityEditor.ObjectNames.NicifyVariableName(target.GetType().Name); + if (newName == null || newName.Trim() == "") newName = UnityEditor.ObjectNames.NicifyVariableName(target.GetType().Name); target.name = newName; AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); } From 5acb7e4ba85df35918af610be0e5f9d63cbeb1a4 Mon Sep 17 00:00:00 2001 From: NoiseCrime Date: Mon, 18 Feb 2019 12:28:35 +0000 Subject: [PATCH 21/69] UI Sharpness complete fix (#115) Complete fix to address bluriness of the UI in the Node Editor Window. Previous fixes were not all encompassing and failed to account for now even dimensions of Editor Window. --- Scripts/Editor/NodeEditorAction.cs | 8 ++------ Scripts/Editor/NodeEditorWindow.cs | 5 +++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index efc1cb6..b4f6562 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -136,12 +136,7 @@ namespace XNodeEditor { Repaint(); } } else if (e.button == 1 || e.button == 2) { - Vector2 tempOffset = panOffset; - tempOffset += e.delta * zoom; - // Round value to increase crispyness of UI text - tempOffset.x = Mathf.Round(tempOffset.x); - tempOffset.y = Mathf.Round(tempOffset.y); - panOffset = tempOffset; + panOffset += e.delta * zoom; isPanning = true; } break; @@ -276,6 +271,7 @@ namespace XNodeEditor { GenericMenu menu = new GenericMenu(); NodeEditor.GetEditor(hoveredNode).AddContextMenuItems(menu); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); + e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places. } else if (!IsHoveringNode) { GenericMenu menu = new GenericMenu(); graphEditor.AddContextMenuItems(menu); diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 72fd4ce..740fd20 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -123,8 +123,9 @@ namespace XNodeEditor { public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) { Vector2 center = position.size * 0.5f; - float xOffset = (center.x * zoom + (panOffset.x + gridPosition.x)); - float yOffset = (center.y * zoom + (panOffset.y + gridPosition.y)); + // UI Sharpness complete fix - Round final offset not panOffset + float xOffset = Mathf.Round(center.x * zoom + (panOffset.x + gridPosition.x)); + float yOffset = Mathf.Round(center.y * zoom + (panOffset.y + gridPosition.y)); return new Vector2(xOffset, yOffset); } From 43038cfcc1d5c592bdd2288ef0be41cdbf37a9e4 Mon Sep 17 00:00:00 2001 From: Sergi Tortosa Ortiz-Villajos Date: Tue, 19 Feb 2019 22:50:45 +0100 Subject: [PATCH 22/69] Support inherited attributes (#116) --- Scripts/Editor/NodeEditorUtilities.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index 24dff06..18e295f 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -25,7 +25,7 @@ namespace XNodeEditor { public static bool GetAttrib(object[] attribs, out T attribOut) where T : Attribute { for (int i = 0; i < attribs.Length; i++) { - if (attribs[i].GetType() == typeof(T)) { + if (attribs[i] is T){ attribOut = attribs[i] as T; return true; } @@ -43,7 +43,7 @@ namespace XNodeEditor { attribOut = null; return false; } - object[] attribs = field.GetCustomAttributes(typeof(T), false); + object[] attribs = field.GetCustomAttributes(typeof(T), true); return GetAttrib(attribs, out attribOut); } From 2939fe49353900d37835fd21710a31382a09a84d Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 2 Mar 2019 11:53:48 +0100 Subject: [PATCH 23/69] Added automatic drawing of instance ports Fixed minor issue getting parentType twice --- Scripts/Editor/NodeEditor.cs | 7 +++++++ Scripts/Editor/NodeEditorGUILayout.cs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index f131f66..d222f9e 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -52,6 +52,7 @@ namespace XNodeEditor { string[] excludes = { "m_Script", "graph", "position", "ports" }; portPositions = new Dictionary(); + // Iterate through serialized properties and draw them like the Inspector (But with ports) SerializedProperty iterator = serializedObject.GetIterator(); bool enterChildren = true; EditorGUIUtility.labelWidth = 84; @@ -60,6 +61,12 @@ namespace XNodeEditor { if (excludes.Contains(iterator.name)) continue; NodeEditorGUILayout.PropertyField(iterator, true); } + + // Iterate through instance ports and draw them in the order in which they are serialized + foreach(XNode.NodePort instancePort in target.InstancePorts) { + NodeEditorGUILayout.PortField(instancePort); + } + serializedObject.ApplyModifiedProperties(); } diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index ee4394d..7cc1a21 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -154,7 +154,7 @@ namespace XNodeEditor { private static System.Type GetType(SerializedProperty property) { System.Type parentType = property.serializedObject.targetObject.GetType(); - System.Reflection.FieldInfo fi = NodeEditorWindow.GetFieldInfo(property.serializedObject.targetObject.GetType(), property.name); + System.Reflection.FieldInfo fi = NodeEditorWindow.GetFieldInfo(parentType, property.name); return fi.FieldType; } From 29acbf63489d58b2573ff327723c7651dc458fab Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 3 Mar 2019 00:51:06 +0100 Subject: [PATCH 24/69] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 16d51b4..2b59422 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![alt text](https://user-images.githubusercontent.com/37786733/41541140-71602302-731a-11e8-9434-79b3a57292b6.png) + [![Discord](https://img.shields.io/discord/361769369404964864.svg)](https://discord.gg/qgPrHv4) [![GitHub issues](https://img.shields.io/github/issues/Siccity/xNode.svg)](https://github.com/Siccity/xNode/issues) @@ -15,7 +15,9 @@ Thinking of developing a node-based plugin? Then this is for you. You can downlo xNode is super userfriendly, intuitive and will help you reap the benefits of node graphs in no time. With a minimal footprint, it is ideal as a base for custom state machines, dialogue systems, decision makers etc. -![editor](https://user-images.githubusercontent.com/6402525/33150712-01d60602-cfd5-11e7-83b4-eb008fd9d711.png) +

+ +

### Key features * Lightweight in runtime From 6783324018c231710e5898d3c3e5d40acef7f152 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 4 Mar 2019 18:29:43 +0100 Subject: [PATCH 25/69] Fix drawing instance twice for instanceportlists --- Scripts/Editor/NodeEditor.cs | 1 + Scripts/Editor/NodeEditorGUILayout.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index d222f9e..1fffc66 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -64,6 +64,7 @@ namespace XNodeEditor { // Iterate through instance ports and draw them in the order in which they are serialized foreach(XNode.NodePort instancePort in target.InstancePorts) { + if (NodeEditorGUILayout.IsInstancePortListPort(instancePort)) continue; NodeEditorGUILayout.PortField(instancePort); } diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 7cc1a21..b2ac5e8 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -254,6 +254,18 @@ namespace XNodeEditor { GUI.color = col; } + /// Is this port part of an InstancePortList? + public static bool IsInstancePortListPort(XNode.NodePort port) { + string[] parts = port.fieldName.Split(' '); + if (parts.Length != 2) return false; + Dictionary cache; + if (reorderableListCache.TryGetValue(port.node, out cache)) { + ReorderableList list; + if (cache.TryGetValue(parts[0], out list)) return true; + } + return false; + } + /// Draw an editable list of instance ports. Port names are named as "[fieldName] [index]" /// Supply a list for editable values /// Value type of added instance ports From 01d7f782e41e145cb52bf9bd7bea8f4a7cf2e927 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 4 Mar 2019 19:04:00 +0100 Subject: [PATCH 26/69] Fixed issues relating to InstancePortList --- Scripts/Editor/NodeEditorGUILayout.cs | 35 +++++++++++++++++---------- Scripts/Node.cs | 2 ++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index b2ac5e8..4eca18c 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -301,7 +301,6 @@ namespace XNodeEditor { private static ReorderableList CreateReorderableList(string fieldName, List instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action onCreation) { bool hasArrayData = arrayData != null && arrayData.isArray; - int arraySize = hasArrayData ? arrayData.arraySize : 0; XNode.Node node = serializedObject.targetObject as XNode.Node; ReorderableList list = new ReorderableList(instancePorts, null, true, true, true, true); string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName); @@ -317,8 +316,10 @@ namespace XNodeEditor { SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); EditorGUI.PropertyField(rect, itemData, true); } else EditorGUI.LabelField(rect, port.fieldName); - Vector2 pos = rect.position + (port.IsOutput?new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); - NodeEditorGUILayout.PortField(pos, port); + if (port != null) { + Vector2 pos = rect.position + (port.IsOutput?new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); + NodeEditorGUILayout.PortField(pos, port); + } }; list.elementHeightCallback = (int index) => { @@ -391,11 +392,22 @@ namespace XNodeEditor { else node.AddInstanceInput(type, connectionType, typeConstraint, newName); serializedObject.Update(); EditorUtility.SetDirty(node); - if (hasArrayData) arrayData.InsertArrayElementAtIndex(arraySize); + if (hasArrayData) { + arrayData.InsertArrayElementAtIndex(arrayData.arraySize); + } serializedObject.ApplyModifiedProperties(); }; list.onRemoveCallback = (ReorderableList rl) => { + + Predicate isMatchingInstancePort = + x => { + string[] split = x.Split(' '); + if (split != null && split.Length == 2) return split[0] == fieldName; + else return false; + }; + instancePorts = node.InstancePorts.Where(x => isMatchingInstancePort(x.fieldName)).OrderBy(x => x.fieldName).ToList(); + int index = rl.index; // Clear the removed ports connections instancePorts[index].ClearConnections(); @@ -413,23 +425,21 @@ namespace XNodeEditor { EditorUtility.SetDirty(node); if (hasArrayData) { arrayData.DeleteArrayElementAtIndex(index); - arraySize--; // Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues - if (instancePorts.Count <= arraySize) { - while (instancePorts.Count <= arraySize) { - arrayData.DeleteArrayElementAtIndex(--arraySize); + if (instancePorts.Count <= arrayData.arraySize) { + while (instancePorts.Count <= arrayData.arraySize) { + arrayData.DeleteArrayElementAtIndex(arrayData.arraySize - 1); } UnityEngine.Debug.LogWarning("Array size exceeded instance ports size. Excess items removed."); } serializedObject.ApplyModifiedProperties(); serializedObject.Update(); } - }; if (hasArrayData) { int instancePortCount = instancePorts.Count; - while (instancePortCount < arraySize) { + while (instancePortCount < arrayData.arraySize) { // Add instance port postfixed with an index number string newName = arrayData.name + " 0"; int i = 0; @@ -439,9 +449,8 @@ namespace XNodeEditor { EditorUtility.SetDirty(node); instancePortCount++; } - while (arraySize < instancePortCount) { - arrayData.InsertArrayElementAtIndex(arraySize); - arraySize++; + while (arrayData.arraySize < instancePortCount) { + arrayData.InsertArrayElementAtIndex(arrayData.arraySize); } serializedObject.ApplyModifiedProperties(); serializedObject.Update(); diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 8785280..74eb309 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -127,6 +127,8 @@ namespace XNode { /// Remove an instance port from the node public void RemoveInstancePort(string fieldName) { + NodePort instancePort = GetPort(fieldName); + if (instancePort == null) throw new ArgumentException("port " + fieldName + " doesn't exist"); RemoveInstancePort(GetPort(fieldName)); } From 4263e45b2482229dab42ba4613d60a8f90874f92 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 8 Mar 2019 19:50:31 +0100 Subject: [PATCH 27/69] Rework node renaming Renaming now happens on a separate popup. --- Scripts/Editor/NodeEditor.cs | 34 ++------------- Scripts/Editor/NodeEditorAction.cs | 7 +++- Scripts/Editor/RenamePopup.cs | 66 ++++++++++++++++++++++++++++++ Scripts/Editor/RenamePopup.cs.meta | 13 ++++++ 4 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 Scripts/Editor/RenamePopup.cs create mode 100644 Scripts/Editor/RenamePopup.cs.meta diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 1fffc66..a6d1748 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -13,34 +13,9 @@ namespace XNodeEditor { /// Fires every whenever a node was modified through the editor public static Action onUpdateNode; public static Dictionary portPositions; - public int renaming; public virtual void OnHeaderGUI() { - string title = target.name; - if (renaming != 0) { - if (Selection.Contains(target)) { - int controlID = EditorGUIUtility.GetControlID(FocusType.Keyboard) + 1; - if (renaming == 1) { - EditorGUIUtility.keyboardControl = controlID; - EditorGUIUtility.editingTextField = true; - renaming = 2; - } - target.name = EditorGUILayout.TextField(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); - if (!EditorGUIUtility.editingTextField) { - Debug.Log("Finish renaming"); - Rename(target.name); - renaming = 0; - } - } - else { - // Selection changed, so stop renaming. - GUILayout.Label(title, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); - Rename(target.name); - renaming = 0; - } - } else { - GUILayout.Label(title, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); - } + GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); } /// Draws standard field editors for all public fields @@ -63,7 +38,7 @@ namespace XNodeEditor { } // Iterate through instance ports and draw them in the order in which they are serialized - foreach(XNode.NodePort instancePort in target.InstancePorts) { + foreach (XNode.NodePort instancePort in target.InstancePorts) { if (NodeEditorGUILayout.IsInstancePortListPort(instancePort)) continue; NodeEditorGUILayout.PortField(instancePort); } @@ -109,10 +84,7 @@ namespace XNodeEditor { } } - public void InitiateRename() { - renaming = 1; - } - + /// Rename the node asset. This will trigger a reimport of the node. public void Rename(string newName) { if (newName == null || newName.Trim() == "") newName = UnityEditor.ObjectNames.NicifyVariableName(target.GetType().Name); target.name = newName; diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index b4f6562..211f3cc 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -366,7 +366,12 @@ namespace XNodeEditor { public void RenameSelectedNode() { if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { XNode.Node node = Selection.activeObject as XNode.Node; - NodeEditor.GetEditor(node).InitiateRename(); + Vector2 size; + if (nodeSizes.TryGetValue(node, out size)) { + RenamePopup.Show(Selection.activeObject, size.x); + } else { + RenamePopup.Show(Selection.activeObject); + } } } diff --git a/Scripts/Editor/RenamePopup.cs b/Scripts/Editor/RenamePopup.cs new file mode 100644 index 0000000..a49a948 --- /dev/null +++ b/Scripts/Editor/RenamePopup.cs @@ -0,0 +1,66 @@ +using UnityEditor; +using UnityEngine; + +namespace XNodeEditor { + /// Utility for renaming assets + public class RenamePopup : EditorWindow { + public static RenamePopup current { get; private set; } + public Object target; + public string input; + + private bool firstFrame = true; + + /// Show a rename popup for an asset at mouse position. Will trigger reimport of the asset on apply. + public static RenamePopup Show(Object target, float width = 200) { + RenamePopup window = EditorWindow.GetWindow(true, "Rename " + target.name, true); + if (current != null) current.Close(); + current = window; + window.target = target; + window.input = target.name; + window.minSize = new Vector2(100, 44); + window.position = new Rect(0, 0, width, 44); + GUI.FocusControl("ClearAllFocus"); + window.UpdatePositionToMouse(); + return window; + } + + private void UpdatePositionToMouse() { + if (Event.current == null) return; + Vector3 mousePoint = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); + Rect pos = position; + pos.x = mousePoint.x - position.width * 0.5f; + pos.y = mousePoint.y - 10; + position = pos; + } + + private void OnLostFocus() { + // Make the popup close on lose focus + Close(); + } + + private void OnGUI() { + if (firstFrame) { + UpdatePositionToMouse(); + firstFrame = false; + } + input = EditorGUILayout.TextField(input); + Event e = Event.current; + // If input is empty, revert name to default instead + if (input == null || input.Trim() == "") { + if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) { + target.name = UnityEditor.ObjectNames.NicifyVariableName(target.GetType().Name); + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + Close(); + } + } + // Rename asset to input text + else { + if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) { + target.name = input; + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + Close(); + } + } + } + } +} \ No newline at end of file diff --git a/Scripts/Editor/RenamePopup.cs.meta b/Scripts/Editor/RenamePopup.cs.meta new file mode 100644 index 0000000..5c40a02 --- /dev/null +++ b/Scripts/Editor/RenamePopup.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 4ef3ddc25518318469bce838980c64be +timeCreated: 1552067957 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 2f4adadb72b01c522b82b765b6db43955c89a7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20K=C4=B1r=C4=B1mgeri?= Date: Wed, 20 Mar 2019 12:42:47 +0300 Subject: [PATCH 28/69] Zoom out limit feature (#122) Zoom out limit added --- Scripts/Editor/NodeEditorPreferences.cs | 3 ++- Scripts/Editor/NodeEditorWindow.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index e896f34..bd2cb55 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -23,6 +23,7 @@ namespace XNodeEditor { [SerializeField] private Color32 _gridBgColor = new Color(0.18f, 0.18f, 0.18f); public Color32 gridBgColor { get { return _gridBgColor; } set { _gridBgColor = value; _gridTexture = null; } } + public float zoomOutLimit = 5f; public Color32 highlightColor = new Color32(255, 255, 255, 255); public bool gridSnap = true; public bool autoSave = true; @@ -113,7 +114,7 @@ namespace XNodeEditor { EditorGUILayout.LabelField("Grid", EditorStyles.boldLabel); settings.gridSnap = EditorGUILayout.Toggle(new GUIContent("Snap", "Hold CTRL in editor to invert"), settings.gridSnap); settings.zoomToMouse = EditorGUILayout.Toggle(new GUIContent("Zoom to Mouse", "Zooms towards mouse position"), settings.zoomToMouse); - + settings.zoomOutLimit = EditorGUILayout.FloatField(new GUIContent("Zoom out Limit", "Upper limit to zoom"), settings.zoomOutLimit); settings.gridLineColor = EditorGUILayout.ColorField("Color", settings.gridLineColor); settings.gridBgColor = EditorGUILayout.ColorField(" ", settings.gridBgColor); if (GUI.changed) { diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 740fd20..08b0a56 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -61,7 +61,7 @@ namespace XNodeEditor { public XNode.NodeGraph graph; public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } } private Vector2 _panOffset; - public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, 1f, 5f); Repaint(); } } + public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, 1f, NodeEditorPreferences.GetSettings().zoomOutLimit); Repaint(); } } private float _zoom = 1; void OnFocus() { @@ -163,4 +163,4 @@ namespace XNodeEditor { } } } -} \ No newline at end of file +} From af0523db2d5311401f4033ae40ebb71477d9da65 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 1 Apr 2019 19:16:54 +0200 Subject: [PATCH 29/69] Fixed #124 - Errors on DynamicPortList with >10 items Was using OrderBy(x => x.fieldName). The resulting order would then be 1, 10, 11, .. , 2, 3, 4, etc. Fixed by parsing the indices as ints, and ordering by that value instead --- Scripts/Editor/NodeEditorGUILayout.cs | 36 ++++++++++++++++----------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 4eca18c..32e527f 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -275,13 +275,17 @@ namespace XNodeEditor { public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action onCreation = null) { XNode.Node node = serializedObject.targetObject as XNode.Node; - Predicate isMatchingInstancePort = - x => { - string[] split = x.Split(' '); - if (split != null && split.Length == 2) return split[0] == fieldName; - else return false; - }; - List instancePorts = node.InstancePorts.Where(x => isMatchingInstancePort(x.fieldName)).OrderBy(x => x.fieldName).ToList(); + var indexedPorts = node.InstancePorts.Select(x => { + string[] split = x.fieldName.Split(' '); + if (split != null && split.Length == 2 && split[0] == fieldName) { + int i = -1; + if (int.TryParse(split[1], out i)) { + return new { index = i, port = x }; + } + } + return new { index = -1, port = (XNode.NodePort) null }; + }); + List instancePorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); ReorderableList list = null; Dictionary rlc; @@ -400,13 +404,17 @@ namespace XNodeEditor { list.onRemoveCallback = (ReorderableList rl) => { - Predicate isMatchingInstancePort = - x => { - string[] split = x.Split(' '); - if (split != null && split.Length == 2) return split[0] == fieldName; - else return false; - }; - instancePorts = node.InstancePorts.Where(x => isMatchingInstancePort(x.fieldName)).OrderBy(x => x.fieldName).ToList(); + var indexedPorts = node.InstancePorts.Select(x => { + string[] split = x.fieldName.Split(' '); + if (split != null && split.Length == 2 && split[0] == fieldName) { + int i = -1; + if (int.TryParse(split[1], out i)) { + return new { index = i, port = x }; + } + } + return new { index = -1, port = (XNode.NodePort) null }; + }); + instancePorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); int index = rl.index; // Clear the removed ports connections From d8a4a41a8f57007dc3931a44e1ddd58070051e48 Mon Sep 17 00:00:00 2001 From: Robin Neal Date: Wed, 3 Apr 2019 19:39:21 +0100 Subject: [PATCH 30/69] Open non-persistent graphs on double-click (#126) --- Scripts/Editor/NodeEditorWindow.cs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 08b0a56..24bd8f5 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -70,6 +70,20 @@ namespace XNodeEditor { if (graphEditor != null && NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } + [InitializeOnLoadMethod] + private static void OnLoad() { + Selection.selectionChanged -= OnSelectionChanged; + Selection.selectionChanged += OnSelectionChanged; + } + + /// Handle Selection Change events + private static void OnSelectionChanged() { + XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph; + if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) { + Open(nodeGraph); + } + } + /// Create editor window public static NodeEditorWindow Init() { NodeEditorWindow w = CreateInstance(); @@ -147,14 +161,21 @@ namespace XNodeEditor { public static bool OnOpen(int instanceID, int line) { XNode.NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.NodeGraph; if (nodeGraph != null) { - NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow; - w.wantsMouseMove = true; - w.graph = nodeGraph; + Open(nodeGraph); return true; } return false; } + /// Open the provided graph in the NodeEditor + public static void Open(XNode.NodeGraph graph) { + if (!graph) return; + + NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow; + w.wantsMouseMove = true; + w.graph = graph; + } + /// Repaint all open NodeEditorWindows. public static void RepaintAll() { NodeEditorWindow[] windows = Resources.FindObjectsOfTypeAll(); From f6e0e3bc4d839b41ce792c161229d530117b29da Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Wed, 3 Apr 2019 21:50:29 +0200 Subject: [PATCH 31/69] Added NodeGraphEditor.GetPortColor --- Scripts/Editor/NodeEditorGUI.cs | 2 +- Scripts/Editor/NodeEditorGUILayout.cs | 6 +++--- Scripts/Editor/NodeGraphEditor.cs | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index e22a0b4..b26b9ba 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -184,7 +184,7 @@ namespace XNodeEditor { Rect fromRect; if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue; - Color connectionColor = graphEditor.GetTypeColor(output.ValueType); + Color connectionColor = graphEditor.GetPortColor(output); for (int k = 0; k < output.ConnectionCount; k++) { XNode.NodePort input = output.GetConnection(k); diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 32e527f..e3f7c60 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -142,7 +142,7 @@ namespace XNodeEditor { Color backgroundColor = new Color32(90, 97, 105, 255); Color tint; if (NodeEditorWindow.nodeTint.TryGetValue(port.node.GetType(), out tint)) backgroundColor *= tint; - Color col = NodeEditorWindow.current.graphEditor.GetTypeColor(port.ValueType); + Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); // Register the handle position @@ -199,7 +199,7 @@ namespace XNodeEditor { Color backgroundColor = new Color32(90, 97, 105, 255); Color tint; if (NodeEditorWindow.nodeTint.TryGetValue(port.node.GetType(), out tint)) backgroundColor *= tint; - Color col = NodeEditorWindow.current.graphEditor.GetTypeColor(port.ValueType); + Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); // Register the handle position @@ -228,7 +228,7 @@ namespace XNodeEditor { Color backgroundColor = new Color32(90, 97, 105, 255); Color tint; if (NodeEditorWindow.nodeTint.TryGetValue(port.node.GetType(), out tint)) backgroundColor *= tint; - Color col = NodeEditorWindow.current.graphEditor.GetTypeColor(port.ValueType); + Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); // Register the handle position diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 81a53c8..1286f7a 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -57,6 +57,10 @@ namespace XNodeEditor { NodeEditorWindow.AddCustomContextMenuItems(menu, target); } + public virtual Color GetPortColor(XNode.NodePort port) { + return GetTypeColor(port.ValueType); + } + public virtual Color GetTypeColor(Type type) { return NodeEditorPreferences.GetTypeColor(type); } From 8adc4fd459a103dcd5cf51c28d8b168136901a51 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 6 Apr 2019 13:27:44 +0200 Subject: [PATCH 32/69] Fixed #128, #127, #64 - Added NodeEditorBase.OnCreate, OnGraphEditor.OnOpen, and NodeEditorBase.window --- Scripts/Editor/NodeEditorAction.cs | 2 +- Scripts/Editor/NodeEditorBase.cs | 9 ++++++++- Scripts/Editor/NodeEditorGUI.cs | 6 ++---- Scripts/Editor/NodeEditorWindow.cs | 11 ++++++++++- Scripts/Editor/NodeGraphEditor.cs | 7 +++++-- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 211f3cc..0570777 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -269,7 +269,7 @@ namespace XNodeEditor { } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false); GenericMenu menu = new GenericMenu(); - NodeEditor.GetEditor(hoveredNode).AddContextMenuItems(menu); + NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places. } else if (!IsHoveringNode) { diff --git a/Scripts/Editor/NodeEditorBase.cs b/Scripts/Editor/NodeEditorBase.cs index b94f290..ab463e6 100644 --- a/Scripts/Editor/NodeEditorBase.cs +++ b/Scripts/Editor/NodeEditorBase.cs @@ -14,10 +14,11 @@ namespace XNodeEditor.Internal { /// Custom editors defined with [CustomNodeEditor] private static Dictionary editorTypes; private static Dictionary editors = new Dictionary(); + public NodeEditorWindow window; public K target; public SerializedObject serializedObject; - public static T GetEditor(K target) { + public static T GetEditor(K target, NodeEditorWindow window) { if (target == null) return null; T editor; if (!editors.TryGetValue(target, out editor)) { @@ -26,9 +27,12 @@ namespace XNodeEditor.Internal { editor = Activator.CreateInstance(editorType) as T; editor.target = target; editor.serializedObject = new SerializedObject(target); + editor.window = window; + editor.OnCreate(); editors.Add(target, editor); } if (editor.target == null) editor.target = target; + if (editor.window != window) editor.window = window; if (editor.serializedObject == null) editor.serializedObject = new SerializedObject(target); return editor; } @@ -56,6 +60,9 @@ namespace XNodeEditor.Internal { } } + /// Called on creation, after references have been set + public virtual void OnCreate() { } + public interface INodeEditorAttrib { Type GetInspectedType(); } diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index b26b9ba..509f871 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -18,9 +18,7 @@ namespace XNodeEditor { Event e = Event.current; Matrix4x4 m = GUI.matrix; if (graph == null) return; - graphEditor = NodeGraphEditor.GetEditor(graph); - graphEditor.position = position; - + ValidateGraphEditor(); Controls(); DrawGrid(position, zoom, panOffset); @@ -288,7 +286,7 @@ namespace XNodeEditor { _portConnectionPoints = _portConnectionPoints.Where(x => x.Key.node != node).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } - NodeEditor nodeEditor = NodeEditor.GetEditor(node); + NodeEditor nodeEditor = NodeEditor.GetEditor(node, this); NodeEditor.portPositions = new Dictionary(); diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 24bd8f5..a575c8d 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -66,7 +66,7 @@ namespace XNodeEditor { void OnFocus() { current = this; - graphEditor = NodeGraphEditor.GetEditor(graph); + ValidateGraphEditor(); if (graphEditor != null && NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } @@ -84,6 +84,15 @@ namespace XNodeEditor { } } + /// Make sure the graph editor is assigned and to the right object + private void ValidateGraphEditor() { + NodeGraphEditor graphEditor = NodeGraphEditor.GetEditor(graph, this); + if (this.graphEditor != graphEditor) { + this.graphEditor = graphEditor; + graphEditor.OnOpen(); + } + } + /// Create editor window public static NodeEditorWindow Init() { NodeEditorWindow w = CreateInstance(); diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 1286f7a..8a9d2f0 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -8,13 +8,16 @@ namespace XNodeEditor { /// Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. [CustomNodeGraphEditor(typeof(XNode.NodeGraph))] public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase { - /// The position of the window in screen space. - public Rect position; + [Obsolete("Use window.position instead")] + public Rect position { get { return window.position; } set { window.position = value; } } /// Are we currently renaming a node? protected bool isRenaming; public virtual void OnGUI() { } + /// Called when opened by NodeEditorWindow + public virtual void OnOpen() { } + public virtual Texture2D GetGridTexture() { return NodeEditorPreferences.GetSettings().gridTexture; } From 32fa3d7c9f12ebfb2ef6cfb334f8e0d604d49c51 Mon Sep 17 00:00:00 2001 From: Woland Date: Thu, 11 Apr 2019 12:27:56 +0200 Subject: [PATCH 33/69] gitignore: Ignore meta files for git dot files --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 68af404..1460751 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,8 @@ sysinfo.txt /Examples/ README.md.meta LICENSE.md.meta -CONTRIBUTING.md.meta \ No newline at end of file +CONTRIBUTING.md.meta + +.git.meta +.gitignore.meta +.gitattributes.meta From 7eaa15af4a70aea49267fb39da81a8c317916b51 Mon Sep 17 00:00:00 2001 From: Woland Date: Sun, 14 Apr 2019 03:21:29 +0200 Subject: [PATCH 34/69] NodeEditorReflection: Catch ReflectionTypeLoadException (#131) * NodeEditorReflection: Catch ReflectionTypeLoadException Can happen if dll can not be loaded for some reason * Removed unnecessary editor precompile tags --- Scripts/Editor/NodeEditorReflection.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index a2be28e..18b72fa 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -75,7 +75,9 @@ namespace XNodeEditor { List types = new List(); System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { - types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); + try { + types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); + } catch(ReflectionTypeLoadException) {} } return types.ToArray(); } @@ -162,4 +164,4 @@ namespace XNodeEditor { } } } -} \ No newline at end of file +} From 4cf71137402c667e56a2e3d7a4889db31f193f6d Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 14 Apr 2019 19:01:26 +0200 Subject: [PATCH 35/69] Improved noodle tangents (#136) --- Scripts/Editor/NodeEditorAction.cs | 16 ++-- Scripts/Editor/NodeEditorGUI.cs | 111 ++++++++++++++------------ Scripts/Editor/NodeEditorGUILayout.cs | 30 ++++--- 3 files changed, 88 insertions(+), 69 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 0570777..792631a 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -428,19 +428,19 @@ namespace XNodeEditor { public void DrawDraggedConnection() { if (IsDraggingPort) { Color col = NodeEditorPreferences.GetTypeColor(draggedOutput.ValueType); + col.a = draggedOutputTarget != null ? 1.0f : 0.6f; Rect fromRect; if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return; - Vector2 from = fromRect.center; - col.a = draggedOutputTarget != null ? 1.0f : 0.6f; - Vector2 to = Vector2.zero; + List gridPoints = new List(); + gridPoints.Add(fromRect.center); for (int i = 0; i < draggedOutputReroutes.Count; i++) { - to = draggedOutputReroutes[i]; - DrawConnection(from, to, col); - from = to; + gridPoints.Add(draggedOutputReroutes[i]); } - to = draggedOutputTarget != null ? portConnectionPoints[draggedOutputTarget].center : WindowToGridPosition(Event.current.mousePosition); - DrawConnection(from, to, col); + if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center); + else gridPoints.Add(WindowToGridPosition(Event.current.mousePosition)); + + DrawNoodle(col, gridPoints); Color bgcol = Color.black; Color frcol = col; diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 509f871..c773571 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -114,52 +114,70 @@ namespace XNodeEditor { if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } - /// Draw a bezier from startpoint to endpoint, both in grid coordinates - public void DrawConnection(Vector2 startPoint, Vector2 endPoint, Color col) { - startPoint = GridToWindowPosition(startPoint); - endPoint = GridToWindowPosition(endPoint); - + /// Draw a bezier from output to input in grid coordinates + public void DrawNoodle(Color col, List gridPoints) { + Vector2[] windowPoints = gridPoints.Select(x => GridToWindowPosition(x)).ToArray(); + Handles.color = col; + int length = gridPoints.Count; switch (NodeEditorPreferences.GetSettings().noodleType) { case NodeEditorPreferences.NoodleType.Curve: - Vector2 startTangent = startPoint; - if (startPoint.x < endPoint.x) startTangent.x = Mathf.LerpUnclamped(startPoint.x, endPoint.x, 0.7f); - else startTangent.x = Mathf.LerpUnclamped(startPoint.x, endPoint.x, -0.7f); + Vector2 outputTangent = Vector2.right; + for (int i = 0; i < length - 1; i++) { + Vector2 inputTangent = Vector2.left; - Vector2 endTangent = endPoint; - if (startPoint.x > endPoint.x) endTangent.x = Mathf.LerpUnclamped(endPoint.x, startPoint.x, -0.7f); - else endTangent.x = Mathf.LerpUnclamped(endPoint.x, startPoint.x, 0.7f); - Handles.DrawBezier(startPoint, endPoint, startTangent, endTangent, col, null, 4); + if (i == 0) outputTangent = Vector2.right * Vector2.Distance(windowPoints[i], windowPoints[i + 1]) * 0.01f * zoom; + if (i < length - 2) { + Vector2 ab = (windowPoints[i + 1] - windowPoints[i]).normalized; + Vector2 cb = (windowPoints[i + 1] - windowPoints[i + 2]).normalized; + Vector2 ac = (windowPoints[i + 2] - windowPoints[i]).normalized; + Vector2 p = (ab + cb) * 0.5f; + float tangentLength = (Vector2.Distance(windowPoints[i], windowPoints[i + 1]) + Vector2.Distance(windowPoints[i + 1], windowPoints[i + 2])) * 0.005f * zoom; + float side = ((ac.x * (windowPoints[i + 1].y - windowPoints[i].y)) - (ac.y * (windowPoints[i + 1].x - windowPoints[i].x))); + + p = new Vector2(-p.y, p.x) * Mathf.Sign(side) * tangentLength; + inputTangent = p; + } + else { + inputTangent = Vector2.left * Vector2.Distance(windowPoints[i], windowPoints[i + 1]) * 0.01f * zoom; + } + + Handles.DrawBezier(windowPoints[i], windowPoints[i + 1], windowPoints[i] + ((outputTangent * 50) / zoom), windowPoints[i + 1] + ((inputTangent * 50) / zoom), col, null, 4); + outputTangent = -inputTangent; + } break; case NodeEditorPreferences.NoodleType.Line: - Handles.color = col; - Handles.DrawAAPolyLine(5, startPoint, endPoint); + for (int i = 0; i < length - 1; i++) { + Handles.DrawAAPolyLine(5, windowPoints[i], windowPoints[i + 1]); + } break; case NodeEditorPreferences.NoodleType.Angled: - Handles.color = col; - if (startPoint.x <= endPoint.x - (50 / zoom)) { - float midpoint = (startPoint.x + endPoint.x) * 0.5f; - Vector2 start_1 = startPoint; - Vector2 end_1 = endPoint; - start_1.x = midpoint; - end_1.x = midpoint; - Handles.DrawAAPolyLine(5, startPoint, start_1); - Handles.DrawAAPolyLine(5, start_1, end_1); - Handles.DrawAAPolyLine(5, end_1, endPoint); - } else { - float midpoint = (startPoint.y + endPoint.y) * 0.5f; - Vector2 start_1 = startPoint; - Vector2 end_1 = endPoint; - start_1.x += 25 / zoom; - end_1.x -= 25 / zoom; - Vector2 start_2 = start_1; - Vector2 end_2 = end_1; - start_2.y = midpoint; - end_2.y = midpoint; - Handles.DrawAAPolyLine(5, startPoint, start_1); - Handles.DrawAAPolyLine(5, start_1, start_2); - Handles.DrawAAPolyLine(5, start_2, end_2); - Handles.DrawAAPolyLine(5, end_2, end_1); - Handles.DrawAAPolyLine(5, end_1, endPoint); + for (int i = 0; i < length - 1; i++) { + if (i == length - 1) continue; // Skip last index + if (windowPoints[i].x <= windowPoints[i + 1].x - (50 / zoom)) { + float midpoint = (windowPoints[i].x + windowPoints[i + 1].x) * 0.5f; + Vector2 start_1 = windowPoints[i]; + Vector2 end_1 = windowPoints[i + 1]; + start_1.x = midpoint; + end_1.x = midpoint; + Handles.DrawAAPolyLine(5, windowPoints[i], start_1); + Handles.DrawAAPolyLine(5, start_1, end_1); + Handles.DrawAAPolyLine(5, end_1, windowPoints[i + 1]); + } else { + float midpoint = (windowPoints[i].y + windowPoints[i + 1].y) * 0.5f; + Vector2 start_1 = windowPoints[i]; + Vector2 end_1 = windowPoints[i + 1]; + start_1.x += 25 / zoom; + end_1.x -= 25 / zoom; + Vector2 start_2 = start_1; + Vector2 end_2 = end_1; + start_2.y = midpoint; + end_2.y = midpoint; + Handles.DrawAAPolyLine(5, windowPoints[i], start_1); + Handles.DrawAAPolyLine(5, start_1, start_2); + Handles.DrawAAPolyLine(5, start_2, end_2); + Handles.DrawAAPolyLine(5, end_2, end_1); + Handles.DrawAAPolyLine(5, end_1, windowPoints[i + 1]); + } } break; } @@ -193,18 +211,13 @@ namespace XNodeEditor { Rect toRect; if (!_portConnectionPoints.TryGetValue(input, out toRect)) continue; - Vector2 from = fromRect.center; - Vector2 to = Vector2.zero; List reroutePoints = output.GetReroutePoints(k); - // Loop through reroute points and draw the path - for (int i = 0; i < reroutePoints.Count; i++) { - to = reroutePoints[i]; - DrawConnection(from, to, connectionColor); - from = to; - } - to = toRect.center; - DrawConnection(from, to, connectionColor); + List gridPoints = new List(); + gridPoints.Add(fromRect.center); + gridPoints.AddRange(reroutePoints); + gridPoints.Add(toRect.center); + DrawNoodle(connectionColor, gridPoints); // Loop through reroute points again and draw the points for (int i = 0; i < reroutePoints.Count; i++) { diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index e3f7c60..a1c5d83 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -417,20 +417,26 @@ namespace XNodeEditor { instancePorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); int index = rl.index; - // Clear the removed ports connections - instancePorts[index].ClearConnections(); - // Move following connections one step up to replace the missing connection - for (int k = index + 1; k < instancePorts.Count(); k++) { - for (int j = 0; j < instancePorts[k].ConnectionCount; j++) { - XNode.NodePort other = instancePorts[k].GetConnection(j); - instancePorts[k].Disconnect(other); - instancePorts[k - 1].Connect(other); + + if (instancePorts.Count > index) { + // Clear the removed ports connections + instancePorts[index].ClearConnections(); + // Move following connections one step up to replace the missing connection + for (int k = index + 1; k < instancePorts.Count(); k++) { + for (int j = 0; j < instancePorts[k].ConnectionCount; j++) { + XNode.NodePort other = instancePorts[k].GetConnection(j); + instancePorts[k].Disconnect(other); + instancePorts[k - 1].Connect(other); + } } + // Remove the last instance port, to avoid messing up the indexing + node.RemoveInstancePort(instancePorts[instancePorts.Count() - 1].fieldName); + serializedObject.Update(); + EditorUtility.SetDirty(node); + } else { + Debug.LogWarning("InstancePorts[" + index + "] out of range. Length was " + instancePorts.Count + ". Skipping."); } - // Remove the last instance port, to avoid messing up the indexing - node.RemoveInstancePort(instancePorts[instancePorts.Count() - 1].fieldName); - serializedObject.Update(); - EditorUtility.SetDirty(node); + if (hasArrayData) { arrayData.DeleteArrayElementAtIndex(index); // Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues From eb164225c8a8d091559103dd629697a2e671eb6a Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 15 Apr 2019 00:02:52 +0200 Subject: [PATCH 36/69] Fixed #134 - Marked some more methods as virtual --- Scripts/NodeGraph.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index 6b95fc2..38d0364 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -11,7 +11,7 @@ namespace XNode { /// See: [SerializeField] public List nodes = new List(); - /// Add a node to the graph by type + /// Add a node to the graph by type (convenience method - will call the System.Type version) public T AddNode() where T : Node { return AddNode(typeof(T)) as T; } @@ -37,14 +37,14 @@ namespace XNode { /// Safely remove a node and all its connections /// The node to remove - public void RemoveNode(Node node) { + public virtual void RemoveNode(Node node) { node.ClearConnections(); nodes.Remove(node); if (Application.isPlaying) Destroy(node); } /// Remove all nodes and connections from the graph - public void Clear() { + public virtual void Clear() { if (Application.isPlaying) { for (int i = 0; i < nodes.Count; i++) { Destroy(nodes[i]); @@ -54,7 +54,7 @@ namespace XNode { } /// Create a new deep copy of this graph - public XNode.NodeGraph Copy() { + public virtual XNode.NodeGraph Copy() { // Instantiate a new nodegraph instance NodeGraph graph = Instantiate(this); // Instantiate all nodes inside the graph From 65162d553eb3a29fa868fce35ca3ea074659226f Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 5 May 2019 10:28:47 +0200 Subject: [PATCH 37/69] Marked NodeGraph.OnDestroy virtual --- Scripts/NodeGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index 38d0364..6a0cead 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -77,7 +77,7 @@ namespace XNode { return graph; } - private void OnDestroy() { + protected virtual void OnDestroy() { // Remove all nodes prior to graph destruction Clear(); } From c3e85a9f8292ae42bd33bf4defc57c66b0f748a6 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 5 May 2019 10:45:28 +0200 Subject: [PATCH 38/69] UPGRADE NOTICE: Renamed 'instance ports' to 'dynamic ports'. To upgrade, simply rename all your method calls involving instance ports eg. 'AddInstanceOutput' to the dynamic port equivalent eg. 'AddDynamicOutput' There is no functional difference. The community just agreed this was a more fitting name for the feature. --- Scripts/Editor/NodeEditor.cs | 8 +-- Scripts/Editor/NodeEditorGUILayout.cs | 94 +++++++++++++-------------- Scripts/Node.cs | 72 ++++++++++---------- 3 files changed, 87 insertions(+), 87 deletions(-) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index a6d1748..91d92d0 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -37,10 +37,10 @@ namespace XNodeEditor { NodeEditorGUILayout.PropertyField(iterator, true); } - // Iterate through instance ports and draw them in the order in which they are serialized - foreach (XNode.NodePort instancePort in target.InstancePorts) { - if (NodeEditorGUILayout.IsInstancePortListPort(instancePort)) continue; - NodeEditorGUILayout.PortField(instancePort); + // Iterate through dynamic ports and draw them in the order in which they are serialized + foreach (XNode.NodePort dynamicPort in target.DynamicPorts) { + if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort)) continue; + NodeEditorGUILayout.PortField(dynamicPort); } serializedObject.ApplyModifiedProperties(); diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index a1c5d83..7c4de29 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -50,14 +50,14 @@ namespace XNodeEditor { // Get data from [Input] attribute XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; XNode.Node.InputAttribute inputAttribute; - bool instancePortList = false; + bool dynamicPortList = false; if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute)) { - instancePortList = inputAttribute.instancePortList; + dynamicPortList = inputAttribute.dynamicPortList; showBacking = inputAttribute.backingValue; } //Call GUILayout.Space if Space attribute is set and we are NOT drawing a PropertyField - bool useLayoutSpace = instancePortList || + bool useLayoutSpace = dynamicPortList || showBacking == XNode.Node.ShowBackingValue.Never || (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); if (spacePadding > 0 && useLayoutSpace) { @@ -65,10 +65,10 @@ namespace XNodeEditor { spacePadding = 0; } - if (instancePortList) { + if (dynamicPortList) { Type type = GetType(property); XNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; - InstancePortList(property.name, type, property.serializedObject, port.direction, connectionType); + DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); return; } switch (showBacking) { @@ -95,14 +95,14 @@ namespace XNodeEditor { // Get data from [Output] attribute XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; XNode.Node.OutputAttribute outputAttribute; - bool instancePortList = false; + bool dynamicPortList = false; if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute)) { - instancePortList = outputAttribute.instancePortList; + dynamicPortList = outputAttribute.dynamicPortList; showBacking = outputAttribute.backingValue; } //Call GUILayout.Space if Space attribute is set and we are NOT drawing a PropertyField - bool useLayoutSpace = instancePortList || + bool useLayoutSpace = dynamicPortList || showBacking == XNode.Node.ShowBackingValue.Never || (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); if (spacePadding > 0 && useLayoutSpace) { @@ -110,10 +110,10 @@ namespace XNodeEditor { spacePadding = 0; } - if (instancePortList) { + if (dynamicPortList) { Type type = GetType(property); XNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; - InstancePortList(property.name, type, property.serializedObject, port.direction, connectionType); + DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); return; } switch (showBacking) { @@ -254,8 +254,8 @@ namespace XNodeEditor { GUI.color = col; } - /// Is this port part of an InstancePortList? - public static bool IsInstancePortListPort(XNode.NodePort port) { + /// Is this port part of a DynamicPortList? + public static bool IsDynamicPortListPort(XNode.NodePort port) { string[] parts = port.fieldName.Split(' '); if (parts.Length != 2) return false; Dictionary cache; @@ -266,16 +266,16 @@ namespace XNodeEditor { return false; } - /// Draw an editable list of instance ports. Port names are named as "[fieldName] [index]" + /// Draw an editable list of dynamic ports. Port names are named as "[fieldName] [index]" /// Supply a list for editable values - /// Value type of added instance ports + /// Value type of added dynamic ports /// The serializedObject of the node - /// Connection type of added instance ports + /// Connection type of added dynamic ports /// Called on the list on creation. Use this if you want to customize the created ReorderableList - public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action onCreation = null) { + public static void DynamicPortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action onCreation = null) { XNode.Node node = serializedObject.targetObject as XNode.Node; - var indexedPorts = node.InstancePorts.Select(x => { + var indexedPorts = node.DynamicPorts.Select(x => { string[] split = x.fieldName.Split(' '); if (split != null && split.Length == 2 && split[0] == fieldName) { int i = -1; @@ -285,7 +285,7 @@ namespace XNodeEditor { } return new { index = -1, port = (XNode.NodePort) null }; }); - List instancePorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); + List dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); ReorderableList list = null; Dictionary rlc; @@ -295,18 +295,18 @@ namespace XNodeEditor { // If a ReorderableList isn't cached for this array, do so. if (list == null) { SerializedProperty arrayData = serializedObject.FindProperty(fieldName); - list = CreateReorderableList(fieldName, instancePorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation); + list = CreateReorderableList(fieldName, dynamicPorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation); if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list); else reorderableListCache.Add(serializedObject.targetObject, new Dictionary() { { fieldName, list } }); } - list.list = instancePorts; + list.list = dynamicPorts; list.DoLayoutList(); } - private static ReorderableList CreateReorderableList(string fieldName, List instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action onCreation) { + private static ReorderableList CreateReorderableList(string fieldName, List dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action onCreation) { bool hasArrayData = arrayData != null && arrayData.isArray; XNode.Node node = serializedObject.targetObject as XNode.Node; - ReorderableList list = new ReorderableList(instancePorts, null, true, true, true, true); + ReorderableList list = new ReorderableList(dynamicPorts, null, true, true, true, true); string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName); list.drawElementCallback = @@ -387,13 +387,13 @@ namespace XNodeEditor { }; list.onAddCallback = (ReorderableList rl) => { - // Add instance port postfixed with an index number + // Add dynamic port postfixed with an index number string newName = fieldName + " 0"; int i = 0; while (node.HasPort(newName)) newName = fieldName + " " + (++i); - if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName); - else node.AddInstanceInput(type, connectionType, typeConstraint, newName); + if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName); + else node.AddDynamicInput(type, connectionType, typeConstraint, newName); serializedObject.Update(); EditorUtility.SetDirty(node); if (hasArrayData) { @@ -404,7 +404,7 @@ namespace XNodeEditor { list.onRemoveCallback = (ReorderableList rl) => { - var indexedPorts = node.InstancePorts.Select(x => { + var indexedPorts = node.DynamicPorts.Select(x => { string[] split = x.fieldName.Split(' '); if (split != null && split.Length == 2 && split[0] == fieldName) { int i = -1; @@ -414,37 +414,37 @@ namespace XNodeEditor { } return new { index = -1, port = (XNode.NodePort) null }; }); - instancePorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); + dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); int index = rl.index; - if (instancePorts.Count > index) { + if (dynamicPorts.Count > index) { // Clear the removed ports connections - instancePorts[index].ClearConnections(); + dynamicPorts[index].ClearConnections(); // Move following connections one step up to replace the missing connection - for (int k = index + 1; k < instancePorts.Count(); k++) { - for (int j = 0; j < instancePorts[k].ConnectionCount; j++) { - XNode.NodePort other = instancePorts[k].GetConnection(j); - instancePorts[k].Disconnect(other); - instancePorts[k - 1].Connect(other); + for (int k = index + 1; k < dynamicPorts.Count(); k++) { + for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++) { + XNode.NodePort other = dynamicPorts[k].GetConnection(j); + dynamicPorts[k].Disconnect(other); + dynamicPorts[k - 1].Connect(other); } } - // Remove the last instance port, to avoid messing up the indexing - node.RemoveInstancePort(instancePorts[instancePorts.Count() - 1].fieldName); + // Remove the last dynamic port, to avoid messing up the indexing + node.RemoveDynamicPort(dynamicPorts[dynamicPorts.Count() - 1].fieldName); serializedObject.Update(); EditorUtility.SetDirty(node); } else { - Debug.LogWarning("InstancePorts[" + index + "] out of range. Length was " + instancePorts.Count + ". Skipping."); + Debug.LogWarning("DynamicPorts[" + index + "] out of range. Length was " + dynamicPorts.Count + ". Skipping."); } if (hasArrayData) { arrayData.DeleteArrayElementAtIndex(index); // Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues - if (instancePorts.Count <= arrayData.arraySize) { - while (instancePorts.Count <= arrayData.arraySize) { + if (dynamicPorts.Count <= arrayData.arraySize) { + while (dynamicPorts.Count <= arrayData.arraySize) { arrayData.DeleteArrayElementAtIndex(arrayData.arraySize - 1); } - UnityEngine.Debug.LogWarning("Array size exceeded instance ports size. Excess items removed."); + UnityEngine.Debug.LogWarning("Array size exceeded dynamic ports size. Excess items removed."); } serializedObject.ApplyModifiedProperties(); serializedObject.Update(); @@ -452,18 +452,18 @@ namespace XNodeEditor { }; if (hasArrayData) { - int instancePortCount = instancePorts.Count; - while (instancePortCount < arrayData.arraySize) { - // Add instance port postfixed with an index number + int dynamicPortCount = dynamicPorts.Count; + while (dynamicPortCount < arrayData.arraySize) { + // Add dynamic port postfixed with an index number string newName = arrayData.name + " 0"; int i = 0; while (node.HasPort(newName)) newName = arrayData.name + " " + (++i); - if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, typeConstraint, newName); - else node.AddInstanceInput(type, connectionType, typeConstraint, newName); + if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, typeConstraint, newName); + else node.AddDynamicInput(type, connectionType, typeConstraint, newName); EditorUtility.SetDirty(node); - instancePortCount++; + dynamicPortCount++; } - while (arrayData.arraySize < instancePortCount) { + while (arrayData.arraySize < dynamicPortCount) { arrayData.InsertArrayElementAtIndex(arrayData.arraySize); } serializedObject.ApplyModifiedProperties(); diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 74eb309..895abbb 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -57,12 +57,12 @@ namespace XNode { public IEnumerable Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } } /// Iterate over all inputs on this node. public IEnumerable Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } } - /// Iterate over all instane ports on this node. - public IEnumerable InstancePorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } } - /// Iterate over all instance outputs on this node. - public IEnumerable InstanceOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } } - /// Iterate over all instance inputs on this node. - public IEnumerable InstanceInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } } + /// Iterate over all dynamic ports on this node. + public IEnumerable DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } } + /// Iterate over all dynamic outputs on this node. + public IEnumerable DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } } + /// Iterate over all dynamic inputs on this node. + public IEnumerable DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } } /// Parent [SerializeField] public NodeGraph graph; /// Position on the @@ -97,25 +97,25 @@ namespace XNode { /// Convenience function. /// /// - public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { - return AddInstancePort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName); + public NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName); } /// Convenience function. /// /// - public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { - return AddInstancePort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName); + public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName); } /// Add a dynamic, serialized port to this node. - /// - /// - private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + /// + /// + private NodePort AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { if (fieldName == null) { - fieldName = "instanceInput_0"; + fieldName = "dynamicInput_0"; int i = 0; - while (HasPort(fieldName)) fieldName = "instanceInput_" + (++i); + while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i); } else if (HasPort(fieldName)) { Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this); return ports[fieldName]; @@ -125,27 +125,27 @@ namespace XNode { return port; } - /// Remove an instance port from the node - public void RemoveInstancePort(string fieldName) { - NodePort instancePort = GetPort(fieldName); - if (instancePort == null) throw new ArgumentException("port " + fieldName + " doesn't exist"); - RemoveInstancePort(GetPort(fieldName)); + /// Remove an dynamic port from the node + public void RemoveDynamicPort(string fieldName) { + NodePort dynamicPort = GetPort(fieldName); + if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist"); + RemoveDynamicPort(GetPort(fieldName)); } - /// Remove an instance port from the node - public void RemoveInstancePort(NodePort port) { + /// Remove an dynamic port from the node + public void RemoveDynamicPort(NodePort port) { if (port == null) throw new ArgumentNullException("port"); else if (port.IsStatic) throw new ArgumentException("cannot remove static port"); port.ClearConnections(); ports.Remove(port.fieldName); } - /// Removes all instance ports from the node - [ContextMenu("Clear Instance Ports")] - public void ClearInstancePorts() { - List instancePorts = new List(InstancePorts); - foreach (NodePort port in instancePorts) { - RemoveInstancePort(port); + /// Removes all dynamic ports from the node + [ContextMenu("Clear Dynamic Ports")] + public void ClearDynamicPorts() { + List dynamicPorts = new List(DynamicPorts); + foreach (NodePort port in dynamicPorts) { + RemoveDynamicPort(port); } } #endregion @@ -223,18 +223,18 @@ namespace XNode { public class InputAttribute : Attribute { public ShowBackingValue backingValue; public ConnectionType connectionType; - public bool instancePortList; + public bool dynamicPortList; public TypeConstraint typeConstraint; /// Mark a serializable field as an input port. You can access this through /// Should we display the backing value for this port as an editor field? /// Should we allow multiple connections? /// Constrains which input connections can be made to this port - /// If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays - public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool instancePortList = false) { + /// If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays + public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) { this.backingValue = backingValue; this.connectionType = connectionType; - this.instancePortList = instancePortList; + this.dynamicPortList = dynamicPortList; this.typeConstraint = typeConstraint; } } @@ -244,16 +244,16 @@ namespace XNode { public class OutputAttribute : Attribute { public ShowBackingValue backingValue; public ConnectionType connectionType; - public bool instancePortList; + public bool dynamicPortList; /// Mark a serializable field as an output port. You can access this through /// Should we display the backing value for this port as an editor field? /// Should we allow multiple connections? - /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays - public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, bool instancePortList = false) { + /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays + public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, bool dynamicPortList = false) { this.backingValue = backingValue; this.connectionType = connectionType; - this.instancePortList = instancePortList; + this.dynamicPortList = dynamicPortList; } } From d7f5bd2a1a2b5a8735f02d339d4f49271181ce73 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 6 May 2019 17:54:43 +0200 Subject: [PATCH 39/69] Added obsolete methods to help with upgrading Relating the Instance port > dynamic port change --- Scripts/Editor/NodeEditorGUILayout.cs | 12 +++++++ Scripts/Node.cs | 47 ++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 7c4de29..1826107 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -254,6 +254,18 @@ namespace XNodeEditor { GUI.color = col; } +#region Obsolete + [Obsolete("Use IsDynamicPortListPort instead")] + public static bool IsInstancePortListPort(XNode.NodePort port) { + return IsDynamicPortListPort(port); + } + + [Obsolete("Use DynamicPortList instead")] + public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action onCreation = null) { + DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation); + } +#endregion + /// Is this port part of a DynamicPortList? public static bool IsDynamicPortListPort(XNode.NodePort port) { string[] parts = port.fieldName.Split(' '); diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 895abbb..cd86b95 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -51,6 +51,47 @@ namespace XNode { Strict, } +#region Obsolete + [Obsolete("Use DynamicPorts instead")] + public IEnumerable InstancePorts { get { return DynamicPorts; } } + + [Obsolete("Use DynamicOutputs instead")] + public IEnumerable InstanceOutputs { get { return DynamicOutputs; } } + + [Obsolete("Use DynamicInputs instead")] + public IEnumerable InstanceInputs { get { return DynamicInputs; } } + + [Obsolete("Use AddDynamicInput instead")] + public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddInstanceInput(type, connectionType, typeConstraint, fieldName); + } + + [Obsolete("Use AddDynamicOutput instead")] + public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddDynamicOutput(type, connectionType, typeConstraint, fieldName); + } + + [Obsolete("Use AddDynamicPort instead")] + private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName); + } + + [Obsolete("Use RemoveDynamicPort instead")] + public void RemoveInstancePort(string fieldName) { + RemoveDynamicPort(fieldName); + } + + [Obsolete("Use RemoveDynamicPort instead")] + public void RemoveInstancePort(NodePort port) { + RemoveDynamicPort(port); + } + + [Obsolete("Use ClearDynamicPorts instead")] + public void ClearInstancePorts() { + ClearDynamicPorts(); + } +#endregion + /// Iterate over all ports on this node. public IEnumerable Ports { get { foreach (NodePort port in ports.Values) yield return port; } } /// Iterate over all outputs on this node. @@ -93,7 +134,7 @@ namespace XNode { foreach (NodePort port in Ports) port.VerifyConnections(); } -#region Instance Ports +#region Dynamic Ports /// Convenience function. /// /// @@ -223,6 +264,8 @@ namespace XNode { public class InputAttribute : Attribute { public ShowBackingValue backingValue; public ConnectionType connectionType; + [Obsolete("Use dynamicPortList instead")] + public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } } public bool dynamicPortList; public TypeConstraint typeConstraint; @@ -244,6 +287,8 @@ namespace XNode { public class OutputAttribute : Attribute { public ShowBackingValue backingValue; public ConnectionType connectionType; + [Obsolete("Use dynamicPortList instead")] + public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } } public bool dynamicPortList; /// Mark a serializable field as an output port. You can access this through From 91151b327a86a55b2529e3b495c81da60533d6f4 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Thu, 9 May 2019 00:02:58 +0200 Subject: [PATCH 40/69] Bugfix: Calling base.OnBodyGUI after drawing ports would make noodles disappear --- Scripts/Editor/NodeEditor.cs | 3 +-- Scripts/Editor/NodeEditorGUI.cs | 5 ++--- Scripts/Editor/NodeEditorGUILayout.cs | 9 +++------ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 91d92d0..7652658 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -12,7 +12,7 @@ namespace XNodeEditor { /// Fires every whenever a node was modified through the editor public static Action onUpdateNode; - public static Dictionary portPositions; + public readonly static Dictionary portPositions = new Dictionary(); public virtual void OnHeaderGUI() { GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); @@ -25,7 +25,6 @@ namespace XNodeEditor { // serializedObject.ApplyModifiedProperties(); goes at the end. serializedObject.Update(); string[] excludes = { "m_Script", "graph", "position", "ports" }; - portPositions = new Dictionary(); // Iterate through serialized properties and draw them like the Inspector (But with ports) SerializedProperty iterator = serializedObject.GetIterator(); diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index c773571..c9c460f 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -301,7 +301,7 @@ namespace XNodeEditor { NodeEditor nodeEditor = NodeEditor.GetEditor(node, this); - NodeEditor.portPositions = new Dictionary(); + NodeEditor.portPositions.Clear(); //Get node position Vector2 nodePos = GridToWindowPositionNoClipped(node.position); @@ -351,8 +351,7 @@ namespace XNodeEditor { Vector2 portHandlePos = kvp.Value; portHandlePos += node.position; Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16); - if (portConnectionPoints.ContainsKey(kvp.Key)) portConnectionPoints[kvp.Key] = rect; - else portConnectionPoints.Add(kvp.Key, rect); + portConnectionPoints[kvp.Key] = rect; } } diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 1826107..344b3fb 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -147,8 +147,7 @@ namespace XNodeEditor { // Register the handle position Vector2 portPos = rect.center; - if (NodeEditor.portPositions.ContainsKey(port)) NodeEditor.portPositions[port] = portPos; - else NodeEditor.portPositions.Add(port, portPos); + NodeEditor.portPositions[port] = portPos; } } @@ -204,8 +203,7 @@ namespace XNodeEditor { // Register the handle position Vector2 portPos = rect.center; - if (NodeEditor.portPositions.ContainsKey(port)) NodeEditor.portPositions[port] = portPos; - else NodeEditor.portPositions.Add(port, portPos); + NodeEditor.portPositions[port] = portPos; } /// Add a port field to previous layout element. @@ -233,8 +231,7 @@ namespace XNodeEditor { // Register the handle position Vector2 portPos = rect.center; - if (NodeEditor.portPositions.ContainsKey(port)) NodeEditor.portPositions[port] = portPos; - else NodeEditor.portPositions.Add(port, portPos); + NodeEditor.portPositions[port] = portPos; } /// Draws an input and an output port on the same line From 29a05cba28abe90f99957babc824a6471e8f691d Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 10 May 2019 09:05:48 +0200 Subject: [PATCH 41/69] Fixed #143 - Added minZoom to preferences Also renamed zoomOutLimit to maxZoom --- Scripts/Editor/NodeEditorPreferences.cs | 13 +++++++++++-- Scripts/Editor/NodeEditorWindow.cs | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index bd2cb55..4fc6b7b 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -23,7 +23,12 @@ namespace XNodeEditor { [SerializeField] private Color32 _gridBgColor = new Color(0.18f, 0.18f, 0.18f); public Color32 gridBgColor { get { return _gridBgColor; } set { _gridBgColor = value; _gridTexture = null; } } - public float zoomOutLimit = 5f; + [Obsolete("Use maxZoom instead")] + public float zoomOutLimit { get { return maxZoom; } set { maxZoom = value; } } + + [UnityEngine.Serialization.FormerlySerializedAs("zoomOutLimit")] + public float maxZoom = 5f; + public float minZoom = 1f; public Color32 highlightColor = new Color32(255, 255, 255, 255); public bool gridSnap = true; public bool autoSave = true; @@ -114,7 +119,11 @@ namespace XNodeEditor { EditorGUILayout.LabelField("Grid", EditorStyles.boldLabel); settings.gridSnap = EditorGUILayout.Toggle(new GUIContent("Snap", "Hold CTRL in editor to invert"), settings.gridSnap); settings.zoomToMouse = EditorGUILayout.Toggle(new GUIContent("Zoom to Mouse", "Zooms towards mouse position"), settings.zoomToMouse); - settings.zoomOutLimit = EditorGUILayout.FloatField(new GUIContent("Zoom out Limit", "Upper limit to zoom"), settings.zoomOutLimit); + EditorGUILayout.LabelField("Zoom"); + EditorGUI.indentLevel++; + settings.maxZoom = EditorGUILayout.FloatField(new GUIContent("Max", "Upper limit to zoom"), settings.maxZoom); + settings.minZoom = EditorGUILayout.FloatField(new GUIContent("Min", "Lower limit to zoom"), settings.minZoom); + EditorGUI.indentLevel--; settings.gridLineColor = EditorGUILayout.ColorField("Color", settings.gridLineColor); settings.gridBgColor = EditorGUILayout.ColorField(" ", settings.gridBgColor); if (GUI.changed) { diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index a575c8d..fc04439 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -61,7 +61,7 @@ namespace XNodeEditor { public XNode.NodeGraph graph; public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } } private Vector2 _panOffset; - public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, 1f, NodeEditorPreferences.GetSettings().zoomOutLimit); Repaint(); } } + public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, NodeEditorPreferences.GetSettings().minZoom, NodeEditorPreferences.GetSettings().maxZoom); Repaint(); } } private float _zoom = 1; void OnFocus() { From d68aea2a8a59c023eff467f36676e72712cc6222 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 10 May 2019 09:55:10 +0200 Subject: [PATCH 42/69] Fixed #142 - New feature: Automatically remove "Node" postfix in names --- Scripts/Editor/NodeGraphEditor.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 8a9d2f0..6fd00ea 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -72,7 +72,12 @@ namespace XNodeEditor { public virtual void CreateNode(Type type, Vector2 position) { XNode.Node node = target.AddNode(type); node.position = position; - if (string.IsNullOrEmpty(node.name)) node.name = UnityEditor.ObjectNames.NicifyVariableName(type.Name); + if (string.IsNullOrEmpty(node.name)) { + // Automatically remove redundant 'Node' postfix + string typeName = type.Name; + if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node")); + node.name = UnityEditor.ObjectNames.NicifyVariableName(typeName); + } AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); NodeEditorWindow.RepaintAll(); From c868c037ea718853446e5cc55ff24d75b7dc69e7 Mon Sep 17 00:00:00 2001 From: Simon Rodriguez Date: Mon, 13 May 2019 10:23:54 +0200 Subject: [PATCH 43/69] Fixed #99 - Color for types in preferences will reseted after playing * SavePrefs was saving to the wrong key. * A dictionary cant be changed while being enumerated. Now clones the keys and gets/sets values. --- Scripts/Editor/NodeEditorPreferences.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index 4fc6b7b..2f84a4a 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -158,11 +158,13 @@ namespace XNodeEditor { //Label EditorGUILayout.LabelField("Types", EditorStyles.boldLabel); + //Clone keys so we can enumerate the dictionary and make changes. + var typeColorKeys = new List(typeColors.Keys); + //Display type colors. Save them if they are edited by the user - foreach (var typeColor in typeColors) { - Type type = typeColor.Key; + foreach (var type in typeColorKeys) { string typeColorKey = NodeEditorUtilities.PrettyName(type); - Color col = typeColor.Value; + Color col = typeColors[type]; EditorGUI.BeginChangeCheck(); EditorGUILayout.BeginHorizontal(); col = EditorGUILayout.ColorField(typeColorKey, col); @@ -171,7 +173,7 @@ namespace XNodeEditor { typeColors[type] = col; if (settings.typeColors.ContainsKey(typeColorKey)) settings.typeColors[typeColorKey] = col; else settings.typeColors.Add(typeColorKey, col); - SavePrefs(typeColorKey, settings); + SavePrefs(key, settings); NodeEditorWindow.RepaintAll(); } } From 8a03811ee35a4ceb034c9317c0b5543e16db9172 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 13 May 2019 14:26:29 +0200 Subject: [PATCH 44/69] More DynamicPortList debug messages --- Scripts/Editor/NodeEditorGUILayout.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 344b3fb..581339a 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -323,7 +323,8 @@ namespace XNodeEditor { XNode.NodePort port = node.GetPort(fieldName + " " + index); if (hasArrayData) { if (arrayData.arraySize <= index) { - EditorGUI.LabelField(rect, "Invalid element " + index); + string portInfo = port != null ? port.fieldName : ""; + EditorGUI.LabelField(rect, "Array[" + index + "] data out of range"); return; } SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); @@ -427,7 +428,12 @@ namespace XNodeEditor { int index = rl.index; - if (dynamicPorts.Count > index) { + if (dynamicPorts[index] == null) { + Debug.LogWarning("No port found at index " + index + " - Skipped"); + } else if (dynamicPorts.Count <= index) { + Debug.LogWarning("DynamicPorts[" + index + "] out of range. Length was " + dynamicPorts.Count + " - Skipped"); + } else { + // Clear the removed ports connections dynamicPorts[index].ClearConnections(); // Move following connections one step up to replace the missing connection @@ -442,11 +448,14 @@ namespace XNodeEditor { node.RemoveDynamicPort(dynamicPorts[dynamicPorts.Count() - 1].fieldName); serializedObject.Update(); EditorUtility.SetDirty(node); - } else { - Debug.LogWarning("DynamicPorts[" + index + "] out of range. Length was " + dynamicPorts.Count + ". Skipping."); } if (hasArrayData) { + if (arrayData.arraySize <= index) { + Debug.LogWarning("Attempted to remove array index " + index + " where only " + arrayData.arraySize + " exist - Skipped"); + Debug.Log(rl.list[0]); + return; + } arrayData.DeleteArrayElementAtIndex(index); // Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues if (dynamicPorts.Count <= arrayData.arraySize) { From 833e26ccc3247526d2c498a37c5659d53ebac386 Mon Sep 17 00:00:00 2001 From: Simon Rodriguez Date: Mon, 13 May 2019 20:53:34 +0200 Subject: [PATCH 45/69] Make RemoveNode + switch order * made RemoveNode function virtual used for example when a graph wants to block a delete * switched order of remove function and destroy function. target.RemoveNode was always getting a null value --- Scripts/Editor/NodeGraphEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 6fd00ea..c509d28 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -93,9 +93,9 @@ namespace XNodeEditor { } /// Safely remove a node and all its connections. - public void RemoveNode(XNode.Node node) { - UnityEngine.Object.DestroyImmediate(node, true); + public virtual void RemoveNode(XNode.Node node) { target.RemoveNode(node); + UnityEngine.Object.DestroyImmediate(node, true); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } From 4721edc7ec04efa7d0b95a98aec2e629e9b00a0c Mon Sep 17 00:00:00 2001 From: Adsitoz Date: Sat, 8 Jun 2019 04:04:19 +1000 Subject: [PATCH 46/69] Fixed rename not saving the filename change. (#149) - AssetDataBase.ImportAsset was not correctly renaming the asset. Changed it over to use the standard rename method. --- Scripts/Editor/RenamePopup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/Editor/RenamePopup.cs b/Scripts/Editor/RenamePopup.cs index a49a948..9a1ed7b 100644 --- a/Scripts/Editor/RenamePopup.cs +++ b/Scripts/Editor/RenamePopup.cs @@ -57,10 +57,10 @@ namespace XNodeEditor { else { if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) { target.name = input; - AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(target), input); Close(); } } } } -} \ No newline at end of file +} From 0c1ae4881dcd49f4c1783bac21221e6a605bac80 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 8 Jun 2019 01:41:58 +0200 Subject: [PATCH 47/69] Fixed #147 - Adding dynamic port using "+" causes exception --- Scripts/Editor/NodeEditorGUILayout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 581339a..cd4d320 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -329,7 +329,7 @@ namespace XNodeEditor { } SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); EditorGUI.PropertyField(rect, itemData, true); - } else EditorGUI.LabelField(rect, port.fieldName); + } else EditorGUI.LabelField(rect, port != null ? port.fieldName : ""); if (port != null) { Vector2 pos = rect.position + (port.IsOutput?new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); NodeEditorGUILayout.PortField(pos, port); From 53f85a5d227763aa5c8374dd177ff7c2bd86adb1 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 8 Jun 2019 14:25:07 +0200 Subject: [PATCH 48/69] Improved zoom with the help of Jeroenimoo0's PR --- Scripts/Editor/NodeEditorGUI.cs | 56 ++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index c9c460f..2bf3945 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -10,10 +10,13 @@ namespace XNodeEditor { public NodeGraphEditor graphEditor; private List selectionCache; private List culledNodes; + /// 19 if docked, 22 if not private int topPadding { get { return isDocked() ? 19 : 22; } } /// Executed after all other window GUI. Useful if Zoom is ruining your day. Automatically resets after being run. public event Action onLateGUI; + private Matrix4x4 prevGuiMatrix; + private void OnGUI() { Event e = Event.current; Matrix4x4 m = GUI.matrix; @@ -38,24 +41,40 @@ namespace XNodeEditor { GUI.matrix = m; } - public static void BeginZoomed(Rect rect, float zoom, float topPadding) { - GUI.EndClip(); + public void BeginZoomed() { + GUI.EndGroup(); - GUIUtility.ScaleAroundPivot(Vector2.one / zoom, rect.size * 0.5f); - Vector4 padding = new Vector4(0, topPadding, 0, 0); - padding *= zoom; - GUI.BeginClip(new Rect(-((rect.width * zoom) - rect.width) * 0.5f, -(((rect.height * zoom) - rect.height) * 0.5f) + (topPadding * zoom), - rect.width * zoom, - rect.height * zoom)); + Rect position = new Rect(this.position); + position.x = 0; + position.y = topPadding; + + Vector2 topLeft = new Vector2(position.xMin, position.yMin - topPadding); + Rect clippedArea = ScaleSizeBy(position, zoom, topLeft); + GUI.BeginGroup(clippedArea); + + prevGuiMatrix = GUI.matrix; + Matrix4x4 translation = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, Vector3.one); + Matrix4x4 scale = Matrix4x4.Scale(new Vector3(1.0f / zoom, 1.0f / zoom, 1.0f)); + GUI.matrix = translation * scale * translation.inverse * GUI.matrix; } - public static void EndZoomed(Rect rect, float zoom, float topPadding) { - GUIUtility.ScaleAroundPivot(Vector2.one * zoom, rect.size * 0.5f); - Vector3 offset = new Vector3( - (((rect.width * zoom) - rect.width) * 0.5f), - (((rect.height * zoom) - rect.height) * 0.5f) + (-topPadding * zoom) + topPadding, - 0); - GUI.matrix = Matrix4x4.TRS(offset, Quaternion.identity, Vector3.one); + public void EndZoomed() { + GUI.matrix = prevGuiMatrix; + GUI.EndGroup(); + GUI.BeginGroup(new Rect(0.0f, topPadding - (topPadding * zoom), Screen.width, Screen.height)); + } + + public static Rect ScaleSizeBy(Rect rect, float scale, Vector2 pivotPoint) { + Rect result = rect; + result.x -= pivotPoint.x; + result.y -= pivotPoint.y; + result.xMin *= scale; + result.xMax *= scale; + result.yMin *= scale; + result.yMax *= scale; + result.x += pivotPoint.x; + result.y += pivotPoint.y; + return result; } public void DrawGrid(Rect rect, float zoom, Vector2 panOffset) { @@ -136,8 +155,7 @@ namespace XNodeEditor { p = new Vector2(-p.y, p.x) * Mathf.Sign(side) * tangentLength; inputTangent = p; - } - else { + } else { inputTangent = Vector2.left * Vector2.Distance(windowPoints[i], windowPoints[i + 1]) * 0.01f * zoom; } @@ -258,7 +276,7 @@ namespace XNodeEditor { if (onValidate != null) EditorGUI.BeginChangeCheck(); } - BeginZoomed(position, zoom, topPadding); + BeginZoomed(); Vector2 mousePos = Event.current.mousePosition; @@ -389,7 +407,7 @@ namespace XNodeEditor { } if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray(); - EndZoomed(position, zoom, topPadding); + EndZoomed(); //If a change in is detected in the selected node, call OnValidate method. //This is done through reflection because OnValidate is only relevant in editor, From 8b0eb6bb6067db884428e7e396cab926362b6691 Mon Sep 17 00:00:00 2001 From: Michael Wigley Date: Sat, 8 Jun 2019 14:01:45 -0500 Subject: [PATCH 49/69] #144 Added option to disable tooltips --- Scripts/Editor/NodeEditorGUI.cs | 2 +- Scripts/Editor/NodeEditorPreferences.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 2bf3945..aa88505 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -429,7 +429,7 @@ namespace XNodeEditor { } private void DrawTooltip() { - if (hoveredPort != null) { + if (hoveredPort != null && NodeEditorPreferences.GetSettings().portTooltips) { Type type = hoveredPort.ValueType; GUIContent content = new GUIContent(); content.text = type.PrettyName(); diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index 2f84a4a..35cc22d 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -33,6 +33,7 @@ namespace XNodeEditor { public bool gridSnap = true; public bool autoSave = true; public bool zoomToMouse = true; + public bool portTooltips = true; [SerializeField] private string typeColorsData = ""; [NonSerialized] public Dictionary typeColors = new Dictionary(); public NoodleType noodleType = NoodleType.Curve; @@ -147,6 +148,7 @@ namespace XNodeEditor { EditorGUILayout.LabelField("Node", EditorStyles.boldLabel); settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor); settings.noodleType = (NoodleType) EditorGUILayout.EnumPopup("Noodle type", (Enum) settings.noodleType); + settings.portTooltips = EditorGUILayout.Toggle("Port Tooltips", settings.portTooltips); if (GUI.changed) { SavePrefs(key, settings); NodeEditorWindow.RepaintAll(); From fefea30594b0b5f38b4777452fc43a9182bbcb8a Mon Sep 17 00:00:00 2001 From: Simon Rodriguez Date: Thu, 13 Jun 2019 18:36:40 +0200 Subject: [PATCH 50/69] Added support for copy paste in menu and ctrl+c / ctrl+v / cmd+c / cmd+v. --- Scripts/Editor/NodeEditor.cs | 1 + Scripts/Editor/NodeEditorAction.cs | 55 ++++++++++++++++++++++++++++++ Scripts/Editor/NodeGraphEditor.cs | 2 ++ 3 files changed, 58 insertions(+) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 7652658..c3f2f9e 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -73,6 +73,7 @@ namespace XNodeEditor { } // Add actions to any number of selected nodes + menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes); menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes); menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes); diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 792631a..f74744b 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -11,6 +11,8 @@ namespace XNodeEditor { public static bool isPanning { get; private set; } public static Vector2[] dragOffset; + public static XNode.Node[] copyBuffer = null; + private bool IsDraggingPort { get { return draggedOutput != null; } } private bool IsHoveringPort { get { return hoveredPort != null; } } private bool IsHoveringNode { get { return hoveredNode != null; } } @@ -27,6 +29,7 @@ namespace XNodeEditor { private RerouteReference[] preBoxSelectionReroute; private Rect selectionBox; private bool isDoubleClick = false; + private Vector2 lastMousePosition; private struct RerouteReference { public XNode.NodePort port; @@ -50,6 +53,8 @@ namespace XNodeEditor { Event e = Event.current; switch (e.type) { case EventType.MouseMove: + //Keyboard commands will not get correct mouse position from Event + lastMousePosition = e.mousePosition; break; case EventType.ScrollWheel: float oldZoom = zoom; @@ -303,6 +308,12 @@ namespace XNodeEditor { } else if (e.commandName == "Duplicate") { if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes(); e.Use(); + } else if (e.commandName == "Copy") { + if (e.type == EventType.ExecuteCommand) CopySelectedNodes(); + e.Use(); + } else if (e.commandName == "Paste") { + if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition)); + e.Use(); } Repaint(); break; @@ -424,6 +435,50 @@ namespace XNodeEditor { Selection.objects = newNodes; } + public void CopySelectedNodes() { + copyBuffer = Selection.objects.Where((o) => o != null && o is XNode.Node).Cast().ToArray(); + } + + public void PasteNodes(Vector2 pos) { + if (copyBuffer == null || copyBuffer.Length == 0) return; + + //Center paste around first node in list + Vector2 offset = pos - copyBuffer[0].position; + + UnityEngine.Object[] newNodes = new UnityEngine.Object[copyBuffer.Length]; + Dictionary substitutes = new Dictionary(); + for (int i = 0; i < copyBuffer.Length; i++) { + XNode.Node srcNode = copyBuffer[i] as XNode.Node; + if (srcNode == null) continue; + XNode.Node newNode = graphEditor.CopyNode(srcNode); + substitutes.Add(srcNode, newNode); + newNode.position = srcNode.position + offset; + newNodes[i] = newNode; + } + + // Walk through the selected nodes again, recreate connections, using the new nodes + for (int i = 0; i < copyBuffer.Length; i++) { + XNode.Node srcNode = copyBuffer[i] as XNode.Node; + if (srcNode == null) continue; + foreach (XNode.NodePort port in srcNode.Ports) { + for (int c = 0; c < port.ConnectionCount; c++) { + XNode.NodePort inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c); + XNode.NodePort outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c); + + XNode.Node newNodeIn, newNodeOut; + if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) { + newNodeIn.UpdateStaticPorts(); + newNodeOut.UpdateStaticPorts(); + inputPort = newNodeIn.GetInputPort(inputPort.fieldName); + outputPort = newNodeOut.GetOutputPort(outputPort.fieldName); + } + if (!inputPort.IsConnectedTo(outputPort)) inputPort.Connect(outputPort); + } + } + } + Selection.objects = newNodes; + } + /// Draw a connection as we are dragging it public void DrawDraggedConnection() { if (IsDraggingPort) { diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index c509d28..8ece949 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -56,6 +56,8 @@ namespace XNodeEditor { }); } menu.AddSeparator(""); + if (NodeEditorWindow.copyBuffer != null && NodeEditorWindow.copyBuffer.Length > 0) menu.AddItem(new GUIContent("Paste"), false, () => NodeEditorWindow.current.PasteNodes(pos)); + else menu.AddDisabledItem(new GUIContent("Paste")); menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorWindow.OpenPreferences()); NodeEditorWindow.AddCustomContextMenuItems(menu, target); } From 29f0194eef4a1e801914e45ac6e8ff9932d29413 Mon Sep 17 00:00:00 2001 From: thiezar Date: Thu, 13 Jun 2019 20:17:58 +0200 Subject: [PATCH 51/69] Allow current assembly nodes to be cached (#153) --- Scripts/NodeDataCache.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index 434ffc5..d7fa38c 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -56,10 +56,14 @@ namespace XNode { } else { // Else, check all relevant DDLs (slower) // ignore all unity related assemblies + // never ignore current executing assembly + Assembly executingAssembly = Assembly.GetExecutingAssembly(); foreach (Assembly assembly in assemblies) { - if (assembly.FullName.StartsWith("Unity")) continue; - // unity created assemblies always have version 0.0.0 - if (!assembly.FullName.Contains("Version=0.0.0")) continue; + if(assembly != executingAssembly) { + if (assembly.FullName.StartsWith("Unity")) continue; + // unity created assemblies always have version 0.0.0 + if (!assembly.FullName.Contains("Version=0.0.0")) continue; + } nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); } } @@ -126,4 +130,4 @@ namespace XNode { } } } -} \ No newline at end of file +} From 53bba68ae0562bed28a7bcf829612a64896b62d8 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Thu, 13 Jun 2019 22:13:11 +0200 Subject: [PATCH 52/69] Update README.md Removed third party project links. They can now be found on the wiki home page instead --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 2b59422..7f89dce 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,3 @@ public class MathNode : Node { Join the [Discord](https://discord.gg/qgPrHv4 "Join Discord server") server to leave feedback or get support. Feel free to also leave suggestions/requests in the [issues](https://github.com/Siccity/xNode/issues "Go to Issues") page. - -Projects using xNode: -* [Graphmesh](https://github.com/Siccity/Graphmesh "Go to github page") -* [Dialogue](https://github.com/Siccity/Dialogue "Go to github page") -* [qAI](https://github.com/jlreymendez/qAI "Go to github page") From 369a3fd6eac299d5d6fffe21005f1a730ea34220 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 17 Jun 2019 09:26:35 +0200 Subject: [PATCH 53/69] Update CONTRIBUTING.md --- CONTRIBUTING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8638230..33da9d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,10 +4,11 @@ If you haven't already, join our [Discord channel](https://discord.gg/qgPrHv4)! ## Pull Requests -Try to keep your pull requests relevant, neat, and manageable. If you are adding multiple features, try splitting them into separate commits. +Try to keep your pull requests relevant, neat, and manageable. If you are adding multiple features, split them into separate PRs. * Avoid including irellevant whitespace or formatting changes. * Comment your code. * Spell check your code / comments +* Use consistent formatting ## New features xNode aims to be simple and extendible, not trying to fix all of Unity's shortcomings. @@ -15,7 +16,7 @@ xNode aims to be simple and extendible, not trying to fix all of Unity's shortco If your feature aims to cover something not related to editing nodes, it generally won't be accepted. If in doubt, ask on the Discord channel. ## Coding conventions -Skim through the code and you'll get the hang of it quickly. +Using consistent formatting is key to having a clean git history. Skim through the code and you'll get the hang of it quickly. * Methods, Types and properties PascalCase * Variables camelCase * Public methods XML commented. Params described if not obvious From c350c135cac1c976044a3948d17618a7b396b2e8 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 17 Jun 2019 20:11:25 +0200 Subject: [PATCH 54/69] Improved handling of null sub assets Thanks to Lenny#3404 from the discord channel --- Scripts/Editor/NodeEditorAssetModProcessor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs b/Scripts/Editor/NodeEditorAssetModProcessor.cs index bd76116..edaebaa 100644 --- a/Scripts/Editor/NodeEditorAssetModProcessor.cs +++ b/Scripts/Editor/NodeEditorAssetModProcessor.cs @@ -6,7 +6,8 @@ namespace XNodeEditor { class NodeEditorAssetModProcessor : UnityEditor.AssetModificationProcessor { /// Automatically delete Node sub-assets before deleting their script. - /// This is important to do, because you can't delete null sub assets. + /// This is important to do, because you can't delete null sub assets. + /// For another workaround, see: https://gitlab.com/RotaryHeart-UnityShare/subassetmissingscriptdelete private static AssetDeleteResult OnWillDeleteAsset (string path, RemoveAssetOptions options) { // Get the object that is requested for deletion UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath (path); @@ -51,6 +52,8 @@ namespace XNodeEditor { Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath); // Ensure that all sub node assets are present in the graph node list for (int u = 0; u < objs.Length; u++) { + // Ignore null sub assets + if (objs[u] == null) continue; if (!graph.nodes.Contains (objs[u] as XNode.Node)) graph.nodes.Add(objs[u] as XNode.Node); } } From 3ba0b13c77c50a91b278b44f9cdd21cf7b389f8b Mon Sep 17 00:00:00 2001 From: Adsitoz Date: Tue, 18 Jun 2019 19:34:23 +1000 Subject: [PATCH 55/69] Zoom System Fixes (#155) - Due to the OnGUI method being called last to draw over everything else in the window, the GUI.Group being created in the new zoom system was also moving around the GUI elements created in the NodeGraphEditor OnGUI method. --- Scripts/Editor/NodeEditorGUI.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index aa88505..7447b8a 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -12,6 +12,8 @@ namespace XNodeEditor { private List culledNodes; /// 19 if docked, 22 if not private int topPadding { get { return isDocked() ? 19 : 22; } } + /// 0 if docked, 3 if not + private int leftPadding { get { return isDocked() ? 2 : 0; } } /// Executed after all other window GUI. Useful if Zoom is ruining your day. Automatically resets after being run. public event Action onLateGUI; @@ -30,7 +32,7 @@ namespace XNodeEditor { DrawNodes(); DrawSelectionBox(); DrawTooltip(); - graphEditor.OnGUI(); + DrawGraphOnGUI(); // Run and reset onLateGUI if (onLateGUI != null) { @@ -64,6 +66,16 @@ namespace XNodeEditor { GUI.BeginGroup(new Rect(0.0f, topPadding - (topPadding * zoom), Screen.width, Screen.height)); } + /// Ends the GUI Group temporarily to draw any additional elements in the NodeGraphEditor. + private void DrawGraphOnGUI() { + GUI.EndGroup(); + Rect rect = new Rect(new Vector2(leftPadding, topPadding), new Vector2(Screen.width, Screen.height)); + GUI.BeginGroup(rect); + graphEditor.OnGUI(); + GUI.EndGroup(); + GUI.BeginGroup(new Rect(0.0f, topPadding - (topPadding * zoom), Screen.width, Screen.height)); + } + public static Rect ScaleSizeBy(Rect rect, float scale, Vector2 pivotPoint) { Rect result = rect; result.x -= pivotPoint.x; From 6c38439e84f6cc23a208376191870850e770230c Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Tue, 18 Jun 2019 22:00:31 +0200 Subject: [PATCH 56/69] Built DuplicateSelectedNodes and PasteNodes together --- Scripts/Editor/NodeEditorAction.cs | 63 +++++++++--------------------- 1 file changed, 19 insertions(+), 44 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index f74744b..ada5992 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -397,42 +397,11 @@ namespace XNodeEditor { /// Duplicate selected nodes and select the duplicates public void DuplicateSelectedNodes() { - UnityEngine.Object[] newNodes = new UnityEngine.Object[Selection.objects.Length]; - Dictionary substitutes = new Dictionary(); - for (int i = 0; i < Selection.objects.Length; i++) { - if (Selection.objects[i] is XNode.Node) { - XNode.Node srcNode = Selection.objects[i] as XNode.Node; - if (srcNode.graph != graph) continue; // ignore nodes selected in another graph - XNode.Node newNode = graphEditor.CopyNode(srcNode); - substitutes.Add(srcNode, newNode); - newNode.position = srcNode.position + new Vector2(30, 30); - newNodes[i] = newNode; - } - } - - // Walk through the selected nodes again, recreate connections, using the new nodes - for (int i = 0; i < Selection.objects.Length; i++) { - if (Selection.objects[i] is XNode.Node) { - XNode.Node srcNode = Selection.objects[i] as XNode.Node; - if (srcNode.graph != graph) continue; // ignore nodes selected in another graph - foreach (XNode.NodePort port in srcNode.Ports) { - for (int c = 0; c < port.ConnectionCount; c++) { - XNode.NodePort inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c); - XNode.NodePort outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c); - - XNode.Node newNodeIn, newNodeOut; - if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) { - newNodeIn.UpdateStaticPorts(); - newNodeOut.UpdateStaticPorts(); - inputPort = newNodeIn.GetInputPort(inputPort.fieldName); - outputPort = newNodeOut.GetOutputPort(outputPort.fieldName); - } - if (!inputPort.IsConnectedTo(outputPort)) inputPort.Connect(outputPort); - } - } - } - } - Selection.objects = newNodes; + // Get selected nodes which are part of this graph + XNode.Node[] selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); + // Get top left node position + Vector2 topLeftNode = selectedNodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); + InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30)); } public void CopySelectedNodes() { @@ -440,15 +409,20 @@ namespace XNodeEditor { } public void PasteNodes(Vector2 pos) { - if (copyBuffer == null || copyBuffer.Length == 0) return; + InsertDuplicateNodes(copyBuffer, pos); + } - //Center paste around first node in list - Vector2 offset = pos - copyBuffer[0].position; + private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft) { + if (nodes == null || nodes.Length == 0) return; - UnityEngine.Object[] newNodes = new UnityEngine.Object[copyBuffer.Length]; + // Get top-left node + Vector2 topLeftNode = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); + Vector2 offset = topLeft - topLeftNode; + + UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.Length]; Dictionary substitutes = new Dictionary(); - for (int i = 0; i < copyBuffer.Length; i++) { - XNode.Node srcNode = copyBuffer[i] as XNode.Node; + for (int i = 0; i < nodes.Length; i++) { + XNode.Node srcNode = nodes[i]; if (srcNode == null) continue; XNode.Node newNode = graphEditor.CopyNode(srcNode); substitutes.Add(srcNode, newNode); @@ -457,8 +431,8 @@ namespace XNodeEditor { } // Walk through the selected nodes again, recreate connections, using the new nodes - for (int i = 0; i < copyBuffer.Length; i++) { - XNode.Node srcNode = copyBuffer[i] as XNode.Node; + for (int i = 0; i < nodes.Length; i++) { + XNode.Node srcNode = nodes[i]; if (srcNode == null) continue; foreach (XNode.NodePort port in srcNode.Ports) { for (int c = 0; c < port.ConnectionCount; c++) { @@ -476,6 +450,7 @@ namespace XNodeEditor { } } } + // Select the new nodes Selection.objects = newNodes; } From 56e84b7c92e932a8f3895d6cd8d9ce27e41ad390 Mon Sep 17 00:00:00 2001 From: Simon Rodriguez Date: Tue, 18 Jun 2019 22:17:56 +0200 Subject: [PATCH 57/69] Changed so CopySelectedNodes uses the same Linq-select as DuplicateSelectedNodes does. --- Scripts/Editor/NodeEditorAction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index ada5992..4ae446e 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -405,7 +405,7 @@ namespace XNodeEditor { } public void CopySelectedNodes() { - copyBuffer = Selection.objects.Where((o) => o != null && o is XNode.Node).Cast().ToArray(); + copyBuffer = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); } public void PasteNodes(Vector2 pos) { From 5d45300935ca63653e5054667d7d1d6348e5af6e Mon Sep 17 00:00:00 2001 From: Simon Rodriguez Date: Sun, 23 Jun 2019 11:54:14 +0200 Subject: [PATCH 58/69] Unified all functions that set the default node name (#161) --- Scripts/Editor/NodeEditor.cs | 4 ++-- Scripts/Editor/NodeEditorUtilities.cs | 9 +++++++++ Scripts/Editor/NodeGraphEditor.cs | 7 +------ Scripts/Editor/RenamePopup.cs | 6 +++--- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index c3f2f9e..19b71f3 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -86,9 +86,9 @@ namespace XNodeEditor { /// Rename the node asset. This will trigger a reimport of the node. public void Rename(string newName) { - if (newName == null || newName.Trim() == "") newName = UnityEditor.ObjectNames.NicifyVariableName(target.GetType().Name); + if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType()); target.name = newName; - AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(target), target.name); } [AttributeUsage(AttributeTargets.Class)] diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index 18e295f..77e71bb 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -133,6 +133,15 @@ namespace XNodeEditor { } else return type.ToString(); } + /// Returns the default name for the node type. + public static string NodeDefaultName(Type type) { + string typeName = type.Name; + // Automatically remove redundant 'Node' postfix + if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node")); + typeName = UnityEditor.ObjectNames.NicifyVariableName(typeName); + return typeName; + } + /// Creates a new C# Class. [MenuItem("Assets/Create/xNode/Node C# Script", false, 89)] private static void CreateNode() { diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 8ece949..f79779f 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -74,12 +74,7 @@ namespace XNodeEditor { public virtual void CreateNode(Type type, Vector2 position) { XNode.Node node = target.AddNode(type); node.position = position; - if (string.IsNullOrEmpty(node.name)) { - // Automatically remove redundant 'Node' postfix - string typeName = type.Name; - if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node")); - node.name = UnityEditor.ObjectNames.NicifyVariableName(typeName); - } + if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type); AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); NodeEditorWindow.RepaintAll(); diff --git a/Scripts/Editor/RenamePopup.cs b/Scripts/Editor/RenamePopup.cs index 9a1ed7b..1b4f718 100644 --- a/Scripts/Editor/RenamePopup.cs +++ b/Scripts/Editor/RenamePopup.cs @@ -48,8 +48,8 @@ namespace XNodeEditor { // If input is empty, revert name to default instead if (input == null || input.Trim() == "") { if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) { - target.name = UnityEditor.ObjectNames.NicifyVariableName(target.GetType().Name); - AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + target.name = NodeEditorUtilities.NodeDefaultName(target.GetType()); + AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(target), target.name); Close(); } } @@ -57,7 +57,7 @@ namespace XNodeEditor { else { if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) { target.name = input; - AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(target), input); + AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(target), target.name); Close(); } } From 55dddf814282a95b33f5eb40ce31595ad88cb1ba Mon Sep 17 00:00:00 2001 From: Adsitoz Date: Wed, 26 Jun 2019 18:08:22 +1000 Subject: [PATCH 59/69] Rename Revert (#164) - Not working the same way across all versions of Unity. --- Scripts/Editor/NodeEditor.cs | 2 +- Scripts/Editor/RenamePopup.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 19b71f3..76f4fe6 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -88,7 +88,7 @@ namespace XNodeEditor { public void Rename(string newName) { if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType()); target.name = newName; - AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(target), target.name); + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); } [AttributeUsage(AttributeTargets.Class)] diff --git a/Scripts/Editor/RenamePopup.cs b/Scripts/Editor/RenamePopup.cs index 1b4f718..5a80d59 100644 --- a/Scripts/Editor/RenamePopup.cs +++ b/Scripts/Editor/RenamePopup.cs @@ -49,16 +49,16 @@ namespace XNodeEditor { if (input == null || input.Trim() == "") { if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) { target.name = NodeEditorUtilities.NodeDefaultName(target.GetType()); - AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(target), target.name); - Close(); + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + Close(); } } // Rename asset to input text else { if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) { target.name = input; - AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(target), target.name); - Close(); + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + Close(); } } } From 4e1d9b67217bfee4dbfb518620e07698b5b38ad9 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Wed, 26 Jun 2019 10:13:12 +0200 Subject: [PATCH 60/69] Reformat RenamePopup.cs Was inconsistently formatted as tabs instead of spaces --- Scripts/Editor/RenamePopup.cs | 106 +++++++++++++++++----------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/Scripts/Editor/RenamePopup.cs b/Scripts/Editor/RenamePopup.cs index 5a80d59..85d229f 100644 --- a/Scripts/Editor/RenamePopup.cs +++ b/Scripts/Editor/RenamePopup.cs @@ -2,65 +2,65 @@ using UnityEngine; namespace XNodeEditor { - /// Utility for renaming assets - public class RenamePopup : EditorWindow { - public static RenamePopup current { get; private set; } - public Object target; - public string input; + /// Utility for renaming assets + public class RenamePopup : EditorWindow { + public static RenamePopup current { get; private set; } + public Object target; + public string input; - private bool firstFrame = true; + private bool firstFrame = true; - /// Show a rename popup for an asset at mouse position. Will trigger reimport of the asset on apply. - public static RenamePopup Show(Object target, float width = 200) { - RenamePopup window = EditorWindow.GetWindow(true, "Rename " + target.name, true); - if (current != null) current.Close(); - current = window; - window.target = target; - window.input = target.name; - window.minSize = new Vector2(100, 44); - window.position = new Rect(0, 0, width, 44); - GUI.FocusControl("ClearAllFocus"); - window.UpdatePositionToMouse(); - return window; - } + /// Show a rename popup for an asset at mouse position. Will trigger reimport of the asset on apply. + public static RenamePopup Show(Object target, float width = 200) { + RenamePopup window = EditorWindow.GetWindow(true, "Rename " + target.name, true); + if (current != null) current.Close(); + current = window; + window.target = target; + window.input = target.name; + window.minSize = new Vector2(100, 44); + window.position = new Rect(0, 0, width, 44); + GUI.FocusControl("ClearAllFocus"); + window.UpdatePositionToMouse(); + return window; + } - private void UpdatePositionToMouse() { - if (Event.current == null) return; - Vector3 mousePoint = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); - Rect pos = position; - pos.x = mousePoint.x - position.width * 0.5f; - pos.y = mousePoint.y - 10; - position = pos; - } + private void UpdatePositionToMouse() { + if (Event.current == null) return; + Vector3 mousePoint = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); + Rect pos = position; + pos.x = mousePoint.x - position.width * 0.5f; + pos.y = mousePoint.y - 10; + position = pos; + } - private void OnLostFocus() { - // Make the popup close on lose focus - Close(); - } + private void OnLostFocus() { + // Make the popup close on lose focus + Close(); + } - private void OnGUI() { - if (firstFrame) { - UpdatePositionToMouse(); - firstFrame = false; - } - input = EditorGUILayout.TextField(input); - Event e = Event.current; - // If input is empty, revert name to default instead - if (input == null || input.Trim() == "") { - if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) { - target.name = NodeEditorUtilities.NodeDefaultName(target.GetType()); + private void OnGUI() { + if (firstFrame) { + UpdatePositionToMouse(); + firstFrame = false; + } + input = EditorGUILayout.TextField(input); + Event e = Event.current; + // If input is empty, revert name to default instead + if (input == null || input.Trim() == "") { + if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) { + target.name = NodeEditorUtilities.NodeDefaultName(target.GetType()); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); Close(); - } - } - // Rename asset to input text - else { - if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) { - target.name = input; + } + } + // Rename asset to input text + else { + if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) { + target.name = input; AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); Close(); - } - } - } - } -} + } + } + } + } +} \ No newline at end of file From f8ba6339c855a3430335aa106aa32e7e9ad5bf2f Mon Sep 17 00:00:00 2001 From: Hao Wu Date: Wed, 26 Jun 2019 16:25:01 +0800 Subject: [PATCH 61/69] Added validation support for Context Menu items (#162) * Added validation support for Context Menu items --- Scripts/Editor/NodeEditorReflection.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index 18b72fa..6b8b121 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -77,18 +77,28 @@ namespace XNodeEditor { foreach (Assembly assembly in assemblies) { try { types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); - } catch(ReflectionTypeLoadException) {} + } catch (ReflectionTypeLoadException) { } } return types.ToArray(); } public static void AddCustomContextMenuItems(GenericMenu contextMenu, object obj) { - KeyValuePair[] items = GetContextMenuMethods(obj); + KeyValuePair[] items = GetContextMenuMethods(obj); if (items.Length != 0) { contextMenu.AddSeparator(""); + List invalidatedEntries = new List(); + foreach (KeyValuePair checkValidate in items) { + if (checkValidate.Key.validate && !(bool) checkValidate.Value.Invoke(obj, null)) { + invalidatedEntries.Add(checkValidate.Key.menuItem); + } + } for (int i = 0; i < items.Length; i++) { - KeyValuePair kvp = items[i]; - contextMenu.AddItem(new GUIContent(kvp.Key.menuItem), false, () => kvp.Value.Invoke(obj, null)); + KeyValuePair kvp = items[i]; + if (invalidatedEntries.Contains(kvp.Key.menuItem)) { + contextMenu.AddDisabledItem(new GUIContent(kvp.Key.menuItem)); + } else { + contextMenu.AddItem(new GUIContent(kvp.Key.menuItem), false, () => kvp.Value.Invoke(obj, null)); + } } } } From f0fd6b963465b7391a47909a34cab75a60e0fbea Mon Sep 17 00:00:00 2001 From: Greg Poole Date: Thu, 27 Jun 2019 07:42:36 +1000 Subject: [PATCH 62/69] Add Unity Package Manager support (#165) --- .gitignore | 3 --- CONTRIBUTING.md.meta | 7 +++++++ LICENSE.md.meta | 7 +++++++ README.md | 10 ++++++++++ README.md.meta | 7 +++++++ Scripts/Editor/XNodeEditor.asmdef | 17 +++++++++++++++++ Scripts/Editor/XNodeEditor.asmdef.meta | 7 +++++++ Scripts/XNode.asmdef | 13 +++++++++++++ Scripts/XNode.asmdef.meta | 7 +++++++ package.json | 7 +++++++ package.json.meta | 7 +++++++ 11 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 CONTRIBUTING.md.meta create mode 100644 LICENSE.md.meta create mode 100644 README.md.meta create mode 100644 Scripts/Editor/XNodeEditor.asmdef create mode 100644 Scripts/Editor/XNodeEditor.asmdef.meta create mode 100644 Scripts/XNode.asmdef create mode 100644 Scripts/XNode.asmdef.meta create mode 100644 package.json create mode 100644 package.json.meta diff --git a/.gitignore b/.gitignore index 1460751..64ab4c0 100644 --- a/.gitignore +++ b/.gitignore @@ -21,9 +21,6 @@ sysinfo.txt /Examples/ -README.md.meta -LICENSE.md.meta -CONTRIBUTING.md.meta .git.meta .gitignore.meta diff --git a/CONTRIBUTING.md.meta b/CONTRIBUTING.md.meta new file mode 100644 index 0000000..5d7c128 --- /dev/null +++ b/CONTRIBUTING.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: bc1db8b29c76d44648c9c86c2dfade6d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE.md.meta b/LICENSE.md.meta new file mode 100644 index 0000000..5f0a7c7 --- /dev/null +++ b/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 77523c356ccf04f56b53e6527c6b12fd +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index 7f89dce..3c3abfb 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,16 @@ With a minimal footprint, it is ideal as a base for custom state machines, dialo * [Getting started](https://github.com/Siccity/xNode/wiki/Getting%20Started) - create your very first node node and graph * [Examples branch](https://github.com/Siccity/xNode/tree/examples) - look at other small projects +### Installing with Unity Package Manager +To install this project as a dependency using the Unity Package Manager, +add the following line to your project's `manifest.json`: + +``` +"com.github.siccity.xnode": "git+https://github.com/siccity/xNode.git" +``` + +If you are using [Assembly Definitions](https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html) in your project, you will need to add `XNode` and/or `XNodeEditor` as Assembly Definition References. + ### Node example: ```csharp // public classes deriving from Node are registered as nodes for use within a graph diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..dd3ed6f --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 243efae3a6b7941ad8f8e54dcf38ce8c +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/XNodeEditor.asmdef b/Scripts/Editor/XNodeEditor.asmdef new file mode 100644 index 0000000..5fa1aab --- /dev/null +++ b/Scripts/Editor/XNodeEditor.asmdef @@ -0,0 +1,17 @@ +{ + "name": "XNodeEditor", + "references": [ + "XNode" + ], + "optionalUnityReferences": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [] +} \ No newline at end of file diff --git a/Scripts/Editor/XNodeEditor.asmdef.meta b/Scripts/Editor/XNodeEditor.asmdef.meta new file mode 100644 index 0000000..7bff074 --- /dev/null +++ b/Scripts/Editor/XNodeEditor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 002c1bbed08fa44d282ef34fd5edb138 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/XNode.asmdef b/Scripts/XNode.asmdef new file mode 100644 index 0000000..eb64493 --- /dev/null +++ b/Scripts/XNode.asmdef @@ -0,0 +1,13 @@ +{ + "name": "XNode", + "references": [], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [] +} diff --git a/Scripts/XNode.asmdef.meta b/Scripts/XNode.asmdef.meta new file mode 100644 index 0000000..8479d75 --- /dev/null +++ b/Scripts/XNode.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b8e24fd1eb19b4226afebb2810e3c19b +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..91252ef --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "com.github.siccity.xnode", + "description": "xNode provides a set of APIs and an editor interface for creating and editing custom node graphs.", + "version": "1.7.0", + "unity": "2018.1", + "displayName": "xNode" +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..c8f1dc4 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e9869d68f06b74538a01e9b8e406159e +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 91aecabd1a93369bfe8b5fc9d11d22b71df4b8e9 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 28 Jun 2019 00:19:29 +0200 Subject: [PATCH 63/69] Fixed UPM Readme #166 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c3abfb..47e2750 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,13 @@ With a minimal footprint, it is ideal as a base for custom state machines, dialo * [Examples branch](https://github.com/Siccity/xNode/tree/examples) - look at other small projects ### Installing with Unity Package Manager +*(Requires Unity version 2018.3.0b7 or above)* + To install this project as a dependency using the Unity Package Manager, add the following line to your project's `manifest.json`: ``` -"com.github.siccity.xnode": "git+https://github.com/siccity/xNode.git" +"com.github.siccity.xnode": "https://github.com/siccity/xNode.git" ``` If you are using [Assembly Definitions](https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html) in your project, you will need to add `XNode` and/or `XNodeEditor` as Assembly Definition References. From c264f4877b20baecae2b450330989485fcf704a1 Mon Sep 17 00:00:00 2001 From: Greg Poole Date: Fri, 28 Jun 2019 21:30:27 +1000 Subject: [PATCH 64/69] Git readme tweak (#167) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 47e2750..52a1c49 100644 --- a/README.md +++ b/README.md @@ -35,13 +35,15 @@ With a minimal footprint, it is ideal as a base for custom state machines, dialo ### Installing with Unity Package Manager *(Requires Unity version 2018.3.0b7 or above)* -To install this project as a dependency using the Unity Package Manager, +To install this project as a [Git dependency](https://docs.unity3d.com/Manual/upm-git.html) using the Unity Package Manager, add the following line to your project's `manifest.json`: ``` "com.github.siccity.xnode": "https://github.com/siccity/xNode.git" ``` +You will need to have Git installed and available in your system's PATH. + If you are using [Assembly Definitions](https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html) in your project, you will need to add `XNode` and/or `XNodeEditor` as Assembly Definition References. ### Node example: From 8d64843d427d7390527c31400770096f5e0e9a79 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 28 Jun 2019 23:08:18 +0200 Subject: [PATCH 65/69] Node skin is now white with default grey tint This lets you get more outrageous colors with [NodeTint] --- Scripts/Editor/NodeEditor.cs | 8 ++++++-- Scripts/Editor/Resources/xnode_node.png | Bin 20154 -> 20191 bytes .../Editor/Resources/xnode_node_workfile.psd | Bin 33860 -> 33269 bytes 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 76f4fe6..015ed0a 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -6,10 +6,11 @@ using UnityEngine; namespace XNodeEditor { /// Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. - [CustomNodeEditor(typeof(XNode.Node))] public class NodeEditor : XNodeEditor.Internal.NodeEditorBase { + private readonly Color DEFAULTCOLOR = new Color32(90, 97, 105, 255); + /// Fires every whenever a node was modified through the editor public static Action onUpdateNode; public readonly static Dictionary portPositions = new Dictionary(); @@ -52,11 +53,14 @@ namespace XNodeEditor { else return 208; } + /// Returns color for target node public virtual Color GetTint() { + // Try get color from [NodeTint] attribute Type type = target.GetType(); Color color; if (NodeEditorWindow.nodeTint.TryGetValue(type, out color)) return color; - else return Color.white; + // Return default color (grey) + else return DEFAULTCOLOR; } public virtual GUIStyle GetBodyStyle() { diff --git a/Scripts/Editor/Resources/xnode_node.png b/Scripts/Editor/Resources/xnode_node.png index a8aa5342c4f1372bfdbb950bdb5d8bf190abae12..6f0b42ebf871065541423b686e18ba31cf24fb5e 100644 GIT binary patch delta 1084 zcmdlrm+}5w#tl=r7^62&5YRP#jLGy?;R)D+`HbMvI+&GY%2SyU`i6D`dW4NY_tla0)D4Gc|;bS=|Tl61`t z43bll%#0F^Qj;b>5I4ne(B_Lm0*sqyiz+e^=PYbyOui^|dh!Q}Mr~qTVWVGM5|o-Y z*;hnz@>WTq$*KHeDuxy*MrJ9dMnKmaB!c{Du4|c?YNnfHnqp~TX=IjUlxnj1v*c$( zmME(kZzsR<)~{de>EaktaqG?Pvt73WMA$$4KijT6w}q$2z)FN|*%UpUzS66{3wM8G zjXDwJWp;V4bZy%s<{Pc8`y9nKuj)6Poi|6s^n%eeF00Z85Z8nlb=6l&Wueu;*Tx%9)JANb@NRdQ{T@r2RIIxHte*2UTWI#a@V>e zmX!XXI#0oO8_br)$QnvXJ%H?=x61Y}h^PvlT;R z;FRm0E595~`e?}>P+@qVOCi;0=5bHP6Hkl2GrlQi&=g@v_VMFt(CAW{|Ia#+QDTy~ z5rbilppR06Knb(^epaBLQINt^#iI`u!v8J%|GM@c_knH9-1Sv=_rHyoYybIYE5p*D z&MXE-0X)bdiXrBDY5t^1OCDSNQ~jK$(Qq>5lcgrZwp{*)UwQAevhLWb^IAufVXohE znYi-@O!ugDE#3L=Hp2wn>8JVK7YAPNt^D%)uea}IDTX*E3#JLNr|L_28#og7N3Go_ z!Pg%DXjaYo>$}&4XuW56!7%SYw1I9LV*4&V3dHXndb}de>G_@qv5Y_X9!Se8 zvurt&!xW*AQgMk(-kKw#RCZy3ZROT-2A>}hS+jl#Hypj5c+u>6e39JOZ9A>)gw3{{ jHO!i~Ec#)|@%4-hyPBoD=89a`WB>wBS3j3^P6WTq$<|UPn>nRk8?t;lY^yc-hqr!xkEe@cNX4x;w@+sa2a2#ixIfoD%_mrE zt%RcJ!q^=bLKOv;2x)dbxcBbPiq${FcW$f>=Q^7AikWL`z=Z^}yfs^v=;|7F&f@XR zGrE3sj+4+QiSo}D?C+Eg=bS6|f9G?;(n+(`qP=YfJF|hqnZpvLfgFY(CNA*Vb$>y& z(gI7(sQTC_p$6~kd!&Ey)g?!@u1P6gc_-^%>(mW$+|K(~a_UMxku9D2+C#Jc&$_GT z_xINRT~$;U$9#e1f?~(PvvOZ0mT+CTHs2=plulgz;@8L48cZwSebws6mc5}GqWh;X zTw&b8u;Z^~zcup_FSF@f#h(}wZhi~;_wVrZ&Bev18T6Pv>Uru*k27ZMyjr7p=45yrBLh!7 z!{MNXbvFfP`9015$tuthH-n*Me|v%mj1m|qNh0~bxP_hNGp+;%wt67O zg*vz!ny#nGbq3sxt3SQx6RX0~KXRfB#|yQ#EVSLvaQE5#{j&oZEo?QXKRC;?)kxL<$}wC*(;{5Uh>#v)$*sGwm z!h$8iTXcF}?BWX&ypVZ+*`)l0jEq!VT@v6mR5D2sHY;JdO>56Vkh7GzT>8x@8=Y+ z-I(;3Nx)Lqz)aW3BE-np%D}+N#C&o-vl*t~R_1CI6H9ZG6eCL$UBeUuBi%Gh6BFGe zV-rJNOLOy7lceON)I?+B%^@srScpax@5dL#!i*dkP++?Ca_Py z{sHq1zct|U#2^9e{Nm>pJBhs^8XjUoLl@? z;*ZnGsGnQ+o#K{IJ<`F|e#7WLL-9xXdy{6p)Rs@EKmGUok@~9m1PJYwy<|;ov-Wp; z!M`tIwC1`sCH^lh=U>bJ>n!wl{?y<5JN|vun;5nFpYK8YRwVvsI92(d;ad8?>XoYY z_5Uw@HJ@NM`N+Rmkts3P_AXr~#ILpZ!)G`Iw&#+`ta4lZR^pfvpXa2m7T+Q3K;OEAkwfDcwn7pg5w)bXU zX>0ogp$o_4Kd(-fS^MbGuKS|eMipmzaYW$$^J1GF-ZG@yDf~m;YY*fA!_XpO^kz{&(fy)qmIiUH^CE@6CU= z{@wn6XL3T3%j7#nmc!RNW?=rA{I}GMTZlmb2>F1Jcd|p7BC`O49FW$X++U_brrh+& zY849g%zwW>xIh?-hBW2=l}2jOn*OKy?l}7%(GyyCfPBV356)j?-g)lLA29Fx6(H}>_6OfVJciHbE;65Aa^*9a^Zx8brkQ8ogW2zZ z>?x<-f!S|?>`5ozg4p#8NSpy+*MnVw+v0oRGzfDo!`t^5De?a;gzVY>q_XQt)j(zS z_5Xo>C)w469KrbR-}(Q{KcMl=`2Gu!_W+s_7(YJ;^0q9w3Qi-2Oy3`Vy7-^vX&*EN zG5>jU`Q7FJtWPE^gQhUHf8U>;IQ#tP&Ho%fA8qNIe6K2hG}WT#Bh(Z;nrflpLsH5f YO|_sBoOb2$XjwZ1%i4g=wvFt}03UvI-v9sr delta 3764 zcmc(fdr%Wc9LM*PBLY@UTOA!M5*_S_G@4KYh}I~KZ>?y>7Eve(nS=mIo4W`|I+WGv{V#}jAMO4RKW5OpHwRtX;DONr>zwo1c!%ENpju0#3W4rslloHn{0mH-|lZ8 zx1Zf!J?r8&)^6U&@DGDRmaITgK$o$(0Y2P7$w0JMQfq&xQT6T<|HNsfN;8Wa$QKIu zf}k}*VX(h2SQJ1eu><}ctY)YBNQD6cfmAY!FYy-%_%X3F-{(uD{sO)%C~&4QHdYiU znk6!&Yn902qPC@_?*e49mt%zG<2Vxw8! zrk&H=zI*+>k%%Jn&Vk{j~i}ro9mxq-&`S#eho$hZE6#D8;Xd#dI}`g zQh=9lYHy3~<58ehO@aCubC*oy?wtI5)S&1T3d{~3YpxeRYo$Qf@W#AlTl}`wWJNa@ zW)ad0?~rI)py;~y!w-tSU$ra#&V(l3(~z#ryXF=3W%mo?Q*tA=HfEr!=a*AAI@8V& z^%36VDe(Aq)!&qDJDY8pI_TX3@i{=_m64ayb#-vDJ?iWed@B>eSHNw@#ftPprcyVU^-l zXTzHd#*Ikb{v?jKxjrL<-@PaQ0P)kvf`N{KtI^%;63akM*DmfN51n~Ro3~Bpo6bF| zt?8^fbaBl2XmM0#uV;CANl?W+EZciixH;s}j^xj=Z-+^iwv`HfFCIE|^WHHfTI@+s zEesu#AOrvyVm@La+`wR=AXpH@v)>48%?@|oNdR~_Wj45no8BDOyfJmxxQtokaQV6s zEE*_;1t6OU%b<}@MQqKT8d=vH`{?$DWO#|S4-MgIp3UUc}21DBc(y_iVf*ol)S)@|~RkZEQ zk?#FaB^3X{(7@`u8*hw2H)PvTVVl~|dnuYC+%^y;HkLS+z6TdGC6O%=?vYUmIb>m4 z7+I0*O;cKxo8F@1r0+cLptNmwlMc;tqI9=Whh|>3Q(D!iWACw3nhVtzc1j7THajrY zIbo`E!c@l`GGYBjR`D8q!U?sPU4+Cb*HxdOFR81^uOOG8p9%(3mluI_$QmW=7(1q$ zppw}!{SKL!VsG<^`J*-B}P#9b;=55lx2e`|}Lvz07!VESlp zlOEl1+3}d}HR{o92d1@+dUn1Y(|o8h?U-glmEpit?}Vw|2~)i@rdDD7UjM7_jOoix ztZ>5ARiDAs)nppeA?7+Tb$KA9BfdfL;$V?ZqEW$)w^LdI74D!k$*4e;IgZElf*01L Xa8nMZ^h>p}ML{V!SdO7E?PK;o1>Go` From 9b239c3564886d175d18f31bd7b79aff938dd215 Mon Sep 17 00:00:00 2001 From: William Henry Date: Fri, 28 Jun 2019 18:04:39 -0400 Subject: [PATCH 66/69] Added Drop event to Node Graph (#157) Override NodeGraphEditor.OnDropObjects to deal with items dropped into the graph through DragAndDrop --- Scripts/Editor/NodeEditorAction.cs | 8 ++++++++ Scripts/Editor/NodeGraphEditor.cs | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 4ae446e..5319639 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -52,6 +52,14 @@ namespace XNodeEditor { wantsMouseMove = true; Event e = Event.current; switch (e.type) { + case EventType.DragUpdated: + case EventType.DragPerform: + DragAndDrop.visualMode = DragAndDropVisualMode.Generic; + if (e.type == EventType.DragPerform) { + DragAndDrop.AcceptDrag(); + graphEditor.OnDropObjects(DragAndDrop.objectReferences); + } + break; case EventType.MouseMove: //Keyboard commands will not get correct mouse position from Event lastMousePosition = e.mousePosition; diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index f79779f..d33a3b3 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -70,6 +70,11 @@ namespace XNodeEditor { return NodeEditorPreferences.GetTypeColor(type); } + /// Deal with objects dropped into the graph through DragAndDrop + public virtual void OnDropObjects(UnityEngine.Object[] objects) { + Debug.Log("No OnDropItems override defined for " + GetType()); + } + /// Create a node and save it in the graph asset public virtual void CreateNode(Type type, Vector2 position) { XNode.Node node = target.AddNode(type); From 95884080b0128c6e0205d4ebd0ac99f3c950c8ab Mon Sep 17 00:00:00 2001 From: elrod Date: Sat, 29 Jun 2019 16:38:25 +0200 Subject: [PATCH 67/69] Resetting random generator state/seed after random color extraction. (#160) --- Scripts/Editor/NodeEditorPreferences.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index 35cc22d..c28c064 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -220,12 +220,19 @@ namespace XNodeEditor { if (settings[lastKey].typeColors.ContainsKey(typeName)) typeColors.Add(type, settings[lastKey].typeColors[typeName]); else { #if UNITY_5_4_OR_NEWER + UnityEngine.Random.State oldState = UnityEngine.Random.state; UnityEngine.Random.InitState(typeName.GetHashCode()); #else + int oldSeed = UnityEngine.Random.seed; UnityEngine.Random.seed = typeName.GetHashCode(); #endif col = new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value); typeColors.Add(type, col); +#if UNITY_5_4_OR_NEWER + UnityEngine.Random.state = oldState; +#else + UnityEngine.Random.seed = oldSeed; +#endif } } return col; From d0e1cd5d66ee9b35a5175e0e4490cf20c907e812 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 29 Jun 2019 16:56:44 +0200 Subject: [PATCH 68/69] Fixed #144 - Added virtual GetPortTooltip in NodeGraphEditor --- Scripts/Editor/NodeEditorGUI.cs | 12 ++++-------- Scripts/Editor/NodeGraphEditor.cs | 11 +++++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 7447b8a..a2908b6 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -441,14 +441,10 @@ namespace XNodeEditor { } private void DrawTooltip() { - if (hoveredPort != null && NodeEditorPreferences.GetSettings().portTooltips) { - Type type = hoveredPort.ValueType; - GUIContent content = new GUIContent(); - content.text = type.PrettyName(); - if (hoveredPort.IsOutput) { - object obj = hoveredPort.node.GetValue(hoveredPort); - content.text += " = " + (obj != null ? obj.ToString() : "null"); - } + if (hoveredPort != null && NodeEditorPreferences.GetSettings().portTooltips && graphEditor != null) { + string tooltip = graphEditor.GetPortTooltip(hoveredPort); + if (string.IsNullOrEmpty(tooltip)) return; + GUIContent content = new GUIContent(tooltip); Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); Rect rect = new Rect(Event.current.mousePosition - (size), size); EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip); diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index d33a3b3..ff2ca5c 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -70,6 +70,17 @@ namespace XNodeEditor { return NodeEditorPreferences.GetTypeColor(type); } + public virtual string GetPortTooltip(XNode.NodePort port) { + Type portType = port.ValueType; + string tooltip = ""; + tooltip = portType.PrettyName(); + if (port.IsOutput) { + object obj = port.node.GetValue(port); + tooltip += " = " + (obj != null ? obj.ToString() : "null"); + } + return tooltip; + } + /// Deal with objects dropped into the graph through DragAndDrop public virtual void OnDropObjects(UnityEngine.Object[] objects) { Debug.Log("No OnDropItems override defined for " + GetType()); From 8e0bd964ad9664e9586f4ddb1f7d18dc10bb570a Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 1 Jul 2019 09:54:51 +0200 Subject: [PATCH 69/69] Fixed #107 - OnValidate OnValidate is now called on rename as well. Removed outdated OnValidate fix. It probably never worked anyway. --- Scripts/Editor/NodeEditorGUI.cs | 11 ----------- Scripts/Editor/NodeEditorReflection.cs | 9 +++++++++ Scripts/Editor/RenamePopup.cs | 2 ++ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index a2908b6..6304754 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -282,12 +282,6 @@ namespace XNodeEditor { selectionCache = new List(Selection.objects); } - System.Reflection.MethodInfo onValidate = null; - if (Selection.activeObject != null && Selection.activeObject is XNode.Node) { - onValidate = Selection.activeObject.GetType().GetMethod("OnValidate"); - if (onValidate != null) EditorGUI.BeginChangeCheck(); - } - BeginZoomed(); Vector2 mousePos = Event.current.mousePosition; @@ -420,11 +414,6 @@ namespace XNodeEditor { if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray(); EndZoomed(); - - //If a change in is detected in the selected node, call OnValidate method. - //This is done through reflection because OnValidate is only relevant in editor, - //and thus, the code should not be included in build. - if (onValidate != null && EditorGUI.EndChangeCheck()) onValidate.Invoke(Selection.activeObject, null); } private bool ShouldBeCulled(XNode.Node node) { diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index 6b8b121..76ec656 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -103,6 +103,15 @@ namespace XNodeEditor { } } + /// Call OnValidate on target + public static void TriggerOnValidate(UnityEngine.Object target) { + System.Reflection.MethodInfo onValidate = null; + if (target != null) { + onValidate = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (onValidate != null) onValidate.Invoke(target, null); + } + } + public static KeyValuePair[] GetContextMenuMethods(object obj) { Type type = obj.GetType(); MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); diff --git a/Scripts/Editor/RenamePopup.cs b/Scripts/Editor/RenamePopup.cs index 85d229f..4903525 100644 --- a/Scripts/Editor/RenamePopup.cs +++ b/Scripts/Editor/RenamePopup.cs @@ -51,6 +51,7 @@ namespace XNodeEditor { target.name = NodeEditorUtilities.NodeDefaultName(target.GetType()); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); Close(); + NodeEditorWindow.TriggerOnValidate(target); } } // Rename asset to input text @@ -59,6 +60,7 @@ namespace XNodeEditor { target.name = input; AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); Close(); + NodeEditorWindow.TriggerOnValidate(target); } } }