From 31aa42141320cb9b1f7c30dd349ca5049bb00e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Steegmu=CC=88ller?= Date: Mon, 10 Aug 2020 09:48:00 +0200 Subject: [PATCH] improving extendability by making accessors more open did split PortConnection into separate class added possibility to have labels on connections --- Scripts/Editor/NodeEditor.cs | 2 +- Scripts/Editor/NodeEditorAction.cs | 16 +- Scripts/Editor/NodeEditorGUI.cs | 402 +++++++++++++++----------- Scripts/Editor/NodeEditorResources.cs | 6 +- Scripts/Editor/NodeEditorWindow.cs | 2 +- Scripts/Node.cs | 8 +- Scripts/NodePort.cs | 37 +-- Scripts/PortConnection.cs | 31 ++ Scripts/PortConnection.cs.meta | 3 + 9 files changed, 294 insertions(+), 213 deletions(-) mode change 100644 => 100755 Scripts/Editor/NodeEditor.cs mode change 100644 => 100755 Scripts/Editor/NodeEditorAction.cs mode change 100644 => 100755 Scripts/Editor/NodeEditorResources.cs mode change 100644 => 100755 Scripts/Editor/NodeEditorWindow.cs mode change 100644 => 100755 Scripts/Node.cs mode change 100644 => 100755 Scripts/NodePort.cs create mode 100644 Scripts/PortConnection.cs create mode 100644 Scripts/PortConnection.cs.meta diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs old mode 100644 new mode 100755 index 36c3a6e3..9419c3e1 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -18,7 +18,7 @@ public class NodeEditor : XNodeEditor.Internal.NodeEditorBase Fires every whenever a node was modified through the editor public static Action onUpdateNode; - public readonly static Dictionary portPositions = new Dictionary(); + public static readonly Dictionary portPositions = new Dictionary(); #if ODIN_INSPECTOR protected internal static bool inNodeEditor = false; diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs old mode 100644 new mode 100755 index b1127329..6be9ff62 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -18,23 +18,23 @@ public enum NodeActivity { Idle, HoldNode, DragNode, HoldGrid, DragGrid } private bool IsHoveringPort { get { return hoveredPort != null; } } private bool IsHoveringNode { get { return hoveredNode != null; } } private bool IsHoveringReroute { get { return hoveredReroute.port != null; } } - private XNode.Node hoveredNode = null; + protected XNode.Node hoveredNode = null; [NonSerialized] public 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(); + protected RerouteReference hoveredReroute = new RerouteReference(); public List selectedReroutes = new List(); - private Vector2 dragBoxStart; - private UnityEngine.Object[] preBoxSelection; - private RerouteReference[] preBoxSelectionReroute; - private Rect selectionBox; + protected Vector2 dragBoxStart; + protected UnityEngine.Object[] preBoxSelection; + protected RerouteReference[] preBoxSelectionReroute; + protected Rect selectionBox; private bool isDoubleClick = false; private Vector2 lastMousePosition; private float dragThreshold = 1f; - public void Controls() { + protected virtual void Controls() { wantsMouseMove = true; Event e = Event.current; switch (e.type) { @@ -483,7 +483,7 @@ private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft) { } /// Draw a connection as we are dragging it - public void DrawDraggedConnection() { + protected virtual void DrawDraggedConnection() { if (IsDraggingPort) { Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null); float thickness = graphEditor.GetNoodleThickness(draggedOutput, null); diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 99cdecf1..cdec34f4 100755 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -3,16 +3,18 @@ using System.Linq; using UnityEditor; using UnityEngine; +using XNode; using XNodeEditor.Internal; +using Object = UnityEngine.Object; namespace XNodeEditor { /// Contains GUI methods public partial class NodeEditorWindow { public NodeGraphEditor graphEditor; - private List selectionCache; - private List culledNodes; + protected List selectionCache; + protected List culledNodes; /// 19 if docked, 22 if not - private int topPadding { get { return isDocked() ? 19 : 22; } } + protected 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 static readonly Vector3[] polyLineTempArray = new Vector3[2]; @@ -32,15 +34,19 @@ protected virtual void OnGUI() { DrawTooltip(); graphEditor.OnGUI(); + RunAndResetOnLateGui(); + + GUI.matrix = m; + } + + protected void RunAndResetOnLateGui() + { // Run and reset onLateGUI if (onLateGUI != null) { onLateGUI(); onLateGUI = null; } - - GUI.matrix = m; } - public static void BeginZoomed(Rect rect, float zoom, float topPadding) { GUI.EndClip(); @@ -61,7 +67,7 @@ public static void EndZoomed(Rect rect, float zoom, float topPadding) { GUI.matrix = Matrix4x4.TRS(offset, Quaternion.identity, Vector3.one); } - public void DrawGrid(Rect rect, float zoom, Vector2 panOffset) { + protected virtual void DrawGrid(Rect rect, float zoom, Vector2 panOffset) { rect.position = Vector2.zero; @@ -86,7 +92,7 @@ public void DrawGrid(Rect rect, float zoom, Vector2 panOffset) { GUI.DrawTextureWithTexCoords(rect, crossTex, new Rect(tileOffset + new Vector2(0.5f, 0.5f), tileAmount)); } - public void DrawSelectionBox() { + protected virtual void DrawSelectionBox() { if (currentActivity == NodeActivity.DragGrid) { Vector2 curPos = WindowToGridPosition(Event.current.mousePosition); Vector2 size = curPos - dragBoxStart; @@ -137,22 +143,27 @@ static void DrawAAPolyLineNonAlloc(float thickness, Vector2 p0, Vector2 p1) { } /// Draw a bezier from output to input in grid coordinates - public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, float thickness, List gridPoints) { + public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, float thickness, + List gridPoints, string connectionLabel = null) { // convert grid points to window points for (int i = 0; i < gridPoints.Count; ++i) gridPoints[i] = GridToWindowPosition(gridPoints[i]); Color originalHandlesColor = Handles.color; Handles.color = gradient.Evaluate(0f); + int length = gridPoints.Count; + Vector2 point_a = Vector2.zero; + Vector2 point_b = Vector2.zero; switch (path) { case NoodlePath.Curvy: Vector2 outputTangent = Vector2.right; for (int i = 0; i < length - 1; i++) { Vector2 inputTangent; // Cached most variables that repeat themselves here to avoid so many indexer calls :p - Vector2 point_a = gridPoints[i]; - Vector2 point_b = gridPoints[i + 1]; + point_a = gridPoints[i]; + point_b = gridPoints[i + 1]; + float dist_ab = Vector2.Distance(point_a, point_b); if (i == 0) outputTangent = zoom * dist_ab * 0.01f * Vector2.right; if (i < length - 2) { @@ -161,6 +172,7 @@ public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, Vector2 cb = (point_b - point_c).normalized; Vector2 ac = (point_c - point_a).normalized; Vector2 p = (ab + cb) * 0.5f; + float tangentLength = (dist_ab + Vector2.Distance(point_b, point_c)) * 0.005f * zoom; float side = ((ac.x * (point_b.y - point_a.y)) - (ac.y * (point_b.x - point_a.x))); @@ -187,7 +199,7 @@ public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, if (draw == 0) bezierPrevious = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, (j - 1f) / (float) division); } if (i == length - 2) - Handles.color = gradient.Evaluate((j + 1f) / division); + Handles.color = gradient.Evaluate(Time.time + (j + 1f) / division); Vector2 bezierNext = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, j / (float) division); DrawAAPolyLineNonAlloc(thickness, bezierPrevious, bezierNext); bezierPrevious = bezierNext; @@ -197,8 +209,8 @@ public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, break; case NoodlePath.Straight: for (int i = 0; i < length - 1; i++) { - Vector2 point_a = gridPoints[i]; - Vector2 point_b = gridPoints[i + 1]; + point_a = gridPoints[i]; + point_b = gridPoints[i + 1]; // Draws the line with the coloring. Vector2 prev_point = point_a; // Approximately one segment per 5 pixels @@ -224,20 +236,20 @@ public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, if (i == length - 1) continue; // Skip last index if (gridPoints[i].x <= gridPoints[i + 1].x - (50 / zoom)) { float midpoint = (gridPoints[i].x + gridPoints[i + 1].x) * 0.5f; - Vector2 start_1 = gridPoints[i]; - Vector2 end_1 = gridPoints[i + 1]; - start_1.x = midpoint; - end_1.x = midpoint; + point_a = gridPoints[i]; + point_b = gridPoints[i + 1]; + point_a.x = midpoint; + point_b.x = midpoint; if (i == length - 2) { - DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1); + DrawAAPolyLineNonAlloc(thickness, gridPoints[i], point_a); Handles.color = gradient.Evaluate(0.5f); - DrawAAPolyLineNonAlloc(thickness, start_1, end_1); + DrawAAPolyLineNonAlloc(thickness, point_a, point_b); Handles.color = gradient.Evaluate(1f); - DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]); + DrawAAPolyLineNonAlloc(thickness, point_b, gridPoints[i + 1]); } else { - DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1); - DrawAAPolyLineNonAlloc(thickness, start_1, end_1); - DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]); + DrawAAPolyLineNonAlloc(thickness, gridPoints[i], point_a); + DrawAAPolyLineNonAlloc(thickness, point_a, point_b); + DrawAAPolyLineNonAlloc(thickness, point_b, gridPoints[i + 1]); } } else { float midpoint = (gridPoints[i].y + gridPoints[i + 1].y) * 0.5f; @@ -281,8 +293,8 @@ public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, Handles.color = gradient.Evaluate(1f); DrawAAPolyLineNonAlloc(thickness, end, gridPoints[length - 1]); for (int i = 0; i < length - 1; i++) { - Vector2 point_a = gridPoints[i]; - Vector2 point_b = gridPoints[i + 1]; + point_a = gridPoints[i]; + point_b = gridPoints[i + 1]; // Draws the line with the coloring. Vector2 prev_point = point_a; // Approximately one segment per 5 pixels @@ -306,79 +318,108 @@ public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, gridPoints[length - 1] = end; break; } + + if (zoom < 2f) + { + NodeEditorResources.styles.connectionLabel.fontSize = (int)(12 / zoom); + var labelSize = (NodeEditorResources.styles.connectionLabel.CalcSize(new GUIContent(connectionLabel)) + Vector2.one * 5 )/ zoom ; + Vector2 noodleCenter = (point_a + point_b) * 0.5f; + EditorGUI.LabelField(new Rect(noodleCenter - labelSize * 0.5f, labelSize), new GUIContent(connectionLabel), NodeEditorResources.styles.connectionLabel); + } + Handles.color = originalHandlesColor; } /// Draws all connections - public void DrawConnections() { - Vector2 mousePos = Event.current.mousePosition; + protected virtual void DrawConnections() { List selection = preBoxSelectionReroute != null ? new List(preBoxSelectionReroute) : new List(); hoveredReroute = new RerouteReference(); - List gridPoints = new List(2); - 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; - - // Draw full connections and output > reroute - foreach (XNode.NodePort output in node.Outputs) { - //Needs cleanup. Null checks are ugly - Rect fromRect; - if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue; - - Color portColor = graphEditor.GetPortColor(output); - for (int k = 0; k < output.ConnectionCount; k++) { - XNode.NodePort input = output.GetConnection(k); - - Gradient noodleGradient = graphEditor.GetNoodleGradient(output, input); - float noodleThickness = graphEditor.GetNoodleThickness(output, input); - NoodlePath noodlePath = graphEditor.GetNoodlePath(output, input); - NoodleStroke noodleStroke = graphEditor.GetNoodleStroke(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); - Rect toRect; - if (!_portConnectionPoints.TryGetValue(input, out toRect)) continue; - - List reroutePoints = output.GetReroutePoints(k); - - gridPoints.Clear(); - gridPoints.Add(fromRect.center); - gridPoints.AddRange(reroutePoints); - gridPoints.Add(toRect.center); - DrawNoodle(noodleGradient, noodlePath, noodleStroke, noodleThickness, gridPoints); - - // 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(12, 12)); - rect.position = new Vector2(rect.position.x - 6, rect.position.y - 6); - rect = GridToWindowRect(rect); - - // Draw selected reroute points with an outline - if (selectedReroutes.Contains(rerouteRef)) { - GUI.color = NodeEditorPreferences.GetSettings().highlightColor; - GUI.DrawTexture(rect, NodeEditorResources.dotOuter); - } + foreach (XNode.Node node in graph.nodes) + { + DrawNodeConnections(node, selection); + } + GUI.color = col; + if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = selection; + } - GUI.color = portColor; - GUI.DrawTexture(rect, NodeEditorResources.dot); - if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef); - if (rect.Contains(mousePos)) hoveredReroute = rerouteRef; + protected virtual void DrawNodeConnections(Node node, List selection) + { + //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) return; - } - } + // Draw full connections and output > reroute + foreach (XNode.NodePort output in node.Outputs) + { + //Needs cleanup. Null checks are ugly + Rect fromRect; + if (!_portConnectionPoints.TryGetValue(output, out fromRect)) return; + + Color portColor = graphEditor.GetPortColor(output); + for (int k = 0; k < output.ConnectionCount; k++) + { + XNode.NodePort input = output.GetConnection(k); + + DrawConnection( selection, output, input, k, fromRect, portColor); } } - GUI.color = col; - if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = selection; } - private void DrawNodes() { + protected virtual void DrawConnection(List selection, NodePort output, + NodePort input, int k, Rect fromRect, Color portColor) + { + Gradient noodleGradient = graphEditor.GetNoodleGradient(output, input); + float noodleThickness = graphEditor.GetNoodleThickness(output, input); + NoodlePath noodlePath = graphEditor.GetNoodlePath(output, input); + NoodleStroke noodleStroke = graphEditor.GetNoodleStroke(output, input); + + // Error handling + if (input == null) + return; + if (!input.IsConnectedTo(output)) input.Connect(output); + Rect toRect; + if (!_portConnectionPoints.TryGetValue(input, out toRect)) return; + + List reroutePoints = output.GetReroutePoints(k); + List gridPoints = new List(2); + + gridPoints.Clear(); + gridPoints.Add(fromRect.center); + gridPoints.AddRange(reroutePoints); + gridPoints.Add(toRect.center); + DrawNoodle(noodleGradient, noodlePath, noodleStroke, noodleThickness, gridPoints, + output.GetPortConnection(k).connectionLabel); + Rect r = fromRect; + r.width = 200; + Rect center = new Rect((toRect.center + fromRect.center) / 2f, new Vector2(100, 20)); + + // 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(12, 12)); + rect.position = new Vector2(rect.position.x - 6, rect.position.y - 6); + rect = GridToWindowRect(rect); + + // Draw selected reroute points with an outline + if (selectedReroutes.Contains(rerouteRef)) + { + GUI.color = NodeEditorPreferences.GetSettings().highlightColor; + GUI.DrawTexture(rect, NodeEditorResources.dotOuter); + } + Vector2 mousePos = Event.current.mousePosition; + + GUI.color = portColor; + GUI.DrawTexture(rect, NodeEditorResources.dot); + if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef); + if (rect.Contains(mousePos)) hoveredReroute = rerouteRef; + + } + } + + protected virtual void DrawNodes() { Event e = Event.current; if (e.type == EventType.Layout) { selectionCache = new List(Selection.objects); @@ -429,115 +470,134 @@ private void DrawNodes() { } } else if (culledNodes.Contains(node)) continue; - if (e.type == EventType.Repaint) { - removeEntries.Clear(); - foreach (var kvp in _portConnectionPoints) - if (kvp.Key.node == node) removeEntries.Add(kvp.Key); - foreach (var k in removeEntries) _portConnectionPoints.Remove(k); - } + DrawNode(e, removeEntries, node, guiColor, mousePos, selectionBox, preSelection); + } - NodeEditor nodeEditor = NodeEditor.GetEditor(node, this); + if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray(); + EndZoomed(position, zoom, topPadding); - NodeEditor.portPositions.Clear(); + //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); + } - // Set default label width. This is potentially overridden in OnBodyGUI - EditorGUIUtility.labelWidth = 84; + protected virtual void DrawNode(Event e, List removeEntries, Node node, Color guiColor, Vector2 mousePos, + Rect selectionBox, List preSelection) + { + if (e.type == EventType.Repaint) + { + removeEntries.Clear(); + foreach (var kvp in _portConnectionPoints) + if (kvp.Key.node == node) + removeEntries.Add(kvp.Key); + foreach (var k in removeEntries) _portConnectionPoints.Remove(k); + } - //Get node position - Vector2 nodePos = GridToWindowPositionNoClipped(node.position); + NodeEditor nodeEditor = NodeEditor.GetEditor(node, this); - GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); + NodeEditor.portPositions.Clear(); - bool selected = selectionCache.Contains(graph.nodes[n]); + // Set default label width. This is potentially overridden in OnBodyGUI + EditorGUIUtility.labelWidth = 84; - if (selected) { - GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); - GUIStyle highlightStyle = new GUIStyle(nodeEditor.GetBodyHighlightStyle()); - highlightStyle.padding = style.padding; - style.padding = new RectOffset(); - GUI.color = nodeEditor.GetTint(); - GUILayout.BeginVertical(style); - GUI.color = NodeEditorPreferences.GetSettings().highlightColor; - GUILayout.BeginVertical(new GUIStyle(highlightStyle)); - } else { - GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); - GUI.color = nodeEditor.GetTint(); - GUILayout.BeginVertical(style); - } + //Get node position + Vector2 nodePos = GridToWindowPositionNoClipped(node.position); - GUI.color = guiColor; - EditorGUI.BeginChangeCheck(); + GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); - //Draw node contents - nodeEditor.OnHeaderGUI(); - nodeEditor.OnBodyGUI(); + bool selected = selectionCache.Contains(node); - //If user changed a value, notify other scripts through onUpdateNode - if (EditorGUI.EndChangeCheck()) { - if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); - EditorUtility.SetDirty(node); - nodeEditor.serializedObject.ApplyModifiedProperties(); - } + if (selected) + { + GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); + GUIStyle highlightStyle = new GUIStyle(nodeEditor.GetBodyHighlightStyle()); + highlightStyle.padding = style.padding; + style.padding = new RectOffset(); + GUI.color = nodeEditor.GetTint(); + GUILayout.BeginVertical(style); + GUI.color = NodeEditorPreferences.GetSettings().highlightColor; + GUILayout.BeginVertical(new GUIStyle(highlightStyle)); + } + else + { + GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); + GUI.color = nodeEditor.GetTint(); + GUILayout.BeginVertical(style); + } - GUILayout.EndVertical(); + GUI.color = guiColor; + EditorGUI.BeginChangeCheck(); - //Cache data about the node for next frame - if (e.type == EventType.Repaint) { - Vector2 size = GUILayoutUtility.GetLastRect().size; - if (nodeSizes.ContainsKey(node)) nodeSizes[node] = size; - else nodeSizes.Add(node, size); + //Draw node contents + nodeEditor.OnHeaderGUI(); + nodeEditor.OnBodyGUI(); - foreach (var kvp in NodeEditor.portPositions) { - Vector2 portHandlePos = kvp.Value; - portHandlePos += node.position; - Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16); - portConnectionPoints[kvp.Key] = rect; - } + //If user changed a value, notify other scripts through onUpdateNode + if (EditorGUI.EndChangeCheck()) + { + if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); + EditorUtility.SetDirty(node); + nodeEditor.serializedObject.ApplyModifiedProperties(); + } + + GUILayout.EndVertical(); + + //Cache data about the node for next frame + if (e.type == EventType.Repaint) + { + Vector2 size = GUILayoutUtility.GetLastRect().size; + if (nodeSizes.ContainsKey(node)) nodeSizes[node] = size; + else nodeSizes.Add(node, size); + + foreach (var kvp in NodeEditor.portPositions) + { + Vector2 portHandlePos = kvp.Value; + portHandlePos += node.position; + Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16); + portConnectionPoints[kvp.Key] = rect; } + } - if (selected) GUILayout.EndVertical(); + if (selected) GUILayout.EndVertical(); - if (e.type != EventType.Layout) { - //Check if we are hovering this node - Vector2 nodeSize = GUILayoutUtility.GetLastRect().size; - Rect windowRect = new Rect(nodePos, nodeSize); - if (windowRect.Contains(mousePos)) hoveredNode = node; + if (e.type != EventType.Layout) + { + //Check if we are hovering this node + Vector2 nodeSize = GUILayoutUtility.GetLastRect().size; + Rect windowRect = new Rect(nodePos, nodeSize); + if (windowRect.Contains(mousePos)) hoveredNode = node; - //If dragging a selection box, add nodes inside to selection - if (currentActivity == NodeActivity.DragGrid) { - if (windowRect.Overlaps(selectionBox)) preSelection.Add(node); - } + //If dragging a selection box, add nodes inside to selection + if (currentActivity == NodeActivity.DragGrid) + { + if (windowRect.Overlaps(selectionBox)) preSelection.Add(node); + } - //Check if we are hovering any of this nodes ports - //Check input ports - foreach (XNode.NodePort input in node.Inputs) { - //Check if port rect is available - if (!portConnectionPoints.ContainsKey(input)) continue; - 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 = GridToWindowRectNoClipped(portConnectionPoints[output]); - if (r.Contains(mousePos)) hoveredPort = output; - } + //Check if we are hovering any of this nodes ports + //Check input ports + foreach (XNode.NodePort input in node.Inputs) + { + //Check if port rect is available + if (!portConnectionPoints.ContainsKey(input)) continue; + Rect r = GridToWindowRectNoClipped(portConnectionPoints[input]); + if (r.Contains(mousePos)) hoveredPort = input; } - GUILayout.EndArea(); + //Check all output ports + foreach (XNode.NodePort output in node.Outputs) + { + //Check if port rect is available + if (!portConnectionPoints.ContainsKey(output)) continue; + Rect r = GridToWindowRectNoClipped(portConnectionPoints[output]); + if (r.Contains(mousePos)) hoveredPort = output; + } } - if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray(); - 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); + GUILayout.EndArea(); } - private bool ShouldBeCulled(XNode.Node node) { + protected virtual bool ShouldBeCulled(XNode.Node node) { Vector2 nodePos = GridToWindowPositionNoClipped(node.position); if (nodePos.x / _zoom > position.width) return true; // Right @@ -550,7 +610,7 @@ private bool ShouldBeCulled(XNode.Node node) { return false; } - private void DrawTooltip() { + protected virtual void DrawTooltip() { if (hoveredPort != null && NodeEditorPreferences.GetSettings().portTooltips && graphEditor != null) { string tooltip = graphEditor.GetPortTooltip(hoveredPort); if (string.IsNullOrEmpty(tooltip)) return; diff --git a/Scripts/Editor/NodeEditorResources.cs b/Scripts/Editor/NodeEditorResources.cs old mode 100644 new mode 100755 index 0a84e0a0..846899f7 --- a/Scripts/Editor/NodeEditorResources.cs +++ b/Scripts/Editor/NodeEditorResources.cs @@ -18,12 +18,16 @@ public static class NodeEditorResources { public static Styles _styles = null; public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } } public class Styles { - public GUIStyle inputPort, nodeHeader, nodeBody, tooltip, nodeHighlight; + public GUIStyle inputPort, nodeHeader, nodeBody, tooltip, nodeHighlight, connectionLabel; public Styles() { GUIStyle baseStyle = new GUIStyle("Label"); baseStyle.fixedHeight = 18; + connectionLabel = new GUIStyle(EditorStyles.boldLabel); + connectionLabel.richText = true; + connectionLabel.alignment = TextAnchor.MiddleCenter; + inputPort = new GUIStyle(baseStyle); inputPort.alignment = TextAnchor.UpperLeft; inputPort.padding.left = 10; diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs old mode 100644 new mode 100755 index 4f0a102c..2ce3f478 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -104,7 +104,7 @@ private static void OnSelectionChanged() { } /// Make sure the graph editor is assigned and to the right object - private void ValidateGraphEditor() { + protected void ValidateGraphEditor() { NodeGraphEditor graphEditor = NodeGraphEditor.GetEditor(graph, this); if (this.graphEditor != graphEditor && graphEditor != null) { this.graphEditor = graphEditor; diff --git a/Scripts/Node.cs b/Scripts/Node.cs old mode 100644 new mode 100755 index 6744cc50..700606b5 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -111,7 +111,7 @@ public void ClearInstancePorts() { /// Position on the [SerializeField] public Vector2 position; /// It is recommended not to modify these at hand. Instead, see and - [SerializeField] private NodePortDictionary ports = new NodePortDictionary(); + [SerializeField] protected NodePortDictionary ports = new NodePortDictionary(); /// Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable public static NodeGraph graphHotfix; @@ -140,14 +140,14 @@ public void VerifyConnections() { /// Convenience function. /// /// - public NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + public virtual NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName); } /// Convenience function. /// /// - public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { + public virtual NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName); } @@ -387,7 +387,7 @@ public NodeWidthAttribute(int width) { } #endregion - [Serializable] private class NodePortDictionary : Dictionary, ISerializationCallbackReceiver { + [Serializable] protected class NodePortDictionary : Dictionary, ISerializationCallbackReceiver { [SerializeField] private List keys = new List(); [SerializeField] private List values = new List(); diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs old mode 100644 new mode 100755 index b2f1ad1d..88c9c862 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -19,7 +19,7 @@ public NodePort Connection { } } - public IO direction { + public IO direction { get { return _direction; } internal set { _direction = value; } } @@ -202,7 +202,8 @@ public int GetInputSum(int fallback) { /// Connect this to another /// The to connect to - public void Connect(NodePort port) { + /// The optional label of the connection + public void Connect(NodePort port, string connectionLabel = null) { if (connections == null) connections = new List(); if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; } if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; } @@ -214,9 +215,9 @@ public void Connect(NodePort port) { #endif if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); } if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); } - connections.Add(new PortConnection(port)); + connections.Add(new PortConnection(port, connectionLabel)); if (port.connections == null) port.connections = new List(); - if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this)); + if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this, connectionLabel)); node.OnCreateConnection(this, port); port.node.OnCreateConnection(this, port); } @@ -230,6 +231,11 @@ public List GetConnections() { return result; } + public PortConnection GetPortConnection(int index) + { + return connections[index]; + } + public NodePort GetConnection(int i) { //If the connection is broken for some reason, remove it. if (connections[i].node == null || string.IsNullOrEmpty(connections[i].fieldName)) { @@ -391,28 +397,5 @@ public void Redirect(List oldNodes, List newNodes) { if (index >= 0) connection.node = newNodes[index]; } } - - [Serializable] - private class PortConnection { - [SerializeField] public string fieldName; - [SerializeField] public Node node; - public NodePort Port { get { return port != null ? port : port = GetPort(); } } - - [NonSerialized] private NodePort port; - /// Extra connection path points for organization - [SerializeField] public List reroutePoints = new List(); - - public PortConnection(NodePort port) { - this.port = port; - node = port.node; - fieldName = port.fieldName; - } - - /// Returns the port that this points to - private NodePort GetPort() { - if (node == null || string.IsNullOrEmpty(fieldName)) return null; - return node.GetPort(fieldName); - } - } } } \ No newline at end of file diff --git a/Scripts/PortConnection.cs b/Scripts/PortConnection.cs new file mode 100644 index 00000000..da8d56ee --- /dev/null +++ b/Scripts/PortConnection.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace XNode +{ + [Serializable] + public class PortConnection { + [SerializeField] public string fieldName; + [SerializeField] public string connectionLabel; + [SerializeField] public Node node; + public NodePort Port { get { return port != null ? port : port = GetPort(); } } + + [NonSerialized] protected NodePort port; + /// Extra connection path points for organization + [SerializeField] public List reroutePoints = new List(); + + public PortConnection(NodePort port, string connectionLabel = null) { + this.port = port; + node = port.node; + fieldName = port.fieldName; + this.connectionLabel = connectionLabel; + } + + /// Returns the port that this points to + private NodePort GetPort() { + if (node == null || string.IsNullOrEmpty(fieldName)) return null; + return node.GetPort(fieldName); + } + } +} \ No newline at end of file diff --git a/Scripts/PortConnection.cs.meta b/Scripts/PortConnection.cs.meta new file mode 100644 index 00000000..3bc2d04c --- /dev/null +++ b/Scripts/PortConnection.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 82f6ec040528431e87c5a15e2092f31e +timeCreated: 1595343084 \ No newline at end of file