From 8a03811ee35a4ceb034c9317c0b5543e16db9172 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 13 May 2019 14:26:29 +0200 Subject: [PATCH 01/52] 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 02/52] 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 03/52] 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 04/52] 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 05/52] 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 06/52] #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 07/52] 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 08/52] 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 09/52] 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 10/52] 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 11/52] 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 12/52] 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 13/52] 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 14/52] 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 15/52] 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 16/52] 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 17/52] 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 18/52] 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 19/52] 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 20/52] 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 21/52] 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 22/52] 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 23/52] 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 24/52] 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 25/52] 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 26/52] 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); } } } From 6a629d159c2c71c3e6455e68b2539b629291bc8a Mon Sep 17 00:00:00 2001 From: Simon Rodriguez Date: Tue, 9 Jul 2019 11:44:00 +0200 Subject: [PATCH 27/52] Added type constraints to output port (#170) --- Scripts/Node.cs | 20 +++++++++++++++----- Scripts/NodePort.cs | 6 +++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Scripts/Node.cs b/Scripts/Node.cs index cd86b95..27e32c7 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -290,16 +290,26 @@ namespace XNode { [Obsolete("Use dynamicPortList instead")] public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } } public bool dynamicPortList; + public TypeConstraint typeConstraint; + + /// 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? + /// Constrains which input connections can be made from this port + /// 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, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) { + this.backingValue = backingValue; + this.connectionType = connectionType; + this.dynamicPortList = dynamicPortList; + this.typeConstraint = typeConstraint; + } /// 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 dynamicPortList = false) { - this.backingValue = backingValue; - this.connectionType = connectionType; - this.dynamicPortList = dynamicPortList; - } + [Obsolete("Use constructor with TypeConstraint")] + public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList) { } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index 24e4941..1000b23 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -67,6 +67,7 @@ namespace XNode { } else if (attribs[i] is Node.OutputAttribute) { _direction = IO.Output; _connectionType = (attribs[i] as Node.OutputAttribute).connectionType; + _typeConstraint = (attribs[i] as Node.OutputAttribute).typeConstraint; } } } @@ -255,9 +256,12 @@ namespace XNode { else output = port; // If there isn't one of each, they can't connect if (input == null || output == null) return false; - // Check type constraints + // Check input 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; + // Check output type constraints + if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; + if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && output.ValueType != input.ValueType) return false; // Success return true; } From 723ecc43c163240e708345f0040e4e83bc8f4854 Mon Sep 17 00:00:00 2001 From: Simon Rodriguez Date: Wed, 10 Jul 2019 01:07:23 +0200 Subject: [PATCH 28/52] Ports losing connections (#171) * clear connections before removing port, so the connected nodes don't have invalid references. * Try reconnecting the previous ports connections in the new port. Skip reconnecting if the port is dynamic or if the direction has changed. --- Scripts/NodeDataCache.cs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index d7fa38c..fa6a327 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -14,6 +14,7 @@ namespace XNode { if (!Initialized) BuildCache(); Dictionary staticPorts = new Dictionary(); + Dictionary> removedPorts = new Dictionary>(); System.Type nodeType = node.GetType(); List typePortCache; @@ -30,16 +31,33 @@ 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 || port.typeConstraint != staticPort.typeConstraint) ports.Remove(port.fieldName); - else port.ValueType = staticPort.ValueType; + if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) { + // If port is not dynamic and direction hasn't changed, add it to the list so we can try reconnecting the ports connections. + if (!port.IsDynamic && port.direction == staticPort.direction) removedPorts.Add(port.fieldName, port.GetConnections()); + port.ClearConnections(); + ports.Remove(port.fieldName); + } else port.ValueType = staticPort.ValueType; } // If port doesn't exist anymore, remove it - else if (port.IsStatic) ports.Remove(port.fieldName); + else if (port.IsStatic) { + port.ClearConnections(); + ports.Remove(port.fieldName); + } } // Add missing ports foreach (NodePort staticPort in staticPorts.Values) { if (!ports.ContainsKey(staticPort.fieldName)) { - ports.Add(staticPort.fieldName, new NodePort(staticPort, node)); + NodePort port = new NodePort(staticPort, node); + //If we just removed the port, try re-adding the connections + List reconnectConnections; + if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) { + for (int i = 0; i < reconnectConnections.Count; i++) { + NodePort connection = reconnectConnections[i]; + if (connection == null) continue; + if (port.CanConnectTo(connection)) port.Connect(connection); + } + } + ports.Add(staticPort.fieldName, port); } } } From 93fa101af833b4d91a57ab9f3ceb857b9837e845 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 22 Jul 2019 01:41:58 +0300 Subject: [PATCH 29/52] Multiple dynamicPortList IndexOutofRange fix. (#176) --- Scripts/Editor/NodeEditorGUILayout.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index cd4d320..7372508 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -293,7 +293,7 @@ namespace XNodeEditor { } } return new { index = -1, port = (XNode.NodePort) null }; - }); + }).Where(x => x.port != null); List dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); ReorderableList list = null; @@ -423,7 +423,7 @@ namespace XNodeEditor { } } return new { index = -1, port = (XNode.NodePort) null }; - }); + }).Where(x => x.port != null); dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); int index = rl.index; @@ -491,4 +491,4 @@ namespace XNodeEditor { return list; } } -} \ No newline at end of file +} From 891ecebc3f79662e02f364d2ca91d145fb5c869d Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Wed, 24 Jul 2019 10:04:05 +0200 Subject: [PATCH 30/52] Put NodeEditorReflection into its own static class --- Scripts/Editor/NodeEditor.cs | 6 +-- Scripts/Editor/NodeEditorBase.cs | 2 +- Scripts/Editor/NodeEditorGUILayout.cs | 8 +-- Scripts/Editor/NodeEditorReflection.cs | 74 ++++++++++++-------------- Scripts/Editor/NodeEditorUtilities.cs | 2 +- Scripts/Editor/NodeEditorWindow.cs | 10 ++++ Scripts/Editor/NodeGraphEditor.cs | 8 +-- Scripts/Editor/RenamePopup.cs | 4 +- 8 files changed, 59 insertions(+), 55 deletions(-) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 015ed0a..631d8e5 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -49,7 +49,7 @@ namespace XNodeEditor { public virtual int GetWidth() { Type type = target.GetType(); int width; - if (NodeEditorWindow.nodeWidth.TryGetValue(type, out width)) return width; + if (type.TryGetAttributeWidth(out width)) return width; else return 208; } @@ -58,7 +58,7 @@ namespace XNodeEditor { // Try get color from [NodeTint] attribute Type type = target.GetType(); Color color; - if (NodeEditorWindow.nodeTint.TryGetValue(type, out color)) return color; + if (type.TryGetAttributeTint(out color)) return color; // Return default color (grey) else return DEFAULTCOLOR; } @@ -84,7 +84,7 @@ namespace XNodeEditor { // Custom sctions if only one node is selected if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { XNode.Node node = Selection.activeObject as XNode.Node; - NodeEditorWindow.AddCustomContextMenuItems(menu, node); + menu.AddCustomContextMenuItems(node); } } diff --git a/Scripts/Editor/NodeEditorBase.cs b/Scripts/Editor/NodeEditorBase.cs index ab463e6..261a284 100644 --- a/Scripts/Editor/NodeEditorBase.cs +++ b/Scripts/Editor/NodeEditorBase.cs @@ -50,7 +50,7 @@ namespace XNodeEditor.Internal { editorTypes = new Dictionary(); //Get all classes deriving from NodeEditor via reflection - Type[] nodeEditors = XNodeEditor.NodeEditorWindow.GetDerivedTypes(typeof(T)); + Type[] nodeEditors = typeof(T).GetDerivedTypes(); for (int i = 0; i < nodeEditors.Length; i++) { if (nodeEditors[i].IsAbstract) continue; var attribs = nodeEditors[i].GetCustomAttributes(typeof(A), false); diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 7372508..417a454 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -141,7 +141,7 @@ namespace XNodeEditor { Color backgroundColor = new Color32(90, 97, 105, 255); Color tint; - if (NodeEditorWindow.nodeTint.TryGetValue(port.node.GetType(), out tint)) backgroundColor *= tint; + if (port.node.GetType().TryGetAttributeTint(out tint)) backgroundColor *= tint; Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); @@ -153,7 +153,7 @@ namespace XNodeEditor { private static System.Type GetType(SerializedProperty property) { System.Type parentType = property.serializedObject.targetObject.GetType(); - System.Reflection.FieldInfo fi = NodeEditorWindow.GetFieldInfo(parentType, property.name); + System.Reflection.FieldInfo fi = parentType.GetFieldInfo(property.name); return fi.FieldType; } @@ -197,7 +197,7 @@ namespace XNodeEditor { Color backgroundColor = new Color32(90, 97, 105, 255); Color tint; - if (NodeEditorWindow.nodeTint.TryGetValue(port.node.GetType(), out tint)) backgroundColor *= tint; + if (port.node.GetType().TryGetAttributeTint(out tint)) backgroundColor *= tint; Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); @@ -225,7 +225,7 @@ namespace XNodeEditor { Color backgroundColor = new Color32(90, 97, 105, 255); Color tint; - if (NodeEditorWindow.nodeTint.TryGetValue(port.node.GetType(), out tint)) backgroundColor *= tint; + if (port.node.GetType().TryGetAttributeTint(out tint)) backgroundColor *= tint; Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index 76ec656..0a0a36a 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -7,62 +7,55 @@ using UnityEditor; using UnityEngine; namespace XNodeEditor { - /// Contains reflection-related info - public partial class NodeEditorWindow { - /// Custom node tint colors defined with [NodeColor(r, g, b)] - public static Dictionary nodeTint { get { return _nodeTint != null ? _nodeTint : _nodeTint = GetNodeTint(); } } - - [NonSerialized] private static Dictionary _nodeTint; - /// Custom node widths defined with [NodeWidth(width)] - public static Dictionary nodeWidth { get { return _nodeWidth != null ? _nodeWidth : _nodeWidth = GetNodeWidth(); } } - - [NonSerialized] private static Dictionary _nodeWidth; + /// Contains reflection-related extensions built for xNode + public static class NodeEditorReflection { + [NonSerialized] private static Dictionary nodeTint; + [NonSerialized] private static Dictionary nodeWidth; /// All available node types public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } } [NonSerialized] private static Type[] _nodeTypes = null; - private Func isDocked { - get { - if (_isDocked == null) { - BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; - MethodInfo isDockedMethod = typeof(NodeEditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true); - _isDocked = (Func) Delegate.CreateDelegate(typeof(Func), this, isDockedMethod); - } - return _isDocked; - } + /// Return a delegate used to determine whether window is docked or not. It is faster to cache this delegate than run the reflection required each time. + public static Func GetIsDockedDelegate(this EditorWindow window) { + BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; + MethodInfo isDockedMethod = typeof(EditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true); + return (Func) Delegate.CreateDelegate(typeof(Func), window, isDockedMethod); } - private Func _isDocked; public static Type[] GetNodeTypes() { //Get all classes deriving from Node via reflection return GetDerivedTypes(typeof(XNode.Node)); } - public static Dictionary GetNodeTint() { - Dictionary tints = new Dictionary(); - for (int i = 0; i < nodeTypes.Length; i++) { - var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeTintAttribute), true); - if (attribs == null || attribs.Length == 0) continue; - XNode.Node.NodeTintAttribute attrib = attribs[0] as XNode.Node.NodeTintAttribute; - tints.Add(nodeTypes[i], attrib.color); + /// Custom node tint colors defined with [NodeColor(r, g, b)] + public static bool TryGetAttributeTint(this Type nodeType, out Color tint) { + if (nodeTint == null) { + CacheAttributes(ref nodeTint, x => x.color); } - return tints; + return nodeTint.TryGetValue(nodeType, out tint); } - public static Dictionary GetNodeWidth() { - Dictionary widths = new Dictionary(); - for (int i = 0; i < nodeTypes.Length; i++) { - var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeWidthAttribute), true); - if (attribs == null || attribs.Length == 0) continue; - XNode.Node.NodeWidthAttribute attrib = attribs[0] as XNode.Node.NodeWidthAttribute; - widths.Add(nodeTypes[i], attrib.width); + /// Get custom node widths defined with [NodeWidth(width)] + public static bool TryGetAttributeWidth(this Type nodeType, out int width) { + if (nodeWidth == null) { + CacheAttributes(ref nodeWidth, x => x.width); + } + return nodeWidth.TryGetValue(nodeType, out width); + } + + private static void CacheAttributes(ref Dictionary dict, Func getter) where A : Attribute { + dict = new Dictionary(); + for (int i = 0; i < nodeTypes.Length; i++) { + object[] attribs = nodeTypes[i].GetCustomAttributes(typeof(A), true); + if (attribs == null || attribs.Length == 0) continue; + A attrib = attribs[0] as A; + dict.Add(nodeTypes[i], getter(attrib)); } - return widths; } /// Get FieldInfo of a field, including those that are private and/or inherited - public static FieldInfo GetFieldInfo(Type type, string fieldName) { + public static FieldInfo GetFieldInfo(this 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 @@ -71,7 +64,7 @@ namespace XNodeEditor { } /// Get all classes deriving from baseType via reflection - public static Type[] GetDerivedTypes(Type baseType) { + public static Type[] GetDerivedTypes(this Type baseType) { List types = new List(); System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { @@ -82,7 +75,8 @@ namespace XNodeEditor { return types.ToArray(); } - public static void AddCustomContextMenuItems(GenericMenu contextMenu, object obj) { + /// Find methods marked with the [ContextMenu] attribute and add them to the context menu + public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj) { KeyValuePair[] items = GetContextMenuMethods(obj); if (items.Length != 0) { contextMenu.AddSeparator(""); @@ -104,7 +98,7 @@ namespace XNodeEditor { } /// Call OnValidate on target - public static void TriggerOnValidate(UnityEngine.Object target) { + public static void TriggerOnValidate(this UnityEngine.Object target) { System.Reflection.MethodInfo onValidate = null; if (target != null) { onValidate = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index 77e71bb..f45a042 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -36,7 +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 = NodeEditorWindow.GetFieldInfo(classType, fieldName); + FieldInfo field = classType.GetFieldInfo(fieldName); // This shouldn't happen. Ever. if (field == null) { Debug.LogWarning("Field " + fieldName + " couldnt be found"); diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index fc04439..a063410 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using UnityEditor; using UnityEditor.Callbacks; using UnityEngine; +using System; +using Object = UnityEngine.Object; namespace XNodeEditor { [InitializeOnLoad] @@ -14,6 +16,14 @@ namespace XNodeEditor { [SerializeField] private NodePortReference[] _references = new NodePortReference[0]; [SerializeField] private Rect[] _rects = new Rect[0]; + private Func isDocked { + get { + if (_isDocked == null) _isDocked = this.GetIsDockedDelegate(); + return _isDocked; + } + } + private Func _isDocked; + [System.Serializable] private class NodePortReference { [SerializeField] private XNode.Node _node; [SerializeField] private string _name; diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index ff2ca5c..110c506 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -44,8 +44,8 @@ namespace XNodeEditor { /// Add items for the context menu when right-clicking this node. Override to add custom menu items. public virtual void AddContextMenuItems(GenericMenu menu) { Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition); - for (int i = 0; i < NodeEditorWindow.nodeTypes.Length; i++) { - Type type = NodeEditorWindow.nodeTypes[i]; + for (int i = 0; i < NodeEditorReflection.nodeTypes.Length; i++) { + Type type = NodeEditorReflection.nodeTypes[i]; //Get node context menu path string path = GetNodeMenuName(type); @@ -58,8 +58,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); + menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorReflection.OpenPreferences()); + menu.AddCustomContextMenuItems(target); } public virtual Color GetPortColor(XNode.NodePort port) { diff --git a/Scripts/Editor/RenamePopup.cs b/Scripts/Editor/RenamePopup.cs index 4903525..564374e 100644 --- a/Scripts/Editor/RenamePopup.cs +++ b/Scripts/Editor/RenamePopup.cs @@ -51,7 +51,7 @@ namespace XNodeEditor { target.name = NodeEditorUtilities.NodeDefaultName(target.GetType()); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); Close(); - NodeEditorWindow.TriggerOnValidate(target); + target.TriggerOnValidate(); } } // Rename asset to input text @@ -60,7 +60,7 @@ namespace XNodeEditor { target.name = input; AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); Close(); - NodeEditorWindow.TriggerOnValidate(target); + target.TriggerOnValidate(); } } } From bb847a3044b70081dc9b4570ccf1ee6575da9acb Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Wed, 24 Jul 2019 10:25:29 +0200 Subject: [PATCH 31/52] Moved IsMac to NodeEditorUtilities --- Scripts/Editor/NodeEditorAction.cs | 12 ++---------- Scripts/Editor/NodeEditorUtilities.cs | 12 ++++++++++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 5319639..4b57ba1 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -299,7 +299,7 @@ namespace XNodeEditor { case EventType.KeyDown: if (EditorGUIUtility.editingTextField) break; else if (e.keyCode == KeyCode.F) Home(); - if (IsMac()) { + if (NodeEditorUtilities.IsMac()) { if (e.keyCode == KeyCode.Return) RenameSelectedNode(); } else { if (e.keyCode == KeyCode.F2) RenameSelectedNode(); @@ -310,7 +310,7 @@ namespace XNodeEditor { if (e.commandName == "SoftDelete") { if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); e.Use(); - } else if (IsMac() && e.commandName == "Delete") { + } else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete") { if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); e.Use(); } else if (e.commandName == "Duplicate") { @@ -335,14 +335,6 @@ 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 diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index f45a042..c636309 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -9,7 +9,7 @@ using UnityEngine; using Object = UnityEngine.Object; namespace XNodeEditor { - /// A set of editor-only utilities and extensions for UnityNodeEditorBase + /// A set of editor-only utilities and extensions for xNode public static class NodeEditorUtilities { /// C#'s Script Icon [The one MonoBhevaiour Scripts have]. @@ -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] is T){ + if (attribs[i] is T) { attribOut = attribs[i] as T; return true; } @@ -84,6 +84,14 @@ namespace XNodeEditor { return true; } + public static bool IsMac() { +#if UNITY_2017_1_OR_NEWER + return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX; +#else + return SystemInfo.operatingSystem.StartsWith("Mac"); +#endif + } + /// Returns true if this can be casted to public static bool IsCastableTo(this Type from, Type to) { if (to.IsAssignableFrom(from)) return true; From 5bc267d23b90427d074e8960d3742a58d43560fe Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Wed, 24 Jul 2019 23:52:31 +0200 Subject: [PATCH 32/52] Fixed #119 - node port background color inconsistency --- Scripts/Editor/NodeEditorGUILayout.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 417a454..b8e2019 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -139,9 +139,8 @@ namespace XNodeEditor { rect.size = new Vector2(16, 16); - Color backgroundColor = new Color32(90, 97, 105, 255); - Color tint; - if (port.node.GetType().TryGetAttributeTint(out tint)) backgroundColor *= tint; + NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); + Color backgroundColor = editor.GetTint(); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); @@ -176,7 +175,6 @@ namespace XNodeEditor { Rect rect = GUILayoutUtility.GetLastRect(); position = rect.position - new Vector2(16, 0); - } // If property is an output, display a text label and put a port handle on the right side else if (port.direction == XNode.NodePort.IO.Output) { @@ -195,9 +193,8 @@ namespace XNodeEditor { Rect rect = new Rect(position, new Vector2(16, 16)); - Color backgroundColor = new Color32(90, 97, 105, 255); - Color tint; - if (port.node.GetType().TryGetAttributeTint(out tint)) backgroundColor *= tint; + NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); + Color backgroundColor = editor.GetTint(); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); @@ -223,9 +220,8 @@ namespace XNodeEditor { rect.size = new Vector2(16, 16); - Color backgroundColor = new Color32(90, 97, 105, 255); - Color tint; - if (port.node.GetType().TryGetAttributeTint(out tint)) backgroundColor *= tint; + NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); + Color backgroundColor = editor.GetTint(); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); From ba48e32a22b5b83d136345db248a91c72ee88f1c Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 4 Aug 2019 22:25:51 +0200 Subject: [PATCH 33/52] Moved RerouteReference to own script --- Scripts/Editor/Internal.meta | 8 ++++++++ Scripts/Editor/Internal/RerouteReference.cs | 20 +++++++++++++++++++ .../Editor/Internal/RerouteReference.cs.meta | 11 ++++++++++ Scripts/Editor/NodeEditorAction.cs | 18 +---------------- Scripts/Editor/NodeEditorGUI.cs | 1 + 5 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 Scripts/Editor/Internal.meta create mode 100644 Scripts/Editor/Internal/RerouteReference.cs create mode 100644 Scripts/Editor/Internal/RerouteReference.cs.meta diff --git a/Scripts/Editor/Internal.meta b/Scripts/Editor/Internal.meta new file mode 100644 index 0000000..600ad29 --- /dev/null +++ b/Scripts/Editor/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a6a1bbc054e282346a02e7bbddde3206 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/Internal/RerouteReference.cs b/Scripts/Editor/Internal/RerouteReference.cs new file mode 100644 index 0000000..4e21130 --- /dev/null +++ b/Scripts/Editor/Internal/RerouteReference.cs @@ -0,0 +1,20 @@ +using UnityEngine; + +namespace XNodeEditor.Internal { + public struct RerouteReference { + public XNode.NodePort port; + public int connectionIndex; + public int pointIndex; + + public RerouteReference(XNode.NodePort port, int connectionIndex, int pointIndex) { + this.port = port; + this.connectionIndex = connectionIndex; + this.pointIndex = pointIndex; + } + + public void InsertPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex).Insert(pointIndex, pos); } + public void SetPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex) [pointIndex] = pos; } + public void RemovePoint() { port.GetReroutePoints(connectionIndex).RemoveAt(pointIndex); } + public Vector2 GetPoint() { return port.GetReroutePoints(connectionIndex) [pointIndex]; } + } +} \ No newline at end of file diff --git a/Scripts/Editor/Internal/RerouteReference.cs.meta b/Scripts/Editor/Internal/RerouteReference.cs.meta new file mode 100644 index 0000000..9a2f9cb --- /dev/null +++ b/Scripts/Editor/Internal/RerouteReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 399f3c5fb717b2c458c3e9746f8959a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 4b57ba1..f0ebec0 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; +using XNodeEditor.Internal; namespace XNodeEditor { public partial class NodeEditorWindow { @@ -31,23 +32,6 @@ namespace XNodeEditor { private bool isDoubleClick = false; private Vector2 lastMousePosition; - private struct RerouteReference { - public XNode.NodePort port; - public int connectionIndex; - public int pointIndex; - - public RerouteReference(XNode.NodePort port, int connectionIndex, int pointIndex) { - this.port = port; - this.connectionIndex = connectionIndex; - this.pointIndex = pointIndex; - } - - public void InsertPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex).Insert(pointIndex, pos); } - public void SetPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex) [pointIndex] = pos; } - public void RemovePoint() { port.GetReroutePoints(connectionIndex).RemoveAt(pointIndex); } - public Vector2 GetPoint() { return port.GetReroutePoints(connectionIndex) [pointIndex]; } - } - public void Controls() { wantsMouseMove = true; Event e = Event.current; diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 6304754..55ffd31 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; +using XNodeEditor.Internal; namespace XNodeEditor { /// Contains GUI methods From d45f396ebf97afe243b39cf397b9c6f8c200433d Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 4 Aug 2019 22:41:38 +0200 Subject: [PATCH 34/52] Revert "Improved zoom with the help of Jeroenimoo0's PR" This commit caused issues with the selection box --- Scripts/Editor/NodeEditorGUI.cs | 66 +++++++++++++++------------------ 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 55ffd31..3fc5f21 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -18,8 +18,6 @@ namespace XNodeEditor { /// 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; @@ -44,27 +42,24 @@ namespace XNodeEditor { GUI.matrix = m; } - public void BeginZoomed() { - GUI.EndGroup(); + public static void BeginZoomed(Rect rect, float zoom, float topPadding) { + GUI.EndClip(); - 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; + 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)); } - public void EndZoomed() { - GUI.matrix = prevGuiMatrix; - GUI.EndGroup(); - GUI.BeginGroup(new Rect(0.0f, topPadding - (topPadding * zoom), Screen.width, Screen.height)); + 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); } /// Ends the GUI Group temporarily to draw any additional elements in the NodeGraphEditor. @@ -77,19 +72,6 @@ namespace XNodeEditor { 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) { rect.position = Vector2.zero; @@ -168,7 +150,8 @@ 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; } @@ -283,7 +266,13 @@ namespace XNodeEditor { selectionCache = new List(Selection.objects); } - BeginZoomed(); + 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(position, zoom, topPadding); Vector2 mousePos = Event.current.mousePosition; @@ -414,7 +403,12 @@ namespace XNodeEditor { } if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray(); - EndZoomed(); + EndZoomed(position, zoom, topPadding); + + //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) { From f38ff44261f081120de1225f5b01d50de5d93a35 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 5 Aug 2019 00:26:10 +0200 Subject: [PATCH 35/52] Fixed #175 - Adjusted skipped DLLs check --- Scripts/NodeDataCache.cs | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index fa6a327..67b0658 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -8,6 +8,7 @@ namespace XNode { public static class NodeDataCache { private static PortDataCache portDataCache; private static bool Initialized { get { return portDataCache != null; } } + private static readonly HashSet skippedDlls = new HashSet() { "UnityEditor", "mscorlib", "System", "System.Xml", "System.Core" }; /// Update static ports to reflect class fields. public static void UpdatePorts(Node node, Dictionary ports) { @@ -62,29 +63,32 @@ namespace XNode { } } + /// Cache node types private static void BuildCache() { portDataCache = new PortDataCache(); System.Type baseType = typeof(Node); List nodeTypes = new List(); System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); - Assembly selfAssembly = Assembly.GetAssembly(baseType); - if (selfAssembly.FullName.StartsWith("Assembly-CSharp") && !selfAssembly.FullName.Contains("-firstpass")) { - // If xNode is not used as a DLL, check only CSharp (fast) - nodeTypes.AddRange(selfAssembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t))); - } 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 != 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()); + + // Loop through assemblies and add node types to list + foreach (Assembly assembly in assemblies) { + // Skip certain dlls to improve performance + string assemblyName = assembly.GetName().Name; + int index = assemblyName.IndexOf('.'); + if (index != -1) assemblyName = assemblyName.Substring(0, index); + switch (assemblyName) { + // The following assemblies, and sub-assemblies (eg. UnityEngine.UI) are skipped + case "UnityEditor": + case "UnityEngine": + case "System": + case "mscorlib": + continue; + default: + nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); + break; } } + for (int i = 0; i < nodeTypes.Count; i++) { CachePorts(nodeTypes[i]); } From 80c8b36ab81d3b8eeb1c9d0b961618e26408d9f8 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 5 Aug 2019 00:51:47 +0200 Subject: [PATCH 36/52] Removed unused hashset --- Scripts/NodeDataCache.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index 67b0658..02e35a1 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -8,7 +8,6 @@ namespace XNode { public static class NodeDataCache { private static PortDataCache portDataCache; private static bool Initialized { get { return portDataCache != null; } } - private static readonly HashSet skippedDlls = new HashSet() { "UnityEditor", "mscorlib", "System", "System.Xml", "System.Core" }; /// Update static ports to reflect class fields. public static void UpdatePorts(Node node, Dictionary ports) { From 25d208d2786e1419e9831dee775925632aaf4d95 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Thu, 8 Aug 2019 09:56:10 +0200 Subject: [PATCH 37/52] Fixed #156 - Incorrect drawing of custom graph editor OnGUI --- Scripts/Editor/NodeEditorGUI.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 3fc5f21..7198486 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -13,8 +13,6 @@ 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; @@ -31,7 +29,7 @@ namespace XNodeEditor { DrawNodes(); DrawSelectionBox(); DrawTooltip(); - DrawGraphOnGUI(); + graphEditor.OnGUI(); // Run and reset onLateGUI if (onLateGUI != null) { @@ -62,16 +60,6 @@ namespace XNodeEditor { GUI.matrix = Matrix4x4.TRS(offset, Quaternion.identity, Vector3.one); } - /// 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 void DrawGrid(Rect rect, float zoom, Vector2 panOffset) { rect.position = Vector2.zero; From 650522223cd5089a3c6d8cec3d2454eeb7fee698 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 11 Aug 2019 23:52:33 +0200 Subject: [PATCH 38/52] Fixed minor jittering with tooltips --- Scripts/Editor/NodeEditorGUI.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 7198486..9954325 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -418,6 +418,7 @@ namespace XNodeEditor { if (string.IsNullOrEmpty(tooltip)) return; GUIContent content = new GUIContent(tooltip); Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); + size.x += 8; Rect rect = new Rect(Event.current.mousePosition - (size), size); EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip); Repaint(); From 788b8d8a0b7aad02e8f4c480aff597b265ceebe3 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 12 Aug 2019 00:11:14 +0200 Subject: [PATCH 39/52] Automatically remove "Node" postfix from creation menu --- Scripts/Editor/NodeEditorUtilities.cs | 9 +++++++++ Scripts/Editor/NodeGraphEditor.cs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index c636309..bf92ba8 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -150,6 +150,15 @@ namespace XNodeEditor { return typeName; } + /// Returns the default creation path for the node type. + public static string NodeDefaultPath(Type type) { + string typePath = type.ToString().Replace('.', '/'); + // Automatically remove redundant 'Node' postfix + if (typePath.EndsWith("Node")) typePath = typePath.Substring(0, typePath.LastIndexOf("Node")); + typePath = UnityEditor.ObjectNames.NicifyVariableName(typePath); + return typePath; + } + /// 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 110c506..a45d566 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -38,7 +38,7 @@ namespace XNodeEditor { if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path return attrib.menuName; else // Return generated path - return ObjectNames.NicifyVariableName(type.ToString().Replace('.', '/')); + return NodeEditorUtilities.NodeDefaultPath(type); } /// Add items for the context menu when right-clicking this node. Override to add custom menu items. From c995bdf0bc2a3c0452366c6c6856663f5b0fd674 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 12 Aug 2019 01:01:57 +0200 Subject: [PATCH 40/52] Added NodeEnumDrawer.EnumPopup so you can use it from your own node editor scripts --- Scripts/Editor/Drawers/NodeEnumDrawer.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Scripts/Editor/Drawers/NodeEnumDrawer.cs b/Scripts/Editor/Drawers/NodeEnumDrawer.cs index 7478f94..9ef1434 100644 --- a/Scripts/Editor/Drawers/NodeEnumDrawer.cs +++ b/Scripts/Editor/Drawers/NodeEnumDrawer.cs @@ -12,6 +12,12 @@ namespace XNodeEditor { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property); + EnumPopup(position, property, label); + + EditorGUI.EndProperty(); + } + + public static void EnumPopup(Rect position, SerializedProperty property, GUIContent label) { // Throw error on wrong type if (property.propertyType != SerializedPropertyType.Enum) { throw new ArgumentException("Parameter selected must be of type System.Enum"); @@ -39,10 +45,9 @@ namespace XNodeEditor { NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property); } #endif - EditorGUI.EndProperty(); } - private void ShowContextMenuAtMouse(SerializedProperty property) { + private static void ShowContextMenuAtMouse(SerializedProperty property) { // Initialize menu GenericMenu menu = new GenericMenu(); @@ -57,7 +62,7 @@ namespace XNodeEditor { menu.DropDown(r); } - private void SetEnum(SerializedProperty property, int index) { + private static void SetEnum(SerializedProperty property, int index) { property.enumValueIndex = index; property.serializedObject.ApplyModifiedProperties(); property.serializedObject.Update(); From 6fa3bdf2ad936092f792a0fdab5fe8a9c8e4c11a Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 12 Aug 2019 22:07:27 +0200 Subject: [PATCH 41/52] Marked NodeEnumDrawer ShowContextMenuAtMouse public --- Scripts/Editor/Drawers/NodeEnumDrawer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/Editor/Drawers/NodeEnumDrawer.cs b/Scripts/Editor/Drawers/NodeEnumDrawer.cs index 9ef1434..8aa748c 100644 --- a/Scripts/Editor/Drawers/NodeEnumDrawer.cs +++ b/Scripts/Editor/Drawers/NodeEnumDrawer.cs @@ -47,7 +47,7 @@ namespace XNodeEditor { #endif } - private static void ShowContextMenuAtMouse(SerializedProperty property) { + public static void ShowContextMenuAtMouse(SerializedProperty property) { // Initialize menu GenericMenu menu = new GenericMenu(); From 43bcb54fda4378e46a678c866242954248a63702 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 1 Sep 2019 23:49:19 +0200 Subject: [PATCH 42/52] Odin inspector support (#182) * Added Odin Inspector support * Added support for ports --- Scripts/Editor/Drawers/Odin.meta | 8 +++ .../Odin/InNodeEditorAttributeProcessor.cs | 48 ++++++++++++++++++ .../InNodeEditorAttributeProcessor.cs.meta | 11 +++++ .../Drawers/Odin/InputAttributeDrawer.cs | 49 +++++++++++++++++++ .../Drawers/Odin/InputAttributeDrawer.cs.meta | 11 +++++ .../Drawers/Odin/OutputAttributeDrawer.cs | 49 +++++++++++++++++++ .../Odin/OutputAttributeDrawer.cs.meta | 11 +++++ Scripts/Editor/NodeEditor.cs | 38 +++++++++++++- Scripts/Editor/NodeEditorBase.cs | 21 ++++++++ 9 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 Scripts/Editor/Drawers/Odin.meta create mode 100644 Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs create mode 100644 Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta create mode 100644 Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs create mode 100644 Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta create mode 100644 Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs create mode 100644 Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta diff --git a/Scripts/Editor/Drawers/Odin.meta b/Scripts/Editor/Drawers/Odin.meta new file mode 100644 index 0000000..c2b0ac9 --- /dev/null +++ b/Scripts/Editor/Drawers/Odin.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 327994a52f523b641898a39ff7500a02 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs b/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs new file mode 100644 index 0000000..84c6d8e --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs @@ -0,0 +1,48 @@ +#if UNITY_EDITOR && ODIN_INSPECTOR +using System; +using System.Collections.Generic; +using System.Reflection; +using Sirenix.OdinInspector.Editor; +using UnityEngine; +using XNode; + +namespace XNodeEditor { + internal class OdinNodeInGraphAttributeProcessor : OdinAttributeProcessor where T : Node { + public override bool CanProcessSelfAttributes(InspectorProperty property) { + return false; + } + + public override bool CanProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member) { + if (!NodeEditor.inNodeEditor) + return false; + + if (member.MemberType == MemberTypes.Field) { + switch (member.Name) { + case "graph": + case "position": + case "ports": + return true; + + default: + break; + } + } + + return false; + } + + public override void ProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member, List attributes) { + switch (member.Name) { + case "graph": + case "position": + case "ports": + attributes.Add(new HideInInspector()); + break; + + default: + break; + } + } + } +} +#endif \ No newline at end of file diff --git a/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta b/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta new file mode 100644 index 0000000..15f6990 --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3cf2561fbfea9a041ac81efbbb5b3e0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs b/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs new file mode 100644 index 0000000..a384bdc --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs @@ -0,0 +1,49 @@ +#if UNITY_EDITOR && ODIN_INSPECTOR +using Sirenix.OdinInspector; +using Sirenix.OdinInspector.Editor; +using Sirenix.Utilities.Editor; +using UnityEngine; +using XNode; + +namespace XNodeEditor { + public class InputAttributeDrawer : OdinAttributeDrawer { + protected override bool CanDrawAttributeProperty(InspectorProperty property) { + Node node = property.Tree.WeakTargets[0] as Node; + return node != null; + } + + protected override void DrawPropertyLayout(GUIContent label) { + Node node = Property.Tree.WeakTargets[0] as Node; + NodePort port = node.GetInputPort(Property.Name); + + if (!NodeEditor.inNodeEditor) { + if (Attribute.backingValue == XNode.Node.ShowBackingValue.Always || Attribute.backingValue == XNode.Node.ShowBackingValue.Unconnected && !port.IsConnected) + CallNextDrawer(label); + return; + } + + if (Property.Tree.WeakTargets.Count > 1) { + SirenixEditorGUI.WarningMessageBox("Cannot draw ports with multiple nodes selected"); + return; + } + + if (port != null) { + var portPropoerty = Property.Tree.GetUnityPropertyForPath(Property.UnityPropertyPath); + if (portPropoerty == null) { + SirenixEditorGUI.ErrorMessageBox("Port property missing at: " + Property.UnityPropertyPath); + return; + } else { + var labelWidth = Property.GetAttribute(); + if (labelWidth != null) + GUIHelper.PushLabelWidth(labelWidth.Width); + + NodeEditorGUILayout.PropertyField(portPropoerty, label == null ? GUIContent.none : label, true, GUILayout.MinWidth(30)); + + if (labelWidth != null) + GUIHelper.PopLabelWidth(); + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta b/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta new file mode 100644 index 0000000..12b7615 --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2fd590b2e9ea0bd49b6986a2ca9010ab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs b/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs new file mode 100644 index 0000000..ff59615 --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs @@ -0,0 +1,49 @@ +#if UNITY_EDITOR && ODIN_INSPECTOR +using Sirenix.OdinInspector; +using Sirenix.OdinInspector.Editor; +using Sirenix.Utilities.Editor; +using UnityEngine; +using XNode; + +namespace XNodeEditor { + public class OutputAttributeDrawer : OdinAttributeDrawer { + protected override bool CanDrawAttributeProperty(InspectorProperty property) { + Node node = property.Tree.WeakTargets[0] as Node; + return node != null; + } + + protected override void DrawPropertyLayout(GUIContent label) { + Node node = Property.Tree.WeakTargets[0] as Node; + NodePort port = node.GetOutputPort(Property.Name); + + if (!NodeEditor.inNodeEditor) { + if (Attribute.backingValue == XNode.Node.ShowBackingValue.Always || Attribute.backingValue == XNode.Node.ShowBackingValue.Unconnected && !port.IsConnected) + CallNextDrawer(label); + return; + } + + if (Property.Tree.WeakTargets.Count > 1) { + SirenixEditorGUI.WarningMessageBox("Cannot draw ports with multiple nodes selected"); + return; + } + + if (port != null) { + var portPropoerty = Property.Tree.GetUnityPropertyForPath(Property.UnityPropertyPath); + if (portPropoerty == null) { + SirenixEditorGUI.ErrorMessageBox("Port property missing at: " + Property.UnityPropertyPath); + return; + } else { + var labelWidth = Property.GetAttribute(); + if (labelWidth != null) + GUIHelper.PushLabelWidth(labelWidth.Width); + + NodeEditorGUILayout.PropertyField(portPropoerty, label == null ? GUIContent.none : label, true, GUILayout.MinWidth(30)); + + if (labelWidth != null) + GUIHelper.PopLabelWidth(); + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta b/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta new file mode 100644 index 0000000..aa22218 --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e7ebd8f2b42e2384aa109551dc46af88 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 631d8e5..8d293ab 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -3,6 +3,11 @@ using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; +#if ODIN_INSPECTOR +using Sirenix.OdinInspector.Editor; +using Sirenix.Utilities; +using Sirenix.Utilities.Editor; +#endif namespace XNodeEditor { /// Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. @@ -10,23 +15,39 @@ namespace XNodeEditor { 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(); +#if ODIN_INSPECTOR + internal static bool inNodeEditor = false; +#endif + public virtual void OnHeaderGUI() { GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); } /// Draws standard field editors for all public fields public virtual void OnBodyGUI() { +#if ODIN_INSPECTOR + inNodeEditor = true; +#endif + // Unity specifically requires this to save/update any serial object. // serializedObject.Update(); must go at the start of an inspector gui, and // serializedObject.ApplyModifiedProperties(); goes at the end. serializedObject.Update(); string[] excludes = { "m_Script", "graph", "position", "ports" }; +#if ODIN_INSPECTOR + InspectorUtilities.BeginDrawPropertyTree(objectTree, true); + GUIHelper.PushLabelWidth(84); + objectTree.Draw(true); + InspectorUtilities.EndDrawPropertyTree(objectTree); + GUIHelper.PopLabelWidth(); +#else + // Iterate through serialized properties and draw them like the Inspector (But with ports) SerializedProperty iterator = serializedObject.GetIterator(); bool enterChildren = true; @@ -36,6 +57,7 @@ namespace XNodeEditor { if (excludes.Contains(iterator.name)) continue; NodeEditorGUILayout.PropertyField(iterator, true); } +#endif // Iterate through dynamic ports and draw them in the order in which they are serialized foreach (XNode.NodePort dynamicPort in target.DynamicPorts) { @@ -44,6 +66,20 @@ namespace XNodeEditor { } serializedObject.ApplyModifiedProperties(); + +#if ODIN_INSPECTOR + // Call repaint so that the graph window elements respond properly to layout changes coming from Odin + if (GUIHelper.RepaintRequested) { + GUIHelper.ClearRepaintRequest(); + window.Repaint(); + } +#else + window.Repaint(); +#endif + +#if ODIN_INSPECTOR + inNodeEditor = false; +#endif } public virtual int GetWidth() { diff --git a/Scripts/Editor/NodeEditorBase.cs b/Scripts/Editor/NodeEditorBase.cs index 261a284..1fc28c7 100644 --- a/Scripts/Editor/NodeEditorBase.cs +++ b/Scripts/Editor/NodeEditorBase.cs @@ -4,6 +4,9 @@ using System.Collections.Generic; using System.Reflection; using UnityEditor; using UnityEngine; +#if ODIN_INSPECTOR +using Sirenix.OdinInspector.Editor; +#endif namespace XNodeEditor.Internal { /// Handles caching of custom editor classes and their target types. Accessible with GetEditor(Type type) @@ -17,6 +20,24 @@ namespace XNodeEditor.Internal { public NodeEditorWindow window; public K target; public SerializedObject serializedObject; +#if ODIN_INSPECTOR + private PropertyTree _objectTree; + public PropertyTree objectTree { + get { + if (this._objectTree == null) { + try { + bool wasInEditor = NodeEditor.inNodeEditor; + NodeEditor.inNodeEditor = true; + this._objectTree = PropertyTree.Create(this.serializedObject); + NodeEditor.inNodeEditor = wasInEditor; + } catch (ArgumentException ex) { + Debug.Log(ex); + } + } + return this._objectTree; + } + } +#endif public static T GetEditor(K target, NodeEditorWindow window) { if (target == null) return null; From c16ad1fff06c7354a72af788c7267ee74a7ae2be Mon Sep 17 00:00:00 2001 From: Simon Rodriguez Date: Tue, 3 Sep 2019 22:18:30 +0200 Subject: [PATCH 43/52] Added functionality that focuses on selected nodes. (#173) Resets view if no nodes are selected. --- Scripts/Editor/NodeEditorAction.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index f0ebec0..1caecd9 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -335,10 +335,17 @@ namespace XNodeEditor { } } - /// Puts all nodes in focus. If no nodes are present, resets view to + /// Puts all selected nodes in focus. If no nodes are present, resets view and zoom to to origin public void Home() { - zoom = 2; - panOffset = Vector2.zero; + var nodes = Selection.objects.Where(o => o is XNode.Node).Cast().ToList(); + if (nodes.Count > 0) { + Vector2 minPos = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); + Vector2 maxPos = nodes.Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero)).Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y))); + panOffset = -(minPos + (maxPos - minPos) / 2f); + } else { + zoom = 2; + panOffset = Vector2.zero; + } } /// Remove nodes in the graph in Selection.objects From b7749e3b999b9af9eeefe11b708a8a18c4e47e52 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Thu, 5 Sep 2019 10:06:10 +0200 Subject: [PATCH 44/52] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52a1c49..9907392 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [Downloads](https://github.com/Siccity/xNode/releases) / [Asset Store](http://u3d.as/108S) / [Documentation](https://github.com/Siccity/xNode/wiki) -[Support Me on Ko-fi](https://ko-fi.com/Z8Z5DYWA) +Support xNode on [Ko-fi](https://ko-fi.com/Z8Z5DYWA) or [Patreon](https://www.patreon.com/thorbrigsted) ### xNode Thinking of developing a node-based plugin? Then this is for you. You can download it as an archive and unpack to a new unity project, or connect it as git submodule. From 5433e368373adbae9d3ced42d54a768456a2dff0 Mon Sep 17 00:00:00 2001 From: MowfaqAlarbi <54871067+MowfaqAlarbi@users.noreply.github.com> Date: Fri, 6 Sep 2019 19:13:00 +0200 Subject: [PATCH 45/52] Show Node Create list When Dragging To Nothing (#183) And a user setting to disable it, of course --- Scripts/Editor/NodeEditorAction.cs | 25 +++++++++++++++++++++++++ Scripts/Editor/NodeEditorPreferences.cs | 2 ++ Scripts/Editor/NodeGraphEditor.cs | 8 +++++--- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 1caecd9..cf52351 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -22,6 +22,7 @@ namespace XNodeEditor { [NonSerialized] private XNode.NodePort hoveredPort = null; [NonSerialized] private XNode.NodePort draggedOutput = null; [NonSerialized] private XNode.NodePort draggedOutputTarget = null; + [NonSerialized] private XNode.NodePort autoConnectOutput = null; [NonSerialized] private List draggedOutputReroutes = new List(); private RerouteReference hoveredReroute = new RerouteReference(); private List selectedReroutes = new List(); @@ -145,8 +146,10 @@ namespace XNodeEditor { if (IsHoveringPort) { if (hoveredPort.IsOutput) { draggedOutput = hoveredPort; + autoConnectOutput = hoveredPort; } else { hoveredPort.VerifyConnections(); + autoConnectOutput = null; if (hoveredPort.IsConnected) { XNode.Node node = hoveredPort.node; XNode.NodePort output = hoveredPort.Connection; @@ -214,6 +217,12 @@ namespace XNodeEditor { EditorUtility.SetDirty(graph); } } + // Open context menu for auto-connection + else if (NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null) { + GenericMenu menu = new GenericMenu(); + graphEditor.AddContextMenuItems(menu); + menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); + } //Release dragged connection draggedOutput = null; draggedOutputTarget = null; @@ -491,5 +500,21 @@ namespace XNodeEditor { Rect windowRect = new Rect(nodePos, new Vector2(width / zoom, 30 / zoom)); return windowRect.Contains(mousePos); } + + /// Attempt to connect dragged output to target node + public void AutoConnect(XNode.Node node) { + if (autoConnectOutput == null) return; + + // Find input port of same type + XNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && x.ValueType == autoConnectOutput.ValueType); + // Fallback to input port + if (inputPort == null) inputPort = node.Ports.FirstOrDefault(x => x.IsInput); + // Autoconnect + if (inputPort != null) autoConnectOutput.Connect(inputPort); + + // Save changes + EditorUtility.SetDirty(graph); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); + } } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index c28c064..9e9bb64 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -32,6 +32,7 @@ namespace XNodeEditor { public Color32 highlightColor = new Color32(255, 255, 255, 255); public bool gridSnap = true; public bool autoSave = true; + public bool dragToCreate = true; public bool zoomToMouse = true; public bool portTooltips = true; [SerializeField] private string typeColorsData = ""; @@ -149,6 +150,7 @@ namespace XNodeEditor { 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); + settings.dragToCreate = EditorGUILayout.Toggle(new GUIContent("Drag to Create", "Drag a port connection anywhere on the grid to create and connect a node"), settings.dragToCreate); if (GUI.changed) { SavePrefs(key, settings); NodeEditorWindow.RepaintAll(); diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index a45d566..bdd4fa0 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -52,7 +52,8 @@ namespace XNodeEditor { if (string.IsNullOrEmpty(path)) continue; menu.AddItem(new GUIContent(path), false, () => { - CreateNode(type, pos); + XNode.Node node = CreateNode(type, pos); + NodeEditorWindow.current.AutoConnect(node); }); } menu.AddSeparator(""); @@ -87,13 +88,14 @@ namespace XNodeEditor { } /// Create a node and save it in the graph asset - public virtual void CreateNode(Type type, Vector2 position) { + public virtual XNode.Node CreateNode(Type type, Vector2 position) { XNode.Node node = target.AddNode(type); node.position = position; if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type); AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); NodeEditorWindow.RepaintAll(); + return node; } /// Creates a copy of the original node in the graph @@ -114,7 +116,7 @@ namespace XNodeEditor { [AttributeUsage(AttributeTargets.Class)] public class CustomNodeGraphEditorAttribute : Attribute, - XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { + XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { private Type inspectedType; public string editorPrefsKey; /// Tells a NodeGraphEditor which Graph type it is an editor for From 305af376c5d3bf5291614764e604e24fc3f06987 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 6 Sep 2019 19:25:29 +0200 Subject: [PATCH 46/52] Added link to documentation in preferences --- Scripts/Editor/NodeEditorPreferences.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index 9e9bb64..b3026b9 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -106,6 +106,9 @@ namespace XNodeEditor { private static void PreferencesGUI() { VerifyLoaded(); Settings settings = NodeEditorPreferences.settings[lastKey]; + + if (GUILayout.Button(new GUIContent("Documentation", "https://github.com/Siccity/xNode/wiki"), GUILayout.Width(100))) Application.OpenURL("https://github.com/Siccity/xNode/wiki"); + EditorGUILayout.Space(); NodeSettingsGUI(lastKey, settings); GridSettingsGUI(lastKey, settings); From c51a3a8f679045055deadc2b8ccf98f4b37ce9f6 Mon Sep 17 00:00:00 2001 From: MowfaqAlarbi <54871067+MowfaqAlarbi@users.noreply.github.com> Date: Sat, 7 Sep 2019 00:57:34 +0200 Subject: [PATCH 47/52] Select All Feature (#184) If anything is selected, A deselects all. If nothing is selected, A selects all. --- Scripts/Editor/NodeEditorAction.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index cf52351..32e512f 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -297,6 +297,17 @@ namespace XNodeEditor { } else { if (e.keyCode == KeyCode.F2) RenameSelectedNode(); } + if (e.keyCode == KeyCode.A) { + if (Selection.objects.Any(x => graph.nodes.Contains(x))) { + foreach (XNode.Node node in graph.nodes) { + DeselectNode(node); + } + } else { + foreach (XNode.Node node in graph.nodes) { + SelectNode(node, true); + } + } + } break; case EventType.ValidateCommand: case EventType.ExecuteCommand: From 1254836a84406ea002e97f0cae4e39fdf07f6ca5 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 7 Sep 2019 04:00:48 +0200 Subject: [PATCH 48/52] .NET 3.5 compatability --- 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 32e512f..02cb599 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -298,7 +298,7 @@ namespace XNodeEditor { if (e.keyCode == KeyCode.F2) RenameSelectedNode(); } if (e.keyCode == KeyCode.A) { - if (Selection.objects.Any(x => graph.nodes.Contains(x))) { + if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node))) { foreach (XNode.Node node in graph.nodes) { DeselectNode(node); } From 4e1b2e1fe60baf22309fd5eaaecf42005bc2a6d7 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 7 Sep 2019 04:03:00 +0200 Subject: [PATCH 49/52] Fixed warning --- Scripts/Editor/NodeEditorGUILayout.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index b8e2019..ec93cc1 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -319,7 +319,6 @@ namespace XNodeEditor { XNode.NodePort port = node.GetPort(fieldName + " " + index); if (hasArrayData) { if (arrayData.arraySize <= index) { - string portInfo = port != null ? port.fieldName : ""; EditorGUI.LabelField(rect, "Array[" + index + "] data out of range"); return; } From 445a47cd1db3b982aeb36149b34cea343ff02c2f Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 9 Sep 2019 00:44:36 +0200 Subject: [PATCH 50/52] Autoconnect bugfix 1. Add a node 2. Drag a port and create a new node that it connects to 3. Delete that latest node 4. Create it with just right click, the first node will autoconnect to it --- Scripts/Editor/NodeEditorAction.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 02cb599..35fbdd1 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -274,11 +274,13 @@ namespace XNodeEditor { ShowPortContextMenu(hoveredPort); } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false); + autoConnectOutput = null; GenericMenu menu = new GenericMenu(); 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) { + autoConnectOutput = null; GenericMenu menu = new GenericMenu(); graphEditor.AddContextMenuItems(menu); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); @@ -526,6 +528,7 @@ namespace XNodeEditor { // Save changes EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); + autoConnectOutput = null; } } } \ No newline at end of file From f86b2b6d42c9f218f54cd53233fb897e08fd2ef3 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 14 Sep 2019 16:09:01 +0200 Subject: [PATCH 51/52] Create FUNDING.yml --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..2cd331a --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: thorbrigsted +open_collective: # Replace with a single Open Collective username +ko_fi: thorbrigsted +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From 979bd5f7cf1273f80718a1590db07d0e6217854e Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 23 Sep 2019 23:25:23 +0200 Subject: [PATCH 52/52] Added nodeGraphEditor.GetNoodleColor Now you can override the color of noodles! --- Scripts/Editor/NodeEditorGUI.cs | 8 ++++---- Scripts/Editor/NodeGraphEditor.cs | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 9954325..c24205e 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -202,11 +202,11 @@ namespace XNodeEditor { Rect fromRect; if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue; - Color connectionColor = graphEditor.GetPortColor(output); - for (int k = 0; k < output.ConnectionCount; k++) { XNode.NodePort input = output.GetConnection(k); + Color noodleColor = graphEditor.GetNoodleColor(output, input); + // Error handling if (input == null) continue; //If a script has been updated and the port doesn't exist, it is removed and null is returned. If this happens, return. if (!input.IsConnectedTo(output)) input.Connect(output); @@ -219,7 +219,7 @@ namespace XNodeEditor { gridPoints.Add(fromRect.center); gridPoints.AddRange(reroutePoints); gridPoints.Add(toRect.center); - DrawNoodle(connectionColor, gridPoints); + DrawNoodle(noodleColor, gridPoints); // Loop through reroute points again and draw the points for (int i = 0; i < reroutePoints.Count; i++) { @@ -235,7 +235,7 @@ namespace XNodeEditor { GUI.DrawTexture(rect, NodeEditorResources.dotOuter); } - GUI.color = connectionColor; + GUI.color = noodleColor; GUI.DrawTexture(rect, NodeEditorResources.dot); if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef); if (rect.Contains(mousePos)) hoveredReroute = rerouteRef; diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index bdd4fa0..431ebb2 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -63,14 +63,22 @@ namespace XNodeEditor { menu.AddCustomContextMenuItems(target); } + /// Returned color is used to color noodles + public virtual Color GetNoodleColor(XNode.NodePort output, XNode.NodePort input) { + return GetTypeColor(output.ValueType); + } + + /// Returned color is used to color ports public virtual Color GetPortColor(XNode.NodePort port) { return GetTypeColor(port.ValueType); } + /// Returns generated color for a type. This color is editable in preferences public virtual Color GetTypeColor(Type type) { return NodeEditorPreferences.GetTypeColor(type); } + /// Override to display custom tooltips public virtual string GetPortTooltip(XNode.NodePort port) { Type portType = port.ValueType; string tooltip = "";