From aa04de987057e0ad9e4ed97aa51cf1bddb554fc0 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 25 Mar 2018 11:40:23 +0200 Subject: [PATCH 01/18] Added CONTRIBUTING.md.meta to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 46cc792..68af404 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ sysinfo.txt /Examples/ README.md.meta LICENSE.md.meta +CONTRIBUTING.md.meta \ No newline at end of file From 6d6a6abd9edb7ac200b6ea302a02324a359f781d Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 25 Mar 2018 12:51:02 +0200 Subject: [PATCH 02/18] Added support for usage from a DLL #23 --- Scripts/Editor/NodeEditorReflection.cs | 23 +++++++++++++---------- Scripts/NodeDataCache.cs | 22 ++++++++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index d1f3c0a..6cdbc42 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -11,10 +11,12 @@ namespace XNodeEditor { 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; + + [NonSerialized] private static Dictionary _nodeTint; /// All available node types public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } } - [NonSerialized] private static Type[] _nodeTypes = null; + + [NonSerialized] private static Type[] _nodeTypes = null; public static Type[] GetNodeTypes() { //Get all classes deriving from Node via reflection @@ -32,13 +34,14 @@ namespace XNodeEditor { return tints; } + /// Get all classes deriving from baseType via reflection public static Type[] GetDerivedTypes(Type baseType) { - //Get all classes deriving from baseType via reflection - Assembly assembly = Assembly.GetAssembly(baseType); - return assembly.GetTypes().Where(t => - !t.IsAbstract && - baseType.IsAssignableFrom(t) - ).ToArray(); + List types = new List(); + System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); + foreach (Assembly assembly in assemblies) { + types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); + } + return types.ToArray(); } public static object ObjectFromType(Type type) { @@ -71,10 +74,10 @@ namespace XNodeEditor { kvp.Add(new KeyValuePair(attribs[k], methods[i])); } } - #if UNITY_5_5_OR_NEWER +#if UNITY_5_5_OR_NEWER //Sort menu items kvp.Sort((x, y) => x.Key.priority.CompareTo(y.Key.priority)); - #endif +#endif return kvp.ToArray(); } diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index acffe69..23507a6 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -46,13 +46,19 @@ namespace XNode { private static void BuildCache() { portDataCache = new PortDataCache(); System.Type baseType = typeof(Node); - Assembly assembly = Assembly.GetAssembly(baseType); - System.Type[] nodeTypes = assembly.GetTypes().Where(t => - !t.IsAbstract && - baseType.IsAssignableFrom(t) - ).ToArray(); - - for (int i = 0; i < nodeTypes.Length; i++) { + List nodeTypes = new List(); + System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); + Assembly selfAssembly = Assembly.GetAssembly(baseType); + if (selfAssembly.FullName.StartsWith("Assembly-CSharp")) { + // 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 DDLs (slow) + foreach (Assembly assembly in assemblies) { + nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); + } + } + for (int i = 0; i < nodeTypes.Count; i++) { CachePorts(nodeTypes[i]); } } @@ -103,4 +109,4 @@ namespace XNode { } } } -} +} \ No newline at end of file From 1fff90cbf2c745f4c35bd65ca1e4be341521bc5b Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Wed, 28 Mar 2018 00:55:53 +0200 Subject: [PATCH 03/18] Added 'Clear Connections' right-click option for NodePorts --- Scripts/Editor/NodeEditorAction.cs | 2 ++ Scripts/Editor/NodeEditorGUI.cs | 8 ++++++++ Scripts/Node.cs | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 7b43574..c1f7a40 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -159,6 +159,8 @@ namespace XNodeEditor { currentActivity = NodeActivity.Idle; } else if (e.button == 1) { if (!isPanning) { + if (IsHoveringPort) + ShowPortContextMenu(hoveredPort); if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false); ShowNodeContextMenu(); diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 4db162b..f5fd919 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -87,6 +87,14 @@ namespace XNodeEditor { return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width)); } + /// Show right-click context menu for hovered port + void ShowPortContextMenu(XNode.NodePort hoveredPort) { + GenericMenu contextMenu = new GenericMenu(); + contextMenu.AddItem(new GUIContent("Clear Connections"), false, () => hoveredPort.ClearConnections()); + contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); + AssetDatabase.SaveAssets(); + } + /// Show right-click context menu for selected nodes public void ShowNodeContextMenu() { GenericMenu contextMenu = new GenericMenu(); diff --git a/Scripts/Node.cs b/Scripts/Node.cs index ddc43ea..099c56e 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -127,7 +127,7 @@ namespace XNode { } /// Removes all instance ports from the node - [ContextMenu("Clear instance ports")] + [ContextMenu("Clear Instance Ports")] public void ClearInstancePorts() { List instancePorts = new List(InstancePorts); foreach (NodePort port in instancePorts) { From 941189a4d67f77c1fdb840a4148fe676e6c8b7f9 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 31 Mar 2018 19:01:36 +0200 Subject: [PATCH 04/18] Started work on Reroute nodes --- Scripts/Editor/NodeEditorAction.cs | 138 +++++++++++++++++++++----- Scripts/Editor/NodeEditorGUI.cs | 46 ++++++++- Scripts/Editor/NodeEditorGUILayout.cs | 2 +- Scripts/Editor/NodeEditorWindow.cs | 8 +- Scripts/NodeGraph.cs | 5 + 5 files changed, 167 insertions(+), 32 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index c1f7a40..df0af09 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -5,7 +5,7 @@ using UnityEngine; namespace XNodeEditor { public partial class NodeEditorWindow { - public enum NodeActivity { Idle, HoldHeader, DragHeader, HoldGrid, DragGrid } + public enum NodeActivity { Idle, HoldNode, DragNode, HoldGrid, DragGrid } public static NodeActivity currentActivity = NodeActivity.Idle; public static bool isPanning { get; private set; } public static Vector2[] dragOffset; @@ -13,13 +13,17 @@ namespace XNodeEditor { private bool IsDraggingPort { get { return draggedOutput != null; } } private bool IsHoveringPort { get { return hoveredPort != null; } } private bool IsHoveringNode { get { return hoveredNode != null; } } + private bool IsHoveringReroute { get { return hoveredReroute >= 0; } } private XNode.Node hoveredNode = null; [NonSerialized] private XNode.NodePort hoveredPort = null; [NonSerialized] private XNode.NodePort draggedOutput = null; [NonSerialized] private XNode.NodePort draggedOutputTarget = null; + private int hoveredReroute = -1; + private List selectedReroutes = new List(); private Rect nodeRects; private Vector2 dragBoxStart; private UnityEngine.Object[] preBoxSelection; + private int[] preBoxSelectionReroute; public void Controls() { wantsMouseMove = true; @@ -42,32 +46,47 @@ namespace XNodeEditor { draggedOutputTarget = null; } Repaint(); - } else if (currentActivity == NodeActivity.HoldHeader || currentActivity == NodeActivity.DragHeader) { + } else if (currentActivity == NodeActivity.HoldNode) { + RecalculateDragOffsets(e); + currentActivity = NodeActivity.DragNode; + Repaint(); + } + if (currentActivity == NodeActivity.DragNode) { + // Holding ctrl inverts grid snap + bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap; + if (e.control) gridSnap = !gridSnap; + + Vector2 mousePos = WindowToGridPosition(e.mousePosition); + // Move selected nodes with offset for (int i = 0; i < Selection.objects.Length; i++) { if (Selection.objects[i] is XNode.Node) { XNode.Node node = Selection.objects[i] as XNode.Node; - node.position = WindowToGridPosition(e.mousePosition) + dragOffset[i]; - bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap; - if (e.control) { - gridSnap = !gridSnap; - } + node.position = mousePos + dragOffset[i]; if (gridSnap) { node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8; node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8; } } } - currentActivity = NodeActivity.DragHeader; + // Move selected reroutes with offset + for (int i = 0; i < selectedReroutes.Count; i++) { + Vector2 pos = mousePos + dragOffset[Selection.objects.Length + i]; + pos.x -= 8; + pos.y -= 8; + if (gridSnap) { + pos.x = (Mathf.Round((pos.x + 8) / 16) * 16); + pos.y = (Mathf.Round((pos.y + 8) / 16) * 16); + } + SetReroute(selectedReroutes[i], pos); + } Repaint(); } else if (currentActivity == NodeActivity.HoldGrid) { currentActivity = NodeActivity.DragGrid; preBoxSelection = Selection.objects; + preBoxSelectionReroute = selectedReroutes.ToArray(); dragBoxStart = WindowToGridPosition(e.mousePosition); Repaint(); } else if (currentActivity == NodeActivity.DragGrid) { - foreach (XNode.Node node in graph.nodes) { - - } Repaint(); } } else if (e.button == 1 || e.button == 2) { @@ -83,7 +102,6 @@ namespace XNodeEditor { case EventType.MouseDown: Repaint(); if (e.button == 0) { - if (IsHoveringPort) { if (hoveredPort.IsOutput) { draggedOutput = hoveredPort; @@ -100,22 +118,36 @@ namespace XNodeEditor { } } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { // If mousedown on node header, select or deselect - if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, e.control || e.shift); - else if (e.control || e.shift) DeselectNode(hoveredNode); + if (!Selection.Contains(hoveredNode)) { + SelectNode(hoveredNode, e.control || e.shift); + if (!e.control && !e.shift) selectedReroutes.Clear(); + } else if (e.control || e.shift) DeselectNode(hoveredNode); e.Use(); - currentActivity = NodeActivity.HoldHeader; - dragOffset = new Vector2[Selection.objects.Length]; - for (int i = 0; i < dragOffset.Length; i++) { - if (Selection.objects[i] is XNode.Node) { - XNode.Node node = Selection.objects[i] as XNode.Node; - dragOffset[i] = node.position - WindowToGridPosition(e.mousePosition); + currentActivity = NodeActivity.HoldNode; + } else if (IsHoveringReroute) { + // If reroute isn't selected + if (!selectedReroutes.Contains(hoveredReroute)) { + // Add it + if (e.control || e.shift) selectedReroutes.Add(hoveredReroute); + // Select it + else { + selectedReroutes = new List() { hoveredReroute }; + Selection.activeObject = null; } + } + // Deselect + else if (e.control || e.shift) selectedReroutes.RemoveAt(hoveredReroute); + e.Use(); + currentActivity = NodeActivity.HoldNode; } // If mousedown on grid background, deselect all else if (!IsHoveringNode) { currentActivity = NodeActivity.HoldGrid; - if (!e.control && !e.shift) Selection.activeObject = null; + if (!e.control && !e.shift) { + selectedReroutes.Clear(); + Selection.activeObject = null; + } } } break; @@ -135,7 +167,7 @@ namespace XNodeEditor { draggedOutputTarget = null; EditorUtility.SetDirty(graph); AssetDatabase.SaveAssets(); - } else if (currentActivity == NodeActivity.DragHeader) { + } else if (currentActivity == NodeActivity.DragNode) { AssetDatabase.SaveAssets(); } else if (!IsHoveringNode) { // If click outside node, release field focus @@ -150,18 +182,27 @@ namespace XNodeEditor { AssetDatabase.SaveAssets(); } - // If click node header, select single node. - if (currentActivity == NodeActivity.HoldHeader && !(e.control || e.shift)) { + // If click node header, select it. + if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift)) { + selectedReroutes.Clear(); SelectNode(hoveredNode, false); } + // If click reroute, select it. + if (IsHoveringReroute && !(e.control || e.shift)) { + selectedReroutes = new List() { hoveredReroute }; + Selection.activeObject = null; + } + Repaint(); currentActivity = NodeActivity.Idle; } else if (e.button == 1) { if (!isPanning) { - if (IsHoveringPort) + if (IsHoveringReroute) { + ShowRerouteContextMenu(hoveredReroute); + } else if (IsHoveringPort) { ShowPortContextMenu(hoveredPort); - if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { + } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false); ShowNodeContextMenu(); } else if (!IsHoveringNode) { @@ -190,6 +231,22 @@ namespace XNodeEditor { } } + private void RecalculateDragOffsets(Event current) { + dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count]; + // Selected nodes + for (int i = 0; i < Selection.objects.Length; i++) { + if (Selection.objects[i] is XNode.Node) { + XNode.Node node = Selection.objects[i] as XNode.Node; + dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition); + } + } + + // Selected reroutes + for (int i = 0; i < selectedReroutes.Count; i++) { + dragOffset[Selection.objects.Length + i] = GetReroutePos(selectedReroutes[i]) - WindowToGridPosition(current.mousePosition); + } + } + /// Puts all nodes in focus. If no nodes are present, resets view to public void Home() { zoom = 2; @@ -253,6 +310,35 @@ namespace XNodeEditor { Selection.objects = newNodes; } + /// Add a reroute node to a graph and return index + public int AddReroute(Vector2 position) { + SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); + reroutes.arraySize++; + reroutes.GetArrayElementAtIndex(reroutes.arraySize - 1).vector2Value = position; + graphEditor.serializedObject.ApplyModifiedProperties(); + return reroutes.arraySize - 1; + } + + /// Set the position of a reroute node + public void SetReroute(int index, Vector2 position) { + SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); + reroutes.GetArrayElementAtIndex(index).vector2Value = position; + graphEditor.serializedObject.ApplyModifiedProperties(); + } + + /// Get the position of a reroute node + public Vector2 GetReroutePos(int index) { + SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); + return reroutes.GetArrayElementAtIndex(index).vector2Value; + } + + /// Remove a reroute node + public void RemoveReroute(int index) { + SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); + reroutes.DeleteArrayElementAtIndex(index); + graphEditor.serializedObject.ApplyModifiedProperties(); + } + /// Draw a connection as we are dragging it public void DrawDraggedConnection() { if (IsDraggingPort) { diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index f5fd919..9cc4b1c 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -20,8 +20,9 @@ namespace XNodeEditor { DrawGrid(position, zoom, panOffset); DrawConnections(); DrawDraggedConnection(); + DrawReroutes(); DrawNodes(); - DrawBox(); + DrawSelectionBox(); DrawTooltip(); GUI.matrix = m; @@ -72,7 +73,7 @@ namespace XNodeEditor { GUI.DrawTextureWithTexCoords(rect, crossTex, new Rect(tileOffset + new Vector2(0.5f, 0.5f), tileAmount)); } - public void DrawBox() { + public void DrawSelectionBox() { if (currentActivity == NodeActivity.DragGrid) { Vector2 curPos = WindowToGridPosition(Event.current.mousePosition); Vector2 size = curPos - dragBoxStart; @@ -87,6 +88,14 @@ namespace XNodeEditor { return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width)); } + /// Show right-click context menu for hovered reroute + void ShowRerouteContextMenu(int reroute) { + GenericMenu contextMenu = new GenericMenu(); + contextMenu.AddItem(new GUIContent("Remove"), false, () => RemoveReroute(reroute)); + contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); + AssetDatabase.SaveAssets(); + } + /// Show right-click context menu for hovered port void ShowPortContextMenu(XNode.NodePort hoveredPort) { GenericMenu contextMenu = new GenericMenu(); @@ -138,6 +147,7 @@ namespace XNodeEditor { }); } contextMenu.AddSeparator(""); + contextMenu.AddItem(new GUIContent("Reroute"), false, () => AddReroute(pos)); contextMenu.AddItem(new GUIContent("Preferences"), false, () => OpenPreferences()); AddCustomContextMenuItems(contextMenu, graph); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); @@ -229,6 +239,34 @@ namespace XNodeEditor { } } + /// Draws all connections + public void DrawReroutes() { + Vector2 mousePos = Event.current.mousePosition; + SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); + + // Box selection support + List selection = preBoxSelectionReroute != null ? new List(preBoxSelectionReroute) : new List(); + Vector2 boxStartPos = GridToWindowPosition(dragBoxStart); + Vector2 boxSize = mousePos - boxStartPos; + if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } + if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); } + Rect boxRect = new Rect(boxStartPos, boxSize); + + hoveredReroute = -1; + for (int i = 0; i < reroutes.arraySize; i++) { + Rect rect = new Rect(reroutes.GetArrayElementAtIndex(i).vector2Value, new Vector2(16, 16)); + rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); + rect = GridToWindowRect(rect); + Color bgcol = Color.black; + if (selectedReroutes.Contains(i)) bgcol = Color.yellow; + NodeEditorGUILayout.DrawPortHandle(rect, bgcol, Color.white); + + if (rect.Overlaps(boxRect)) selection.Add(i); + if (rect.Contains(mousePos)) hoveredReroute = i; + } + if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = new List(selection); + } + private void DrawNodes() { Event e = Event.current; if (e.type == EventType.Layout) { @@ -340,14 +378,14 @@ namespace XNodeEditor { foreach (XNode.NodePort input in node.Inputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(input)) continue; - Rect r = GridToWindowRect(portConnectionPoints[input]); + Rect r = GridToWindowRectNoClipped(portConnectionPoints[input]); if (r.Contains(mousePos)) hoveredPort = input; } //Check all output ports foreach (XNode.NodePort output in node.Outputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(output)) continue; - Rect r = GridToWindowRect(portConnectionPoints[output]); + Rect r = GridToWindowRectNoClipped(portConnectionPoints[output]); if (r.Contains(mousePos)) hoveredPort = output; } } diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index d173abf..8a629e8 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -128,7 +128,7 @@ namespace XNodeEditor { else NodeEditor.portPositions.Add(port, portPos); } - private static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) { + public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) { Color col = GUI.color; GUI.color = backgroundColor; GUI.DrawTexture(rect, NodeEditorResources.dotOuter); diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index cf35616..67d7e73 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -65,11 +65,17 @@ namespace XNodeEditor { return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition / zoom); } - public Rect GridToWindowRect(Rect gridRect) { + public Rect GridToWindowRectNoClipped(Rect gridRect) { gridRect.position = GridToWindowPositionNoClipped(gridRect.position); return gridRect; } + public Rect GridToWindowRect(Rect gridRect) { + gridRect.position = GridToWindowPosition(gridRect.position); + gridRect.size /= zoom; + return gridRect; + } + public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) { Vector2 center = position.size * 0.5f; float xOffset = (center.x * zoom + (panOffset.x + gridPosition.x)); diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index c2092e0..399ea74 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -11,6 +11,11 @@ namespace XNode { /// See: [SerializeField] public List nodes = new List(); +#if UNITY_EDITOR + /// Nodes used primarily for organization (Editor only) + public List reroutes = new List(); +#endif + /// Add a node to the graph by type public T AddNode() where T : Node { return AddNode(typeof(T)) as T; From 3d7dbbc7e2309464124b23734de30d39a3fffe31 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 1 Apr 2018 00:15:25 +0200 Subject: [PATCH 05/18] Fix error on deleting script during compilation --- Scripts/Editor/NodeEditorAssetModProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs b/Scripts/Editor/NodeEditorAssetModProcessor.cs index 61a2518..bd76116 100644 --- a/Scripts/Editor/NodeEditorAssetModProcessor.cs +++ b/Scripts/Editor/NodeEditorAssetModProcessor.cs @@ -17,7 +17,7 @@ namespace XNodeEditor { // Check script type. Return if deleting a non-node script UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; System.Type scriptType = script.GetClass (); - if (scriptType != typeof (XNode.Node) && !scriptType.IsSubclassOf (typeof (XNode.Node))) return AssetDeleteResult.DidNotDelete; + if (scriptType == null || (scriptType != typeof (XNode.Node) && !scriptType.IsSubclassOf (typeof (XNode.Node)))) return AssetDeleteResult.DidNotDelete; // Find all ScriptableObjects using this script string[] guids = AssetDatabase.FindAssets ("t:" + scriptType); From 0132c16448a797905ae29dcd59fcff0ff23ac872 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 1 Apr 2018 01:06:06 +0200 Subject: [PATCH 06/18] Fixed deselect out of range exception --- 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 df0af09..bd3661a 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -137,7 +137,7 @@ namespace XNodeEditor { } // Deselect - else if (e.control || e.shift) selectedReroutes.RemoveAt(hoveredReroute); + else if (e.control || e.shift) selectedReroutes.Remove(hoveredReroute); e.Use(); currentActivity = NodeActivity.HoldNode; } From 34e195e33d90e0b4848401885242f06bd5fb7a34 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 1 Apr 2018 02:15:47 +0200 Subject: [PATCH 07/18] Visual representation in DrawConnections --- Scripts/Editor/NodeEditorGUI.cs | 23 +++++++++++++++++++---- Scripts/NodeGraph.cs | 7 ++----- Scripts/NodePort.cs | 8 ++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 9cc4b1c..a09588f 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -217,6 +217,8 @@ namespace XNodeEditor { /// Draws all connections public void DrawConnections() { + List drawnReroutes = new List(); + foreach (XNode.Node node in graph.nodes) { //If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset. if (node == null) continue; @@ -224,16 +226,29 @@ namespace XNodeEditor { foreach (XNode.NodePort output in node.Outputs) { //Needs cleanup. Null checks are ugly if (!portConnectionPoints.ContainsKey(output)) continue; + + Color connectionColor = graphEditor.GetTypeColor(output.ValueType); + Vector2 from = _portConnectionPoints[output].center; for (int k = 0; k < output.ConnectionCount; k++) { - XNode.NodePort input = output.GetConnection(k); + + // 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); if (!_portConnectionPoints.ContainsKey(input)) continue; - Vector2 to = _portConnectionPoints[input].center; - Color connectionColor = graphEditor.GetTypeColor(output.ValueType); - DrawConnection(from, to, connectionColor); + + Vector2 to = Vector2.zero; + int[] rerouteIndices = output.GetReroutes(k); + for (int i = 0; i < rerouteIndices.Length + 1; i++) { + if (i != rerouteIndices.Length) to = graph.reroutes[rerouteIndices[i]]; + else to = _portConnectionPoints[input].center; + DrawConnection(from, to, connectionColor); + from = to; + + if (drawnReroutes.Contains(i)) break; + else drawnReroutes.Add(i); + } } } } diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index 399ea74..f7d70d0 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -10,11 +10,8 @@ namespace XNode { /// All nodes in the graph. /// See: [SerializeField] public List nodes = new List(); - -#if UNITY_EDITOR - /// Nodes used primarily for organization (Editor only) - public List reroutes = new List(); -#endif + /// Nodes used primarily for organization + [SerializeField] public List reroutes = new List(); /// Add a node to the graph by type public T AddNode() where T : Node { diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index b609066..299559e 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -250,6 +250,12 @@ namespace XNode { } } + /// Get reroute indices. This is used for graph organization purposes + /// Connection index + public int[] GetReroutes(int i) { + return connections[i].reroutes; + } + /// Swap connected nodes from the old list with nodes from the new list public void Redirect(List oldNodes, List newNodes) { foreach (PortConnection connection in connections) { @@ -263,6 +269,8 @@ namespace XNode { [SerializeField] public string fieldName; [SerializeField] public Node node; public NodePort Port { get { return port != null ? port : port = GetPort(); } } + /// Used for organization + [SerializeField] public int[] reroutes = new int[0]; [NonSerialized] private NodePort port; From 33727a1d98ac37c950fe873679c2c65edfabaf76 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 1 Apr 2018 03:12:08 +0200 Subject: [PATCH 08/18] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e9db741..e0544a7 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,10 @@ With a minimal footprint, it is ideal as a base for custom state machines, dialo * Does not rely on any 3rd party plugins * Custom node inspector code is very similar to regular custom inspector code +### Wiki +* [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 + ### Node example: ```csharp [System.Serializable] From 3e68635735b0c7788fc7e75e48590e7a23167285 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sun, 1 Apr 2018 21:42:44 +0200 Subject: [PATCH 09/18] Finished Reroutes --- Scripts/Editor/NodeEditorAction.cs | 110 ++++++++++++++++++----------- Scripts/Editor/NodeEditorGUI.cs | 82 ++++++++++----------- Scripts/NodeGraph.cs | 2 - Scripts/NodePort.cs | 19 +++-- 4 files changed, 118 insertions(+), 95 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index bd3661a..4c20270 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -13,17 +13,36 @@ namespace XNodeEditor { private bool IsDraggingPort { get { return draggedOutput != null; } } private bool IsHoveringPort { get { return hoveredPort != null; } } private bool IsHoveringNode { get { return hoveredNode != null; } } - private bool IsHoveringReroute { get { return hoveredReroute >= 0; } } + private bool IsHoveringReroute { get { return hoveredReroute.port != null; } } private XNode.Node hoveredNode = null; [NonSerialized] private XNode.NodePort hoveredPort = null; [NonSerialized] private XNode.NodePort draggedOutput = null; [NonSerialized] private XNode.NodePort draggedOutputTarget = null; - private int hoveredReroute = -1; - private List selectedReroutes = new List(); + [NonSerialized] private List draggedOutputReroutes = new List(); + private RerouteReference hoveredReroute = new RerouteReference(); + private List selectedReroutes = new List(); private Rect nodeRects; private Vector2 dragBoxStart; private UnityEngine.Object[] preBoxSelection; - private int[] preBoxSelectionReroute; + private RerouteReference[] preBoxSelectionReroute; + private Rect selectionBox; + + 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; @@ -77,7 +96,7 @@ namespace XNodeEditor { pos.x = (Mathf.Round((pos.x + 8) / 16) * 16); pos.y = (Mathf.Round((pos.y + 8) / 16) * 16); } - SetReroute(selectedReroutes[i], pos); + selectedReroutes[i].SetPoint(pos); } Repaint(); } else if (currentActivity == NodeActivity.HoldGrid) { @@ -87,6 +106,11 @@ namespace XNodeEditor { dragBoxStart = WindowToGridPosition(e.mousePosition); Repaint(); } else if (currentActivity == NodeActivity.DragGrid) { + Vector2 boxStartPos = GridToWindowPosition(dragBoxStart); + Vector2 boxSize = e.mousePosition - boxStartPos; + if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } + if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); } + selectionBox = new Rect(boxStartPos, boxSize); Repaint(); } } else if (e.button == 1 || e.button == 2) { @@ -102,6 +126,8 @@ namespace XNodeEditor { case EventType.MouseDown: Repaint(); if (e.button == 0) { + draggedOutputReroutes.Clear(); + if (IsHoveringPort) { if (hoveredPort.IsOutput) { draggedOutput = hoveredPort; @@ -110,6 +136,8 @@ namespace XNodeEditor { if (hoveredPort.IsConnected) { XNode.Node node = hoveredPort.node; XNode.NodePort output = hoveredPort.Connection; + int outputConnectionIndex = output.GetConnectionIndex(hoveredPort); + draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex); hoveredPort.Disconnect(output); draggedOutput = output; draggedOutputTarget = hoveredPort; @@ -131,7 +159,7 @@ namespace XNodeEditor { if (e.control || e.shift) selectedReroutes.Add(hoveredReroute); // Select it else { - selectedReroutes = new List() { hoveredReroute }; + selectedReroutes = new List() { hoveredReroute }; Selection.activeObject = null; } @@ -159,6 +187,8 @@ namespace XNodeEditor { if (draggedOutputTarget != null) { XNode.Node node = draggedOutputTarget.node; if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget); + int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget); + draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes); if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); EditorUtility.SetDirty(graph); } @@ -190,7 +220,7 @@ namespace XNodeEditor { // If click reroute, select it. if (IsHoveringReroute && !(e.control || e.shift)) { - selectedReroutes = new List() { hoveredReroute }; + selectedReroutes = new List() { hoveredReroute }; Selection.activeObject = null; } @@ -198,7 +228,12 @@ namespace XNodeEditor { currentActivity = NodeActivity.Idle; } else if (e.button == 1) { if (!isPanning) { - if (IsHoveringReroute) { + if (IsDraggingPort) { + draggedOutputReroutes.Add(WindowToGridPosition(e.mousePosition)); + } else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null && selectedReroutes.Count == 1) { + selectedReroutes[0].InsertPoint(selectedReroutes[0].GetPoint()); + selectedReroutes[0] = new RerouteReference(selectedReroutes[0].port, selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1); + } else if (IsHoveringReroute) { ShowRerouteContextMenu(hoveredReroute); } else if (IsHoveringPort) { ShowPortContextMenu(hoveredPort); @@ -243,7 +278,7 @@ namespace XNodeEditor { // Selected reroutes for (int i = 0; i < selectedReroutes.Count; i++) { - dragOffset[Selection.objects.Length + i] = GetReroutePos(selectedReroutes[i]) - WindowToGridPosition(current.mousePosition); + dragOffset[Selection.objects.Length + i] = selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition); } } @@ -310,44 +345,37 @@ namespace XNodeEditor { Selection.objects = newNodes; } - /// Add a reroute node to a graph and return index - public int AddReroute(Vector2 position) { - SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); - reroutes.arraySize++; - reroutes.GetArrayElementAtIndex(reroutes.arraySize - 1).vector2Value = position; - graphEditor.serializedObject.ApplyModifiedProperties(); - return reroutes.arraySize - 1; - } - - /// Set the position of a reroute node - public void SetReroute(int index, Vector2 position) { - SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); - reroutes.GetArrayElementAtIndex(index).vector2Value = position; - graphEditor.serializedObject.ApplyModifiedProperties(); - } - - /// Get the position of a reroute node - public Vector2 GetReroutePos(int index) { - SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); - return reroutes.GetArrayElementAtIndex(index).vector2Value; - } - - /// Remove a reroute node - public void RemoveReroute(int index) { - SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); - reroutes.DeleteArrayElementAtIndex(index); - graphEditor.serializedObject.ApplyModifiedProperties(); - } - /// Draw a connection as we are dragging it public void DrawDraggedConnection() { if (IsDraggingPort) { - if (!_portConnectionPoints.ContainsKey(draggedOutput)) return; - Vector2 from = _portConnectionPoints[draggedOutput].center; - Vector2 to = draggedOutputTarget != null ? portConnectionPoints[draggedOutputTarget].center : WindowToGridPosition(Event.current.mousePosition); Color col = NodeEditorPreferences.GetTypeColor(draggedOutput.ValueType); + + if (!_portConnectionPoints.ContainsKey(draggedOutput)) return; col.a = 0.6f; + Vector2 from = _portConnectionPoints[draggedOutput].center; + Vector2 to = Vector2.zero; + for (int i = 0; i < draggedOutputReroutes.Count; i++) { + to = draggedOutputReroutes[i]; + DrawConnection(from, to, col); + from = to; + } + to = draggedOutputTarget != null ? portConnectionPoints[draggedOutputTarget].center : WindowToGridPosition(Event.current.mousePosition); DrawConnection(from, to, col); + + Color bgcol = Color.black; + Color frcol = col; + bgcol.a = 0.6f; + frcol.a = 0.6f; + + // Loop through reroute points again and draw the points + for (int i = 0; i < draggedOutputReroutes.Count; i++) { + // Draw reroute point at position + Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16)); + rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); + rect = GridToWindowRect(rect); + + NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol); + } } } diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index a09588f..2ff98ec 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -20,7 +20,6 @@ namespace XNodeEditor { DrawGrid(position, zoom, panOffset); DrawConnections(); DrawDraggedConnection(); - DrawReroutes(); DrawNodes(); DrawSelectionBox(); DrawTooltip(); @@ -89,9 +88,9 @@ namespace XNodeEditor { } /// Show right-click context menu for hovered reroute - void ShowRerouteContextMenu(int reroute) { + void ShowRerouteContextMenu(RerouteReference reroute) { GenericMenu contextMenu = new GenericMenu(); - contextMenu.AddItem(new GUIContent("Remove"), false, () => RemoveReroute(reroute)); + contextMenu.AddItem(new GUIContent("Remove"), false, () => reroute.RemovePoint()); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); AssetDatabase.SaveAssets(); } @@ -147,7 +146,6 @@ namespace XNodeEditor { }); } contextMenu.AddSeparator(""); - contextMenu.AddItem(new GUIContent("Reroute"), false, () => AddReroute(pos)); contextMenu.AddItem(new GUIContent("Preferences"), false, () => OpenPreferences()); AddCustomContextMenuItems(contextMenu, graph); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); @@ -217,19 +215,21 @@ namespace XNodeEditor { /// Draws all connections public void DrawConnections() { - List drawnReroutes = new List(); + Vector2 mousePos = Event.current.mousePosition; + List selection = preBoxSelectionReroute != null ? new List(preBoxSelectionReroute) : new List(); + hoveredReroute = new RerouteReference(); foreach (XNode.Node node in graph.nodes) { //If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset. if (node == null) continue; + // Draw full connections and output > reroute foreach (XNode.NodePort output in node.Outputs) { //Needs cleanup. Null checks are ugly if (!portConnectionPoints.ContainsKey(output)) continue; Color connectionColor = graphEditor.GetTypeColor(output.ValueType); - Vector2 from = _portConnectionPoints[output].center; for (int k = 0; k < output.ConnectionCount; k++) { XNode.NodePort input = output.GetConnection(k); @@ -238,48 +238,36 @@ namespace XNodeEditor { if (!input.IsConnectedTo(output)) input.Connect(output); if (!_portConnectionPoints.ContainsKey(input)) continue; + Vector2 from = _portConnectionPoints[output].center; Vector2 to = Vector2.zero; - int[] rerouteIndices = output.GetReroutes(k); - for (int i = 0; i < rerouteIndices.Length + 1; i++) { - if (i != rerouteIndices.Length) to = graph.reroutes[rerouteIndices[i]]; - else to = _portConnectionPoints[input].center; + List reroutePoints = output.GetReroutePoints(k); + // Loop through reroute points and draw the path + for (int i = 0; i < reroutePoints.Count; i++) { + to = reroutePoints[i]; DrawConnection(from, to, connectionColor); from = to; + } + to = _portConnectionPoints[input].center; + DrawConnection(from, to, connectionColor); + + // Loop through reroute points again and draw the points + for (int i = 0; i < reroutePoints.Count; i++) { + // Draw reroute point at position + Rect rect = new Rect(reroutePoints[i], new Vector2(16, 16)); + rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); + rect = GridToWindowRect(rect); + Color bgcol = new Color32(90, 97, 105, 255);; + if (selectedReroutes.Contains(new RerouteReference(output, k, i))) bgcol = Color.yellow; + NodeEditorGUILayout.DrawPortHandle(rect, bgcol, connectionColor); + + if (rect.Overlaps(selectionBox)) selection.Add(new RerouteReference(output, k, i)); + if (rect.Contains(mousePos)) hoveredReroute = new RerouteReference(output, k, i); - if (drawnReroutes.Contains(i)) break; - else drawnReroutes.Add(i); } } } } - } - - /// Draws all connections - public void DrawReroutes() { - Vector2 mousePos = Event.current.mousePosition; - SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); - - // Box selection support - List selection = preBoxSelectionReroute != null ? new List(preBoxSelectionReroute) : new List(); - Vector2 boxStartPos = GridToWindowPosition(dragBoxStart); - Vector2 boxSize = mousePos - boxStartPos; - if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } - if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); } - Rect boxRect = new Rect(boxStartPos, boxSize); - - hoveredReroute = -1; - for (int i = 0; i < reroutes.arraySize; i++) { - Rect rect = new Rect(reroutes.GetArrayElementAtIndex(i).vector2Value, new Vector2(16, 16)); - rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); - rect = GridToWindowRect(rect); - Color bgcol = Color.black; - if (selectedReroutes.Contains(i)) bgcol = Color.yellow; - NodeEditorGUILayout.DrawPortHandle(rect, bgcol, Color.white); - - if (rect.Overlaps(boxRect)) selection.Add(i); - if (rect.Contains(mousePos)) hoveredReroute = i; - } - if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = new List(selection); + if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = selection; } private void DrawNodes() { @@ -311,6 +299,13 @@ namespace XNodeEditor { List preSelection = preBoxSelection != null ? new List(preBoxSelection) : new List(); + // Selection box stuff + Vector2 boxStartPos = GridToWindowPositionNoClipped(dragBoxStart); + Vector2 boxSize = mousePos - boxStartPos; + if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } + if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); } + Rect selectionBox = new Rect(boxStartPos, boxSize); + //Save guiColor so we can revert it Color guiColor = GUI.color; for (int n = 0; n < graph.nodes.Count; n++) { @@ -380,12 +375,7 @@ namespace XNodeEditor { //If dragging a selection box, add nodes inside to selection if (currentActivity == NodeActivity.DragGrid) { - Vector2 startPos = GridToWindowPositionNoClipped(dragBoxStart); - Vector2 size = mousePos - startPos; - if (size.x < 0) { startPos.x += size.x; size.x = Mathf.Abs(size.x); } - if (size.y < 0) { startPos.y += size.y; size.y = Mathf.Abs(size.y); } - Rect r = new Rect(startPos, size); - if (windowRect.Overlaps(r)) preSelection.Add(node); + if (windowRect.Overlaps(selectionBox)) preSelection.Add(node); } //Check if we are hovering any of this nodes ports diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index f7d70d0..c2092e0 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -10,8 +10,6 @@ namespace XNode { /// All nodes in the graph. /// See: [SerializeField] public List nodes = new List(); - /// Nodes used primarily for organization - [SerializeField] public List reroutes = new List(); /// Add a node to the graph by type public T AddNode() where T : Node { diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index 299559e..65ad4be 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -216,6 +216,14 @@ namespace XNode { return port; } + /// Get index of the connection connecting this and specified ports + public int GetConnectionIndex(NodePort port) { + for (int i = 0; i < ConnectionCount; i++) { + if (connections[i].Port == port) return i; + } + return -1; + } + public bool IsConnectedTo(NodePort port) { for (int i = 0; i < connections.Count; i++) { if (connections[i].Port == port) return true; @@ -250,10 +258,9 @@ namespace XNode { } } - /// Get reroute indices. This is used for graph organization purposes - /// Connection index - public int[] GetReroutes(int i) { - return connections[i].reroutes; + /// Get reroute points for a given connection. This is used for organization + public List GetReroutePoints(int index) { + return connections[index].reroutePoints; } /// Swap connected nodes from the old list with nodes from the new list @@ -269,10 +276,10 @@ namespace XNode { [SerializeField] public string fieldName; [SerializeField] public Node node; public NodePort Port { get { return port != null ? port : port = GetPort(); } } - /// Used for organization - [SerializeField] public int[] reroutes = new int[0]; [NonSerialized] private NodePort port; + /// Extra connection path points for organization + [SerializeField] public List reroutePoints = new List(); public PortConnection(NodePort port) { this.port = port; From 19e244212c50adca0270b0d045b3175817297f55 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Thu, 5 Apr 2018 20:59:50 +0200 Subject: [PATCH 10/18] Added NodeRename #11 --- Scripts/Editor/NodeEditor.cs | 26 +++++++++++++++++++++++++- Scripts/Editor/NodeEditorAction.cs | 12 ++++++++++++ Scripts/Editor/NodeEditorGUI.cs | 9 ++------- Scripts/Editor/NodeGraphEditor.cs | 1 + Scripts/Node.cs | 2 +- Scripts/NodeGraph.cs | 7 ------- 6 files changed, 41 insertions(+), 16 deletions(-) diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 39febe9..8a64663 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -13,6 +13,7 @@ namespace XNodeEditor { /// Fires every whenever a node was modified through the editor public static Action onUpdateNode; public static Dictionary portPositions; + public static int renaming; /// Draws the node GUI. /// Port handle positions need to be returned to the NodeEditorWindow @@ -24,7 +25,21 @@ namespace XNodeEditor { public virtual void OnHeaderGUI() { GUI.color = Color.white; string title = target.name; - GUILayout.Label(title, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); + if (renaming != 0 && Selection.Contains(target)) { + int controlID = EditorGUIUtility.GetControlID(FocusType.Keyboard) + 1; + if (renaming == 1) { + EditorGUIUtility.keyboardControl = controlID; + EditorGUIUtility.editingTextField = true; + renaming = 2; + } + target.name = EditorGUILayout.TextField(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); + if (!EditorGUIUtility.editingTextField) { + Rename(target.name); + renaming = 0; + } + } else { + GUILayout.Label(title, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); + } } /// Draws standard field editors for all public fields @@ -52,6 +67,15 @@ namespace XNodeEditor { else return Color.white; } + public void InitiateRename() { + renaming = 1; + } + + public void Rename(string newName) { + target.name = newName; + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + } + [AttributeUsage(AttributeTargets.Class)] public class CustomNodeEditorAttribute : Attribute, XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 4c20270..7ef961c 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -291,6 +291,9 @@ namespace XNodeEditor { public void CreateNode(Type type, Vector2 position) { XNode.Node node = graph.AddNode(type); node.position = position; + node.name = UnityEditor.ObjectNames.NicifyVariableName(type.ToString()); + AssetDatabase.AddObjectToAsset(node, graph); + AssetDatabase.SaveAssets(); Repaint(); } @@ -304,6 +307,15 @@ namespace XNodeEditor { } } + /// Draw this node on top of other nodes by placing it last in the graph.nodes list + public void MoveNodeToTop(XNode.Node node) { + int index; + while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) { + graph.nodes[index] = graph.nodes[index + 1]; + graph.nodes[index + 1] = node; + } + } + /// Dublicate selected nodes and select the dublicates public void DublicateSelectedNodes() { UnityEngine.Object[] newNodes = new UnityEngine.Object[Selection.objects.Length]; diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 2ff98ec..9e70693 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -109,13 +109,8 @@ namespace XNodeEditor { // If only one node is selected if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { XNode.Node node = Selection.activeObject as XNode.Node; - contextMenu.AddItem(new GUIContent("Move To Top"), false, () => { - int index; - while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) { - graph.nodes[index] = graph.nodes[index + 1]; - graph.nodes[index + 1] = node; - } - }); + contextMenu.AddItem(new GUIContent("Move To Top"), false, () => MoveNodeToTop(node)); + contextMenu.AddItem(new GUIContent("Rename"), false, NodeEditor.GetEditor(node).InitiateRename); } contextMenu.AddItem(new GUIContent("Duplicate"), false, DublicateSelectedNodes); diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 3fa9406..2a8f43c 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -10,6 +10,7 @@ namespace XNodeEditor { public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase { /// Custom node editors defined with [CustomNodeGraphEditor] [NonSerialized] private static Dictionary editors; + protected bool isRenaming; public virtual Texture2D GetGridTexture() { return NodeEditorPreferences.GetSettings().gridTexture; diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 099c56e..be3d5a7 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -71,7 +71,7 @@ namespace XNode { } /// Initialize node. Called on creation. - protected virtual void Init() { name = GetType().Name; } + protected virtual void Init() { } /// Checks all connections for invalid references, and removes them. public void VerifyConnections() { diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index c2092e0..05b865b 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -19,13 +19,6 @@ namespace XNode { /// Add a node to the graph by type public virtual Node AddNode(Type type) { Node node = ScriptableObject.CreateInstance(type) as Node; -#if UNITY_EDITOR - if (!Application.isPlaying) { - UnityEditor.AssetDatabase.AddObjectToAsset(node, this); - UnityEditor.AssetDatabase.SaveAssets(); - node.name = UnityEditor.ObjectNames.NicifyVariableName(node.name); - } -#endif nodes.Add(node); node.graph = this; return node; From 1885d453a49a53ca63a4667afe67e09030ceb7f1 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Thu, 5 Apr 2018 21:53:03 +0200 Subject: [PATCH 11/18] Removed #if UNITY_EDITOR from NodeGraph.cs #23 --- Scripts/Editor/NodeEditorAction.cs | 4 ++-- Scripts/Editor/NodeGraphEditor.cs | 16 ++++++++++++++++ Scripts/NodeGraph.cs | 13 ------------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 7ef961c..6cea69c 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -302,7 +302,7 @@ namespace XNodeEditor { foreach (UnityEngine.Object item in Selection.objects) { if (item is XNode.Node) { XNode.Node node = item as XNode.Node; - graph.RemoveNode(node); + graphEditor.RemoveNode(node); } } } @@ -324,7 +324,7 @@ namespace XNodeEditor { 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 = graph.CopyNode(srcNode); + XNode.Node newNode = graphEditor.CopyNode(srcNode); substitutes.Add(srcNode, newNode); newNode.position = srcNode.position + new Vector2(30, 30); newNodes[i] = newNode; diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 2a8f43c..5025aaa 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -39,6 +39,22 @@ namespace XNodeEditor { return NodeEditorPreferences.GetTypeColor(type); } + /// Creates a copy of the original node in the graph + public XNode.Node CopyNode(XNode.Node original) { + XNode.Node node = target.CopyNode(original); + node.name = original.name; + AssetDatabase.AddObjectToAsset(node, target); + AssetDatabase.SaveAssets(); + return node; + } + + /// Safely remove a node and all its connections. + public void RemoveNode(XNode.Node node) { + UnityEngine.Object.DestroyImmediate(node, true); + target.RemoveNode(node); + AssetDatabase.SaveAssets(); + } + [AttributeUsage(AttributeTargets.Class)] public class CustomNodeGraphEditorAttribute : Attribute, XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index 05b865b..09010dc 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -28,13 +28,6 @@ namespace XNode { public virtual Node CopyNode(Node original) { Node node = ScriptableObject.Instantiate(original); node.ClearConnections(); -#if UNITY_EDITOR - if (!Application.isPlaying) { - UnityEditor.AssetDatabase.AddObjectToAsset(node, this); - UnityEditor.AssetDatabase.SaveAssets(); - node.name = UnityEditor.ObjectNames.NicifyVariableName(node.name); - } -#endif nodes.Add(node); node.graph = this; return node; @@ -44,12 +37,6 @@ namespace XNode { /// public void RemoveNode(Node node) { node.ClearConnections(); -#if UNITY_EDITOR - if (!Application.isPlaying) { - DestroyImmediate(node, true); - UnityEditor.AssetDatabase.SaveAssets(); - } -#endif nodes.Remove(node); } From 588fc0d80a965cae821423cdb8ae6f0440afc660 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 7 Apr 2018 18:47:28 +0200 Subject: [PATCH 12/18] Fix potential error when copying a NodeGraph with null nodes --- Scripts/NodeGraph.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index 09010dc..32310d2 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -51,6 +51,7 @@ namespace XNode { NodeGraph graph = Instantiate(this); // Instantiate all nodes inside the graph for (int i = 0; i < nodes.Count; i++) { + if (nodes[i] == null) continue; Node node = Instantiate(nodes[i]) as Node; node.graph = graph; graph.nodes[i] = node; @@ -58,6 +59,7 @@ namespace XNode { // Redirect all connections for (int i = 0; i < graph.nodes.Count; i++) { + if (graph.nodes[i] == null) continue; foreach (NodePort port in graph.nodes[i].Ports) { port.Redirect(nodes, graph.nodes); } From 9ce5496b624bcc276877f53990c83454b4fed0af Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Mon, 9 Apr 2018 09:36:42 +0200 Subject: [PATCH 13/18] Fixed IndexOutOfRangeException issue #29 --- Scripts/Editor/NodeEditorAction.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 6cea69c..f562d72 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -187,10 +187,14 @@ namespace XNodeEditor { if (draggedOutputTarget != null) { XNode.Node node = draggedOutputTarget.node; if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget); + + // ConnectionIndex can be -1 if the connection is removed instantly after creation int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget); - draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes); - if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); - EditorUtility.SetDirty(graph); + if (connectionIndex != -1) { + draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes); + if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); + EditorUtility.SetDirty(graph); + } } //Release dragged connection draggedOutput = null; From ba8b9ef44723d55f8c80b3d16bf2509c7717e4c4 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Tue, 10 Apr 2018 22:57:25 +0200 Subject: [PATCH 14/18] Removed unused dictionary --- Scripts/Editor/NodeGraphEditor.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 5025aaa..fa91c03 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -8,8 +8,6 @@ namespace XNodeEditor { /// Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. [CustomNodeGraphEditor(typeof(XNode.NodeGraph))] public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase { - /// Custom node editors defined with [CustomNodeGraphEditor] - [NonSerialized] private static Dictionary editors; protected bool isRenaming; public virtual Texture2D GetGridTexture() { From bab898e4890e181b2765f5fd64c69544963a6ba5 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Tue, 10 Apr 2018 22:59:32 +0200 Subject: [PATCH 15/18] Exposed virtual OnGUI() and 'position' for graph editors --- Scripts/Editor/NodeEditorGUI.cs | 2 ++ Scripts/Editor/NodeGraphEditor.cs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 9e70693..629bc9d 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -14,6 +14,7 @@ namespace XNodeEditor { Matrix4x4 m = GUI.matrix; if (graph == null) return; graphEditor = NodeGraphEditor.GetEditor(graph); + graphEditor.position = position; Controls(); @@ -23,6 +24,7 @@ namespace XNodeEditor { DrawNodes(); DrawSelectionBox(); DrawTooltip(); + graphEditor.OnGUI(); GUI.matrix = m; } diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index fa91c03..37b7eaf 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -8,8 +8,13 @@ namespace XNodeEditor { /// Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. [CustomNodeGraphEditor(typeof(XNode.NodeGraph))] public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase { + /// The position of the window in screen space. + public Rect position; + /// Are we currently renaming a node? protected bool isRenaming; + public virtual void OnGUI() { } + public virtual Texture2D GetGridTexture() { return NodeEditorPreferences.GetSettings().gridTexture; } From 9f09452b43c032b5f510608ad491aa1ec42c5bac Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Thu, 12 Apr 2018 09:27:50 +0200 Subject: [PATCH 16/18] Improved reroute point size, rendering, and enabled deletion of selected points --- Scripts/Editor/NodeEditorAction.cs | 7 +++++++ Scripts/Editor/NodeEditorGUI.cs | 22 +++++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index f562d72..b237d6b 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using UnityEditor; using UnityEngine; @@ -303,6 +304,12 @@ namespace XNodeEditor { /// Remove nodes in the graph in Selection.objects public void RemoveSelectedNodes() { + // We need to delete reroutes starting at the highest point index to avoid shifting indices + selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList(); + for (int i = 0; i < selectedReroutes.Count; i++) { + selectedReroutes[i].RemovePoint(); + } + selectedReroutes.Clear(); foreach (UnityEngine.Object item in Selection.objects) { if (item is XNode.Node) { XNode.Node node = item as XNode.Node; diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 629bc9d..2c71a29 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -216,6 +216,7 @@ namespace XNodeEditor { List selection = preBoxSelectionReroute != null ? new List(preBoxSelectionReroute) : new List(); hoveredReroute = new RerouteReference(); + Color col = GUI.color; foreach (XNode.Node node in graph.nodes) { //If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset. if (node == null) continue; @@ -249,21 +250,28 @@ namespace XNodeEditor { // Loop through reroute points again and draw the points for (int i = 0; i < reroutePoints.Count; i++) { + RerouteReference rerouteRef = new RerouteReference(output, k, i); // Draw reroute point at position - Rect rect = new Rect(reroutePoints[i], new Vector2(16, 16)); - rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); + Rect rect = new Rect(reroutePoints[i], new Vector2(12, 12)); + rect.position = new Vector2(rect.position.x - 6, rect.position.y - 6); rect = GridToWindowRect(rect); - Color bgcol = new Color32(90, 97, 105, 255);; - if (selectedReroutes.Contains(new RerouteReference(output, k, i))) bgcol = Color.yellow; - NodeEditorGUILayout.DrawPortHandle(rect, bgcol, connectionColor); - if (rect.Overlaps(selectionBox)) selection.Add(new RerouteReference(output, k, i)); - if (rect.Contains(mousePos)) hoveredReroute = new RerouteReference(output, k, i); + // Draw selected reroute points with an outline + if (selectedReroutes.Contains(rerouteRef)) { + GUI.color = NodeEditorPreferences.GetSettings().highlightColor; + GUI.DrawTexture(rect, NodeEditorResources.dotOuter); + } + + GUI.color = connectionColor; + GUI.DrawTexture(rect, NodeEditorResources.dot); + if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef); + if (rect.Contains(mousePos)) hoveredReroute = rerouteRef; } } } } + GUI.color = col; if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = selection; } From 2582d5aaf3cac7c769ee21cde7de449bee353e6f Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 13 Apr 2018 10:46:51 +0200 Subject: [PATCH 17/18] Added option to disable autosave in preferences --- Scripts/Editor/NodeEditorAction.cs | 8 ++++---- Scripts/Editor/NodeEditorGUI.cs | 4 ++-- Scripts/Editor/NodeEditorPreferences.cs | 14 ++++++++++++-- Scripts/Editor/NodeEditorWindow.cs | 6 +++--- Scripts/Editor/NodeGraphEditor.cs | 4 ++-- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index b237d6b..651824a 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -201,9 +201,9 @@ namespace XNodeEditor { draggedOutput = null; draggedOutputTarget = null; EditorUtility.SetDirty(graph); - AssetDatabase.SaveAssets(); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } else if (currentActivity == NodeActivity.DragNode) { - AssetDatabase.SaveAssets(); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } else if (!IsHoveringNode) { // If click outside node, release field focus if (!isPanning) { @@ -214,7 +214,7 @@ namespace XNodeEditor { EditorGUIUtility.keyboardControl = 0; EditorGUIUtility.hotControl = 0; } - AssetDatabase.SaveAssets(); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } // If click node header, select it. @@ -298,7 +298,7 @@ namespace XNodeEditor { node.position = position; node.name = UnityEditor.ObjectNames.NicifyVariableName(type.ToString()); AssetDatabase.AddObjectToAsset(node, graph); - AssetDatabase.SaveAssets(); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); Repaint(); } diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 2c71a29..34cd210 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -94,7 +94,7 @@ namespace XNodeEditor { GenericMenu contextMenu = new GenericMenu(); contextMenu.AddItem(new GUIContent("Remove"), false, () => reroute.RemovePoint()); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); - AssetDatabase.SaveAssets(); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } /// Show right-click context menu for hovered port @@ -102,7 +102,7 @@ namespace XNodeEditor { GenericMenu contextMenu = new GenericMenu(); contextMenu.AddItem(new GUIContent("Clear Connections"), false, () => hoveredPort.ClearConnections()); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); - AssetDatabase.SaveAssets(); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } /// Show right-click context menu for selected nodes diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index aa87785..210a619 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -25,6 +25,7 @@ namespace XNodeEditor { public Color32 highlightColor = new Color32(255, 255, 255, 255); public bool gridSnap = true; + public bool autoSave = true; [SerializeField] private string typeColorsData = ""; [NonSerialized] public Dictionary typeColors = new Dictionary(); public NoodleType noodleType = NoodleType.Curve; @@ -73,9 +74,9 @@ namespace XNodeEditor { XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute attrib = attribs[0] as XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute; lastEditor = XNodeEditor.NodeEditorWindow.current.graphEditor; lastKey = attrib.editorPrefsKey; - VerifyLoaded(); } else return null; } + if (!settings.ContainsKey(lastKey)) VerifyLoaded(); return settings[lastKey]; } @@ -86,6 +87,7 @@ namespace XNodeEditor { NodeSettingsGUI(lastKey, settings); GridSettingsGUI(lastKey, settings); + SystemSettingsGUI(lastKey, settings); TypeColorsGUI(lastKey, settings); if (GUILayout.Button(new GUIContent("Set Default", "Reset all values to default"), GUILayout.Width(120))) { ResetPrefs(); @@ -95,7 +97,7 @@ namespace XNodeEditor { private static void GridSettingsGUI(string key, Settings settings) { //Label EditorGUILayout.LabelField("Grid", EditorStyles.boldLabel); - settings.gridSnap = EditorGUILayout.Toggle("Snap", settings.gridSnap); + settings.gridSnap = EditorGUILayout.Toggle(new GUIContent("Snap", "Hold CTRL in editor to invert"), settings.gridSnap); settings.gridLineColor = EditorGUILayout.ColorField("Color", settings.gridLineColor); settings.gridBgColor = EditorGUILayout.ColorField(" ", settings.gridBgColor); @@ -107,6 +109,14 @@ namespace XNodeEditor { EditorGUILayout.Space(); } + private static void SystemSettingsGUI(string key, Settings settings) { + //Label + EditorGUILayout.LabelField("System", EditorStyles.boldLabel); + settings.autoSave = EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"), settings.autoSave); + if (GUI.changed) SavePrefs(key, settings); + EditorGUILayout.Space(); + } + private static void NodeSettingsGUI(string key, Settings settings) { //Label EditorGUILayout.LabelField("Node", EditorStyles.boldLabel); diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 67d7e73..d626130 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -20,8 +20,8 @@ namespace XNodeEditor { private float _zoom = 1; void OnFocus() { - AssetDatabase.SaveAssets(); current = this; + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } partial void OnEnable(); @@ -37,7 +37,7 @@ namespace XNodeEditor { public void Save() { if (AssetDatabase.Contains(graph)) { EditorUtility.SetDirty(graph); - AssetDatabase.SaveAssets(); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } else SaveAs(); } @@ -49,7 +49,7 @@ namespace XNodeEditor { if (existingGraph != null) AssetDatabase.DeleteAsset(path); AssetDatabase.CreateAsset(graph, path); EditorUtility.SetDirty(graph); - AssetDatabase.SaveAssets(); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } } diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 37b7eaf..8d81a07 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -47,7 +47,7 @@ namespace XNodeEditor { XNode.Node node = target.CopyNode(original); node.name = original.name; AssetDatabase.AddObjectToAsset(node, target); - AssetDatabase.SaveAssets(); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); return node; } @@ -55,7 +55,7 @@ namespace XNodeEditor { public void RemoveNode(XNode.Node node) { UnityEngine.Object.DestroyImmediate(node, true); target.RemoveNode(node); - AssetDatabase.SaveAssets(); + if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } [AttributeUsage(AttributeTargets.Class)] From a78d7258ca83dd90e32619236b93d110ef9a67af Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Fri, 13 Apr 2018 10:51:44 +0200 Subject: [PATCH 18/18] Fixed NullRefException on open graph --- Scripts/Editor/NodeEditorWindow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index d626130..db16033 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -21,6 +21,7 @@ namespace XNodeEditor { void OnFocus() { current = this; + graphEditor = NodeGraphEditor.GetEditor(graph); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); }