diff --git a/Example/ExampleNodeGraph.cs b/Example/ExampleNodeGraph.cs index 8b6f750..8b09ece 100644 --- a/Example/ExampleNodeGraph.cs +++ b/Example/ExampleNodeGraph.cs @@ -1,7 +1,7 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; +using UnityEngine; using System; +using XNode; + /// Defines an example nodegraph. [Serializable, CreateAssetMenu(fileName = "ExampleNodeGraph", menuName = "Node Graph/Example")] public class ExampleNodeGraph : NodeGraph { diff --git a/Example/Nodes/DisplayValue.cs b/Example/Nodes/DisplayValue.cs index 4e22d9c..d36b4d4 100644 --- a/Example/Nodes/DisplayValue.cs +++ b/Example/Nodes/DisplayValue.cs @@ -1,4 +1,6 @@ -namespace BasicNodes { +using XNode; + +namespace BasicNodes { public class DisplayValue : Node { [Input(ShowBackingValue.Never)] public object value; diff --git a/Example/Nodes/Editor/DisplayValueEditor.cs b/Example/Nodes/Editor/DisplayValueEditor.cs index 0cc9c67..8e0b985 100644 --- a/Example/Nodes/Editor/DisplayValueEditor.cs +++ b/Example/Nodes/Editor/DisplayValueEditor.cs @@ -1,7 +1,5 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEditor; -using UnityEngine; +using UnityEditor; +using XNodeEditor; namespace BasicNodes { [CustomNodeEditor(typeof(DisplayValue))] diff --git a/Example/Nodes/MathNode.cs b/Example/Nodes/MathNode.cs index bfa9630..25576b5 100644 --- a/Example/Nodes/MathNode.cs +++ b/Example/Nodes/MathNode.cs @@ -1,4 +1,6 @@ -namespace BasicNodes { +using XNode; + +namespace BasicNodes { [System.Serializable] public class MathNode : Node { // Adding [Input] or [Output] is all you need to do to register a field as a valid port on your node diff --git a/Example/Nodes/Vector.cs b/Example/Nodes/Vector.cs index 3d8b0a8..b866283 100644 --- a/Example/Nodes/Vector.cs +++ b/Example/Nodes/Vector.cs @@ -1,4 +1,5 @@ using UnityEngine; +using XNode; namespace BasicNodes { public class Vector : Node { diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 10138c0..d5e7c0c 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -1,59 +1,61 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using UnityEditor; using UnityEngine; +using XNode; -/// Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. -public class NodeEditor { - /// Fires every whenever a node was modified through the editor - public static Action onUpdateNode; - public Node target; - public SerializedObject serializedObject; - public static Dictionary portPositions; +namespace XNodeEditor { + /// Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. + public class NodeEditor { + /// Fires every whenever a node was modified through the editor + public static Action onUpdateNode; + public Node target; + public SerializedObject serializedObject; + public static Dictionary portPositions; - /// Draws the node GUI. - /// Port handle positions need to be returned to the NodeEditorWindow - public void OnNodeGUI() { - OnHeaderGUI(); - OnBodyGUI(); - } + /// Draws the node GUI. + /// Port handle positions need to be returned to the NodeEditorWindow + public void OnNodeGUI() { + OnHeaderGUI(); + OnBodyGUI(); + } - public void OnHeaderGUI() { - GUI.color = Color.white; - string title = NodeEditorUtilities.PrettifyCamelCase(target.name); - GUILayout.Label(title, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); - } + public void OnHeaderGUI() { + GUI.color = Color.white; + string title = NodeEditorUtilities.PrettifyCamelCase(target.name); + GUILayout.Label(title, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); + } - /// Draws standard field editors for all public fields - public virtual void OnBodyGUI() { - string[] excludes = { "m_Script", "graph", "position", "ports" }; - portPositions = new Dictionary(); + /// Draws standard field editors for all public fields + public virtual void OnBodyGUI() { + string[] excludes = { "m_Script", "graph", "position", "ports" }; + portPositions = new Dictionary(); - SerializedProperty iterator = serializedObject.GetIterator(); - bool enterChildren = true; - EditorGUIUtility.labelWidth = 84; - while (iterator.NextVisible(enterChildren)) { - enterChildren = false; - if (excludes.Contains(iterator.name)) continue; - NodeEditorGUILayout.PropertyField(iterator, true); + SerializedProperty iterator = serializedObject.GetIterator(); + bool enterChildren = true; + EditorGUIUtility.labelWidth = 84; + while (iterator.NextVisible(enterChildren)) { + enterChildren = false; + if (excludes.Contains(iterator.name)) continue; + NodeEditorGUILayout.PropertyField(iterator, true); + } + } + + public virtual int GetWidth() { + return 200; } } - public virtual int GetWidth() { - return 200; - } -} - -[AttributeUsage(AttributeTargets.Class)] -public class CustomNodeEditorAttribute : Attribute { - public Type inspectedType { get { return _inspectedType; } } - private Type _inspectedType; - /// Tells a NodeEditor which Node type it is an editor for - /// Type that this editor can edit - /// Path to the node - public CustomNodeEditorAttribute(Type inspectedType) { - _inspectedType = inspectedType; + [AttributeUsage(AttributeTargets.Class)] + public class CustomNodeEditorAttribute : Attribute { + public Type inspectedType { get { return _inspectedType; } } + private Type _inspectedType; + /// Tells a NodeEditor which Node type it is an editor for + /// Type that this editor can edit + /// Path to the node + public CustomNodeEditorAttribute(Type inspectedType) { + _inspectedType = inspectedType; + } } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 8db4d10..14d19e0 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -1,145 +1,146 @@ using System; -using System.Collections; -using System.Collections.Generic; using UnityEditor; using UnityEngine; +using XNode; -public partial class NodeEditorWindow { +namespace XNodeEditor { + public partial class NodeEditorWindow { - public static bool isPanning { get; private set; } - public static Vector2 dragOffset; + public static bool isPanning { get; private set; } + public static Vector2 dragOffset; - private bool IsDraggingNode { get { return draggedNode != null; } } - private bool IsDraggingPort { get { return draggedOutput != null; } } - private bool IsHoveringPort { get { return hoveredPort != null; } } - private bool IsHoveringNode { get { return hoveredNode != null; } } - private bool HasSelectedNode { get { return selectedNode != null; } } + private bool IsDraggingNode { get { return draggedNode != null; } } + private bool IsDraggingPort { get { return draggedOutput != null; } } + private bool IsHoveringPort { get { return hoveredPort != null; } } + private bool IsHoveringNode { get { return hoveredNode != null; } } + private bool HasSelectedNode { get { return selectedNode != null; } } - private Node hoveredNode = null; + private Node hoveredNode = null; - [NonSerialized] private Node selectedNode = null; - [NonSerialized] private Node draggedNode = null; - [NonSerialized] private NodePort hoveredPort = null; - [NonSerialized] private NodePort draggedOutput = null; - [NonSerialized] private NodePort draggedOutputTarget = null; + [NonSerialized] private Node selectedNode = null; + [NonSerialized] private Node draggedNode = null; + [NonSerialized] private NodePort hoveredPort = null; + [NonSerialized] private NodePort draggedOutput = null; + [NonSerialized] private NodePort draggedOutputTarget = null; - private Rect nodeRects; + private Rect nodeRects; - public void Controls() { - wantsMouseMove = true; + public void Controls() { + wantsMouseMove = true; - Event e = Event.current; - switch (e.type) { - case EventType.MouseMove: - break; - case EventType.ScrollWheel: - if (e.delta.y > 0) zoom += 0.1f * zoom; - else zoom -= 0.1f * zoom; - break; - case EventType.MouseDrag: - if (e.button == 0) { - if (IsDraggingPort) { - if (IsHoveringPort && hoveredPort.IsInput) { - if (!draggedOutput.IsConnectedTo(hoveredPort)) { - draggedOutputTarget = hoveredPort; + Event e = Event.current; + switch (e.type) { + case EventType.MouseMove: + break; + case EventType.ScrollWheel: + if (e.delta.y > 0) zoom += 0.1f * zoom; + else zoom -= 0.1f * zoom; + break; + case EventType.MouseDrag: + if (e.button == 0) { + if (IsDraggingPort) { + if (IsHoveringPort && hoveredPort.IsInput) { + if (!draggedOutput.IsConnectedTo(hoveredPort)) { + draggedOutputTarget = hoveredPort; + } + } else { + draggedOutputTarget = null; } + Repaint(); + } else if (IsDraggingNode) { + draggedNode.position = WindowToGridPosition(e.mousePosition) + dragOffset; + Repaint(); + } + } else if (e.button == 1) { + panOffset += e.delta * zoom; + isPanning = true; + } + break; + case EventType.KeyDown: + if (e.keyCode == KeyCode.F) Home(); + break; + case EventType.MouseDown: + Repaint(); + SelectNode(hoveredNode); + if (IsHoveringPort) { + if (hoveredPort.IsOutput) { + draggedOutput = hoveredPort; } else { + hoveredPort.VerifyConnections(); + if (hoveredPort.IsConnected) { + Node node = hoveredPort.node; + NodePort output = hoveredPort.Connection; + hoveredPort.Disconnect(output); + draggedOutput = output; + draggedOutputTarget = hoveredPort; + NodeEditor.onUpdateNode(node); + } + } + } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { + draggedNode = hoveredNode; + dragOffset = hoveredNode.position - WindowToGridPosition(e.mousePosition); + } + break; + case EventType.MouseUp: + if (e.button == 0) { + //Port drag release + if (IsDraggingPort) { + //If connection is valid, save it + if (draggedOutputTarget != null) { + Node node = draggedOutputTarget.node; + if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget); + NodeEditor.onUpdateNode(node); + EditorUtility.SetDirty(graph); + } + //Release dragged connection + draggedOutput = null; draggedOutputTarget = null; - } - Repaint(); - } else if (IsDraggingNode) { - draggedNode.position = WindowToGridPosition(e.mousePosition) + dragOffset; - Repaint(); - } - } else if (e.button == 1) { - panOffset += e.delta * zoom; - isPanning = true; - } - break; - case EventType.KeyDown: - if (e.keyCode == KeyCode.F) Home(); - break; - case EventType.MouseDown: - Repaint(); - SelectNode(hoveredNode); - if (IsHoveringPort) { - if (hoveredPort.IsOutput) { - draggedOutput = hoveredPort; - } else { - hoveredPort.VerifyConnections(); - if (hoveredPort.IsConnected) { - Node node = hoveredPort.node; - NodePort output = hoveredPort.Connection; - hoveredPort.Disconnect(output); - draggedOutput = output; - draggedOutputTarget = hoveredPort; - NodeEditor.onUpdateNode(node); - } - } - } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { - draggedNode = hoveredNode; - dragOffset = hoveredNode.position - WindowToGridPosition(e.mousePosition); - } - break; - case EventType.MouseUp: - if (e.button == 0) { - //Port drag release - if (IsDraggingPort) { - //If connection is valid, save it - if (draggedOutputTarget != null) { - Node node = draggedOutputTarget.node; - if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget); - NodeEditor.onUpdateNode(node); EditorUtility.SetDirty(graph); + Repaint(); + } else if (IsDraggingNode) { + draggedNode = null; } - //Release dragged connection - draggedOutput = null; - draggedOutputTarget = null; - EditorUtility.SetDirty(graph); - Repaint(); - } else if (IsDraggingNode) { - draggedNode = null; + } else if (e.button == 1) { + if (!isPanning) ShowContextMenu(); + isPanning = false; } - } else if (e.button == 1) { - if (!isPanning) ShowContextMenu(); - isPanning = false; - } - AssetDatabase.SaveAssets(); - break; + AssetDatabase.SaveAssets(); + break; + } } - } - /// Puts all nodes in focus. If no nodes are present, resets view to - public void Home() { - zoom = 2; - panOffset = Vector2.zero; - } - - public void CreateNode(Type type, Vector2 position) { - Node node = graph.AddNode(type); - node.position = position; - Repaint(); - } - - /// 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); - col.a = 0.6f; - DrawConnection(from, to, col); + /// Puts all nodes in focus. If no nodes are present, resets view to + public void Home() { + zoom = 2; + panOffset = Vector2.zero; } - } - bool IsHoveringTitle(Node node) { - Vector2 mousePos = Event.current.mousePosition; - //Get node position - Vector2 nodePos = GridToWindowPosition(node.position); - float width = 200; - if (nodeWidths.ContainsKey(node)) width = nodeWidths[node]; - Rect windowRect = new Rect(nodePos, new Vector2(width / zoom, 30 / zoom)); - return windowRect.Contains(mousePos); + public void CreateNode(Type type, Vector2 position) { + Node node = graph.AddNode(type); + node.position = position; + Repaint(); + } + + /// 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); + col.a = 0.6f; + DrawConnection(from, to, col); + } + } + + bool IsHoveringTitle(Node node) { + Vector2 mousePos = Event.current.mousePosition; + //Get node position + Vector2 nodePos = GridToWindowPosition(node.position); + float width = 200; + if (nodeWidths.ContainsKey(node)) width = nodeWidths[node]; + Rect windowRect = new Rect(nodePos, new Vector2(width / zoom, 30 / zoom)); + return windowRect.Contains(mousePos); + } } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs b/Scripts/Editor/NodeEditorAssetModProcessor.cs index a1756f5..aa50f02 100644 --- a/Scripts/Editor/NodeEditorAssetModProcessor.cs +++ b/Scripts/Editor/NodeEditorAssetModProcessor.cs @@ -1,35 +1,36 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEditor; +using UnityEditor; using UnityEngine; +using XNode; -public class NodeEditorAssetModProcessor : UnityEditor.AssetModificationProcessor { - public static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options) { - UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath(path); +namespace XNodeEditor { + public class NodeEditorAssetModProcessor : UnityEditor.AssetModificationProcessor { + public static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options) { + UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath(path); - if (!(obj is UnityEditor.MonoScript)) return AssetDeleteResult.DidNotDelete; + if (!(obj is UnityEditor.MonoScript)) return AssetDeleteResult.DidNotDelete; - UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; - System.Type scriptType = script.GetClass(); + UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; + System.Type scriptType = script.GetClass(); - if (scriptType != typeof(Node) && !scriptType.IsSubclassOf(typeof(Node))) return AssetDeleteResult.DidNotDelete; + if (scriptType != typeof(Node) && !scriptType.IsSubclassOf(typeof(Node))) return AssetDeleteResult.DidNotDelete; - //Find ScriptableObjects using this script - string[] guids = AssetDatabase.FindAssets("t:" + scriptType); - for (int i = 0; i < guids.Length; i++) { - string assetpath = AssetDatabase.GUIDToAssetPath(guids[i]); - Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetpath); - for (int k = 0; k < objs.Length; k++) { - Node node = objs[k] as Node; - if (node.GetType() == scriptType) { - if (node != null && node.graph != null) { - Debug.LogWarning(node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph); - node.graph.RemoveNode(node); + //Find ScriptableObjects using this script + string[] guids = AssetDatabase.FindAssets("t:" + scriptType); + for (int i = 0; i < guids.Length; i++) { + string assetpath = AssetDatabase.GUIDToAssetPath(guids[i]); + Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetpath); + for (int k = 0; k < objs.Length; k++) { + Node node = objs[k] as Node; + if (node.GetType() == scriptType) { + if (node != null && node.graph != null) { + Debug.LogWarning(node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph); + node.graph.RemoveNode(node); + } } } - } + } + return AssetDeleteResult.DidNotDelete; } - return AssetDeleteResult.DidNotDelete; } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 6032fe2..5523abd 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -1,286 +1,288 @@ using System; -using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; +using XNode; -/// Contains GUI methods -public partial class NodeEditorWindow { +namespace XNodeEditor { + /// Contains GUI methods + public partial class NodeEditorWindow { - private void OnGUI() { - Event e = Event.current; - Matrix4x4 m = GUI.matrix; - Controls(); + private void OnGUI() { + Event e = Event.current; + Matrix4x4 m = GUI.matrix; + Controls(); - DrawGrid(position, zoom, panOffset); - DrawConnections(); - DrawDraggedConnection(); - DrawNodes(); - DrawTooltip(); + DrawGrid(position, zoom, panOffset); + DrawConnections(); + DrawDraggedConnection(); + DrawNodes(); + DrawTooltip(); - GUI.matrix = m; - } - - public static void BeginZoomed(Rect rect, float zoom) { - GUI.EndClip(); - - GUIUtility.ScaleAroundPivot(Vector2.one / zoom, rect.size * 0.5f); - Vector4 padding = new Vector4(0, 22, 0, 0); - padding *= zoom; - GUI.BeginClip(new Rect(-((rect.width * zoom) - rect.width) * 0.5f, -(((rect.height * zoom) - rect.height) * 0.5f) + (22 * zoom), - rect.width * zoom, - rect.height * zoom)); - } - - public static void EndZoomed(Rect rect, float zoom) { - GUIUtility.ScaleAroundPivot(Vector2.one * zoom, rect.size * 0.5f); - Vector3 offset = new Vector3( - (((rect.width * zoom) - rect.width) * 0.5f), - (((rect.height * zoom) - rect.height) * 0.5f) + (-22 * zoom) + 22, - 0); - GUI.matrix = Matrix4x4.TRS(offset, Quaternion.identity, Vector3.one); - } - - public static void DrawGrid(Rect rect, float zoom, Vector2 panOffset) { - - rect.position = Vector2.zero; - - Vector2 center = rect.size / 2f; - Texture2D gridTex = NodeEditorResources.gridTexture; - Texture2D crossTex = NodeEditorResources.crossTexture; - - // Offset from origin in tile units - float xOffset = -(center.x * zoom + panOffset.x) / gridTex.width; - float yOffset = ((center.y - rect.size.y) * zoom + panOffset.y) / gridTex.height; - - Vector2 tileOffset = new Vector2(xOffset, yOffset); - - // Amount of tiles - float tileAmountX = Mathf.Round(rect.size.x * zoom) / gridTex.width; - float tileAmountY = Mathf.Round(rect.size.y * zoom) / gridTex.height; - - Vector2 tileAmount = new Vector2(tileAmountX, tileAmountY); - - // Draw tiled background - GUI.DrawTextureWithTexCoords(rect, gridTex, new Rect(tileOffset, tileAmount)); - GUI.DrawTextureWithTexCoords(rect, crossTex, new Rect(tileOffset + new Vector2(0.5f, 0.5f), tileAmount)); - } - - public static bool DropdownButton(string name, float width) { - return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width)); - } - - /// Show right-click context menu - public void ShowContextMenu() { - GenericMenu contextMenu = new GenericMenu(); - Vector2 pos = WindowToGridPosition(Event.current.mousePosition); - - if (hoveredNode != null) { - Node node = hoveredNode; - contextMenu.AddItem(new GUIContent("Remove"), false, () => graph.RemoveNode(node)); - } else { - for (int i = 0; i < nodeTypes.Length; i++) { - Type type = nodeTypes[i]; - - string name = nodeTypes[i].ToString().Replace('.', '/'); - Node.CreateNodeMenuAttribute attrib; - if (NodeEditorUtilities.GetAttrib(type, out attrib)) { - name = attrib.menuName; - } - contextMenu.AddItem(new GUIContent(name), false, () => { - CreateNode(type, pos); - }); - } + GUI.matrix = m; } - contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); - } - /// Draw a bezier from startpoint to endpoint, both in grid coordinates - public void DrawConnection(Vector2 startPoint, Vector2 endPoint, Color col) { - startPoint = GridToWindowPosition(startPoint); - endPoint = GridToWindowPosition(endPoint); + public static void BeginZoomed(Rect rect, float zoom) { + GUI.EndClip(); - Vector2 startTangent = startPoint; - if (startPoint.x < endPoint.x) startTangent.x = Mathf.LerpUnclamped(startPoint.x, endPoint.x, 0.7f); - else startTangent.x = Mathf.LerpUnclamped(startPoint.x, endPoint.x, -0.7f); + GUIUtility.ScaleAroundPivot(Vector2.one / zoom, rect.size * 0.5f); + Vector4 padding = new Vector4(0, 22, 0, 0); + padding *= zoom; + GUI.BeginClip(new Rect(-((rect.width * zoom) - rect.width) * 0.5f, -(((rect.height * zoom) - rect.height) * 0.5f) + (22 * zoom), + rect.width * zoom, + rect.height * zoom)); + } - Vector2 endTangent = endPoint; - if (startPoint.x > endPoint.x) endTangent.x = Mathf.LerpUnclamped(endPoint.x, startPoint.x, -0.7f); - else endTangent.x = Mathf.LerpUnclamped(endPoint.x, startPoint.x, 0.7f); + public static void EndZoomed(Rect rect, float zoom) { + GUIUtility.ScaleAroundPivot(Vector2.one * zoom, rect.size * 0.5f); + Vector3 offset = new Vector3( + (((rect.width * zoom) - rect.width) * 0.5f), + (((rect.height * zoom) - rect.height) * 0.5f) + (-22 * zoom) + 22, + 0); + GUI.matrix = Matrix4x4.TRS(offset, Quaternion.identity, Vector3.one); + } - Handles.DrawBezier(startPoint, endPoint, startTangent, endTangent, col, null, 4); - } + public static void DrawGrid(Rect rect, float zoom, Vector2 panOffset) { - /// Draws all connections - public void DrawConnections() { - foreach (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; + rect.position = Vector2.zero; - foreach (NodePort output in node.Outputs) { - //Needs cleanup. Null checks are ugly - if (!portConnectionPoints.ContainsKey(output)) continue; - Vector2 from = _portConnectionPoints[output].center; - for (int k = 0; k < output.ConnectionCount; k++) { + Vector2 center = rect.size / 2f; + Texture2D gridTex = NodeEditorResources.gridTexture; + Texture2D crossTex = NodeEditorResources.crossTexture; - NodePort input = output.GetConnection(k); - 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; - DrawConnection(from, to, NodeEditorPreferences.GetTypeColor(output.ValueType)); + // Offset from origin in tile units + float xOffset = -(center.x * zoom + panOffset.x) / gridTex.width; + float yOffset = ((center.y - rect.size.y) * zoom + panOffset.y) / gridTex.height; + + Vector2 tileOffset = new Vector2(xOffset, yOffset); + + // Amount of tiles + float tileAmountX = Mathf.Round(rect.size.x * zoom) / gridTex.width; + float tileAmountY = Mathf.Round(rect.size.y * zoom) / gridTex.height; + + Vector2 tileAmount = new Vector2(tileAmountX, tileAmountY); + + // Draw tiled background + GUI.DrawTextureWithTexCoords(rect, gridTex, new Rect(tileOffset, tileAmount)); + GUI.DrawTextureWithTexCoords(rect, crossTex, new Rect(tileOffset + new Vector2(0.5f, 0.5f), tileAmount)); + } + + public static bool DropdownButton(string name, float width) { + return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width)); + } + + /// Show right-click context menu + public void ShowContextMenu() { + GenericMenu contextMenu = new GenericMenu(); + Vector2 pos = WindowToGridPosition(Event.current.mousePosition); + + if (hoveredNode != null) { + Node node = hoveredNode; + contextMenu.AddItem(new GUIContent("Remove"), false, () => graph.RemoveNode(node)); + } else { + for (int i = 0; i < nodeTypes.Length; i++) { + Type type = nodeTypes[i]; + + string name = nodeTypes[i].ToString().Replace('.', '/'); + Node.CreateNodeMenuAttribute attrib; + if (NodeEditorUtilities.GetAttrib(type, out attrib)) { + name = attrib.menuName; + } + contextMenu.AddItem(new GUIContent(name), false, () => { + CreateNode(type, pos); + }); + } + } + contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); + } + + /// Draw a bezier from startpoint to endpoint, both in grid coordinates + public void DrawConnection(Vector2 startPoint, Vector2 endPoint, Color col) { + startPoint = GridToWindowPosition(startPoint); + endPoint = GridToWindowPosition(endPoint); + + Vector2 startTangent = startPoint; + if (startPoint.x < endPoint.x) startTangent.x = Mathf.LerpUnclamped(startPoint.x, endPoint.x, 0.7f); + else startTangent.x = Mathf.LerpUnclamped(startPoint.x, endPoint.x, -0.7f); + + Vector2 endTangent = endPoint; + if (startPoint.x > endPoint.x) endTangent.x = Mathf.LerpUnclamped(endPoint.x, startPoint.x, -0.7f); + else endTangent.x = Mathf.LerpUnclamped(endPoint.x, startPoint.x, 0.7f); + + Handles.DrawBezier(startPoint, endPoint, startTangent, endTangent, col, null, 4); + } + + /// Draws all connections + public void DrawConnections() { + foreach (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; + + foreach (NodePort output in node.Outputs) { + //Needs cleanup. Null checks are ugly + if (!portConnectionPoints.ContainsKey(output)) continue; + Vector2 from = _portConnectionPoints[output].center; + for (int k = 0; k < output.ConnectionCount; k++) { + + NodePort input = output.GetConnection(k); + 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; + DrawConnection(from, to, NodeEditorPreferences.GetTypeColor(output.ValueType)); + } } } } - } - - private void DrawNodes() { - Event e = Event.current; - if (e.type == EventType.Repaint) { - portConnectionPoints.Clear(); - nodeWidths.Clear(); - } - - //Selected node is hashed before and after node GUI to detect changes - int nodeHash = 0; - System.Reflection.MethodInfo onValidate = null; - if (selectedNode != null) { - onValidate = selectedNode.GetType().GetMethod("OnValidate"); - if (onValidate != null) nodeHash = selectedNode.GetHashCode(); - } - - BeginZoomed(position, zoom); - - Vector2 mousePos = Event.current.mousePosition; - - if (e.type != EventType.Layout) { - hoveredNode = null; - hoveredPort = null; - } - - for (int n = 0; n < graph.nodes.Count; n++) { - while (graph.nodes[n] == null) graph.nodes.RemoveAt(n); - if (n >= graph.nodes.Count) return; - Node node = graph.nodes[n]; - - NodeEditor nodeEditor = GetNodeEditor(node.GetType()); - nodeEditor.target = node; - nodeEditor.serializedObject = new SerializedObject(node); - NodeEditor.portPositions = new Dictionary(); - - //Get node position - Vector2 nodePos = GridToWindowPositionNoClipped(node.position); - - GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); - - GUIStyle style = NodeEditorResources.styles.nodeBody; - GUILayout.BeginVertical(new GUIStyle(style)); - EditorGUI.BeginChangeCheck(); - - //Draw node contents - nodeEditor.OnNodeGUI(); - - //Apply - nodeEditor.serializedObject.ApplyModifiedProperties(); - - //If user changed a value, notify other scripts through onUpdateNode - if (EditorGUI.EndChangeCheck()) { - if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); - } + private void DrawNodes() { + Event e = Event.current; if (e.type == EventType.Repaint) { - nodeWidths.Add(node, nodeEditor.GetWidth()); - - 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.Add(kvp.Key, rect); - } + portConnectionPoints.Clear(); + nodeWidths.Clear(); } - GUILayout.EndVertical(); + //Selected node is hashed before and after node GUI to detect changes + int nodeHash = 0; + System.Reflection.MethodInfo onValidate = null; + if (selectedNode != null) { + onValidate = selectedNode.GetType().GetMethod("OnValidate"); + if (onValidate != null) nodeHash = selectedNode.GetHashCode(); + } + + BeginZoomed(position, zoom); + + Vector2 mousePos = Event.current.mousePosition; 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; + hoveredNode = null; + hoveredPort = null; + } - //Check if we are hovering any of this nodes ports - //Check input ports - foreach (NodePort input in node.Inputs) { - //Check if port rect is available - if (!portConnectionPoints.ContainsKey(input)) continue; - Rect r = GridToWindowRect(portConnectionPoints[input]); - if (r.Contains(mousePos)) hoveredPort = input; + for (int n = 0; n < graph.nodes.Count; n++) { + while (graph.nodes[n] == null) graph.nodes.RemoveAt(n); + if (n >= graph.nodes.Count) return; + Node node = graph.nodes[n]; + + NodeEditor nodeEditor = GetNodeEditor(node.GetType()); + nodeEditor.target = node; + nodeEditor.serializedObject = new SerializedObject(node); + NodeEditor.portPositions = new Dictionary(); + + //Get node position + Vector2 nodePos = GridToWindowPositionNoClipped(node.position); + + GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); + + GUIStyle style = NodeEditorResources.styles.nodeBody; + GUILayout.BeginVertical(new GUIStyle(style)); + EditorGUI.BeginChangeCheck(); + + //Draw node contents + nodeEditor.OnNodeGUI(); + + //Apply + nodeEditor.serializedObject.ApplyModifiedProperties(); + + //If user changed a value, notify other scripts through onUpdateNode + if (EditorGUI.EndChangeCheck()) { + if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); } - //Check all output ports - foreach (NodePort output in node.Outputs) { - //Check if port rect is available - if (!portConnectionPoints.ContainsKey(output)) continue; - Rect r = GridToWindowRect(portConnectionPoints[output]); - if (r.Contains(mousePos)) hoveredPort = output; + + if (e.type == EventType.Repaint) { + nodeWidths.Add(node, nodeEditor.GetWidth()); + + 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.Add(kvp.Key, rect); + } } + + 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; + + //Check if we are hovering any of this nodes ports + //Check input ports + foreach (NodePort input in node.Inputs) { + //Check if port rect is available + if (!portConnectionPoints.ContainsKey(input)) continue; + Rect r = GridToWindowRect(portConnectionPoints[input]); + if (r.Contains(mousePos)) hoveredPort = input; + } + //Check all output ports + foreach (NodePort output in node.Outputs) { + //Check if port rect is available + if (!portConnectionPoints.ContainsKey(output)) continue; + Rect r = GridToWindowRect(portConnectionPoints[output]); + if (r.Contains(mousePos)) hoveredPort = output; + } + } + + GUILayout.EndArea(); } - GUILayout.EndArea(); + EndZoomed(position, zoom); + + //If a change in hash 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 (selectedNode != null) { + if (onValidate != null && nodeHash != selectedNode.GetHashCode()) onValidate.Invoke(selectedNode, null); + } } - EndZoomed(position, zoom); - - //If a change in hash 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 (selectedNode != null) { - if (onValidate != null && nodeHash != selectedNode.GetHashCode()) onValidate.Invoke(selectedNode, null); + private void DrawTooltip() { + if (hoveredPort != null) { + Type type = hoveredPort.ValueType; + GUIContent content = new GUIContent(); + content.text = TypeToString(type); + Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); + Rect rect = new Rect(Event.current.mousePosition - (size), size); + EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip); + Repaint(); + } } - } - private void DrawTooltip() { - if (hoveredPort != null) { - Type type = hoveredPort.ValueType; - GUIContent content = new GUIContent(); - content.text = TypeToString(type); - Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); - Rect rect = new Rect(Event.current.mousePosition - (size), size); - EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip); - Repaint(); + private string TypeToString(Type type) { + if (type == null) return "null"; + if (type == typeof(float)) return "float"; + else if (type == typeof(int)) return "int"; + else if (type == typeof(long)) return "long"; + else if (type == typeof(double)) return "double"; + else if (type == typeof(string)) return "string"; + else if (type == typeof(bool)) return "bool"; + else if (type.IsGenericType) { + string s = ""; + Type genericType = type.GetGenericTypeDefinition(); + if (genericType == typeof(List<>)) s = "List"; + else s = type.GetGenericTypeDefinition().ToString(); + + Type[] types = type.GetGenericArguments(); + string[] stypes = new string[types.Length]; + for (int i = 0; i < types.Length; i++) { + stypes[i] = TypeToString(types[i]); + } + return s + "<" + string.Join(", ", stypes) + ">"; + } else if (type.IsArray) { + string rank = ""; + for (int i = 1; i < type.GetArrayRank(); i++) { + rank += ","; + } + Type elementType = type.GetElementType(); + if (!elementType.IsArray) return TypeToString(elementType) + "[" + rank + "]"; + else { + string s = TypeToString(elementType); + int i = s.IndexOf('['); + return s.Substring(0, i) + "[" + rank + "]" + s.Substring(i); + } + } else return hoveredPort.ValueType.ToString(); } } - - private string TypeToString(Type type) { - if (type == null) return "null"; - if (type == typeof(float)) return "float"; - else if (type == typeof(int)) return "int"; - else if (type == typeof(long)) return "long"; - else if (type == typeof(double)) return "double"; - else if (type == typeof(string)) return "string"; - else if (type == typeof(bool)) return "bool"; - else if (type.IsGenericType) { - string s = ""; - Type genericType = type.GetGenericTypeDefinition(); - if (genericType == typeof(List<>)) s = "List"; - else s = type.GetGenericTypeDefinition().ToString(); - - Type[] types = type.GetGenericArguments(); - string[] stypes = new string[types.Length]; - for (int i = 0; i < types.Length; i++) { - stypes[i] = TypeToString(types[i]); - } - return s + "<" + string.Join(", ", stypes) + ">"; - } else if (type.IsArray) { - string rank = ""; - for (int i = 1; i < type.GetArrayRank(); i++) { - rank += ","; - } - Type elementType = type.GetElementType(); - if (!elementType.IsArray) return TypeToString(elementType) + "[" + rank + "]"; - else { - string s = TypeToString(elementType); - int i = s.IndexOf('['); - return s.Substring(0, i) + "[" + rank + "]" + s.Substring(i); - } - } else return hoveredPort.ValueType.ToString(); - } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 8040942..37f0f75 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -1,39 +1,55 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using UnityEditor; using UnityEngine; +using XNode; -/// UNEC-specific version of -public static class NodeEditorGUILayout { +namespace XNodeEditor { + /// UNEC-specific version of + public static class NodeEditorGUILayout { - public static void PropertyField(SerializedProperty property, bool includeChildren = true) { - if (property == null) throw new NullReferenceException(); - Node node = property.serializedObject.targetObject as Node; - NodePort port = node.GetPort(property.name); + public static void PropertyField(SerializedProperty property, bool includeChildren = true) { + if (property == null) throw new NullReferenceException(); + Node node = property.serializedObject.targetObject as Node; + NodePort port = node.GetPort(property.name); - // If property is not a port, display a regular property field - if (port == null) EditorGUILayout.PropertyField(property, includeChildren, GUILayout.MinWidth(30)); - else { - Rect rect = new Rect(); + // If property is not a port, display a regular property field + if (port == null) EditorGUILayout.PropertyField(property, includeChildren, GUILayout.MinWidth(30)); + else { + Rect rect = new Rect(); - // If property is an input, display a regular property field and put a port handle on the left side - if (port.direction == NodePort.IO.Input) { - // Display a label if port is connected - if (port.IsConnected) EditorGUILayout.LabelField(property.displayName); - // Display an editable property field if port is not connected - else EditorGUILayout.PropertyField(property, includeChildren, GUILayout.MinWidth(30)); - rect = GUILayoutUtility.GetLastRect(); - rect.position = rect.position - new Vector2(16, 0); - // If property is an output, display a text label and put a port handle on the right side - } else if (port.direction == NodePort.IO.Output) { - EditorGUILayout.LabelField(property.displayName, NodeEditorResources.styles.outputPort, GUILayout.MinWidth(30)); - rect = GUILayoutUtility.GetLastRect(); - rect.position = rect.position + new Vector2(rect.width, 0); + // If property is an input, display a regular property field and put a port handle on the left side + if (port.direction == NodePort.IO.Input) { + // Display a label if port is connected + if (port.IsConnected) EditorGUILayout.LabelField(property.displayName); + // Display an editable property field if port is not connected + else EditorGUILayout.PropertyField(property, includeChildren, GUILayout.MinWidth(30)); + rect = GUILayoutUtility.GetLastRect(); + rect.position = rect.position - new Vector2(16, 0); + // If property is an output, display a text label and put a port handle on the right side + } else if (port.direction == NodePort.IO.Output) { + EditorGUILayout.LabelField(property.displayName, NodeEditorResources.styles.outputPort, GUILayout.MinWidth(30)); + rect = GUILayoutUtility.GetLastRect(); + rect.position = rect.position + new Vector2(rect.width, 0); + } + + rect.size = new Vector2(16, 16); + + DrawPortHandle(rect, port.ValueType); + + // Register the handle position + Vector2 portPos = rect.center; + if (NodeEditor.portPositions.ContainsKey(port)) NodeEditor.portPositions[port] = portPos; + else NodeEditor.portPositions.Add(port, portPos); } + } + public static void PortField(NodePort port) { + if (port == null) return; + EditorGUILayout.LabelField(port.fieldName.PrettifyCamelCase(), GUILayout.MinWidth(30)); + + Rect rect = GUILayoutUtility.GetLastRect(); + if (port.direction == NodePort.IO.Input) rect.position = rect.position - new Vector2(16, 0); + else if (port.direction == NodePort.IO.Output) rect.position = rect.position + new Vector2(rect.width, 0); rect.size = new Vector2(16, 16); DrawPortHandle(rect, port.ValueType); @@ -43,31 +59,14 @@ public static class NodeEditorGUILayout { if (NodeEditor.portPositions.ContainsKey(port)) NodeEditor.portPositions[port] = portPos; else NodeEditor.portPositions.Add(port, portPos); } - } - public static void PortField(NodePort port) { - if (port == null) return; - EditorGUILayout.LabelField(port.fieldName.PrettifyCamelCase(), GUILayout.MinWidth(30)); - - Rect rect = GUILayoutUtility.GetLastRect(); - if (port.direction == NodePort.IO.Input) rect.position = rect.position - new Vector2(16, 0); - else if (port.direction == NodePort.IO.Output) rect.position = rect.position + new Vector2(rect.width, 0); - rect.size = new Vector2(16, 16); - - DrawPortHandle(rect, port.ValueType); - - // Register the handle position - Vector2 portPos = rect.center; - if (NodeEditor.portPositions.ContainsKey(port)) NodeEditor.portPositions[port] = portPos; - else NodeEditor.portPositions.Add(port, portPos); - } - - private static void DrawPortHandle(Rect rect, Type type) { - Color col = GUI.color; - GUI.color = new Color32(90, 97, 105, 255); - GUI.DrawTexture(rect, NodeEditorResources.dotOuter); - GUI.color = NodeEditorPreferences.GetTypeColor(type); - GUI.DrawTexture(rect, NodeEditorResources.dot); - GUI.color = col; + private static void DrawPortHandle(Rect rect, Type type) { + Color col = GUI.color; + GUI.color = new Color32(90, 97, 105, 255); + GUI.DrawTexture(rect, NodeEditorResources.dotOuter); + GUI.color = NodeEditorPreferences.GetTypeColor(type); + GUI.DrawTexture(rect, NodeEditorResources.dot); + GUI.color = col; + } } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index 9d392b3..80412fc 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -1,98 +1,99 @@ using System; -using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; -public static class NodeEditorPreferences { +namespace XNodeEditor { + public static class NodeEditorPreferences { - /// Have we loaded the prefs yet - private static bool prefsLoaded = false; + /// Have we loaded the prefs yet + private static bool prefsLoaded = false; - private static Dictionary typeColors; - private static Dictionary generatedTypeColors; + private static Dictionary typeColors; + private static Dictionary generatedTypeColors; - [PreferenceItem("Node Editor")] - private static void PreferencesGUI() { - if (!prefsLoaded) LoadPrefs(); - EditorGUILayout.LabelField("Type colors", EditorStyles.boldLabel); + [PreferenceItem("Node Editor")] + private static void PreferencesGUI() { + if (!prefsLoaded) LoadPrefs(); + EditorGUILayout.LabelField("Type colors", EditorStyles.boldLabel); - string[] typeKeys = new string[typeColors.Count]; - typeColors.Keys.CopyTo(typeKeys, 0); + string[] typeKeys = new string[typeColors.Count]; + typeColors.Keys.CopyTo(typeKeys, 0); - foreach (var key in typeKeys) { - EditorGUILayout.BeginHorizontal(); - Color col = typeColors[key]; - col = EditorGUILayout.ColorField(key, col); - typeColors[key] = col; - if (!GUILayout.Toggle(true, "")) { - typeColors.Remove(key); + foreach (var key in typeKeys) { + EditorGUILayout.BeginHorizontal(); + Color col = typeColors[key]; + col = EditorGUILayout.ColorField(key, col); + typeColors[key] = col; + if (!GUILayout.Toggle(true, "")) { + typeColors.Remove(key); + SavePrefs(); + } + EditorGUILayout.EndHorizontal(); + } + if (GUI.changed) { SavePrefs(); } - EditorGUILayout.EndHorizontal(); - } - if (GUI.changed) { - SavePrefs(); - } - string[] generatedTypeKeys = new string[generatedTypeColors.Count]; - generatedTypeColors.Keys.CopyTo(generatedTypeKeys, 0); - foreach (var key in generatedTypeKeys) { - EditorGUILayout.BeginHorizontal(); - Color col = generatedTypeColors[key]; - EditorGUI.BeginDisabledGroup(true); - col = EditorGUILayout.ColorField(key, col); - EditorGUI.EndDisabledGroup(); - if (GUILayout.Toggle(false, "")) { - typeColors.Add(key, generatedTypeColors[key]); - generatedTypeColors.Remove(key); - SavePrefs(); - } - EditorGUILayout.EndHorizontal(); - } - } - - private static void LoadPrefs() { - generatedTypeColors = new Dictionary(); - typeColors = GetTypeColors(); - prefsLoaded = true; - } - - private static void SavePrefs() { - if (!prefsLoaded) return; - string s = ""; - foreach (var item in typeColors) { - s += item.Key + "," + ColorUtility.ToHtmlStringRGB(item.Value) + ","; - } - EditorPrefs.SetString("unec_typecolors", s); - } - - public static void SetDefaultTypeColors() { - EditorPrefs.SetString("unec_typecolors", "int,2568CA,string,CE743A,bool,00FF00"); - } - - public static Dictionary GetTypeColors() { - if (prefsLoaded) return typeColors; - if (!EditorPrefs.HasKey("unec_typecolors")) SetDefaultTypeColors(); - string[] data = EditorPrefs.GetString("unec_typecolors").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - Dictionary dict = new Dictionary(); - for (int i = 0; i < data.Length; i += 2) { - Color col; - if (ColorUtility.TryParseHtmlString("#" + data[i + 1], out col)) { - dict.Add(data[i], col); + string[] generatedTypeKeys = new string[generatedTypeColors.Count]; + generatedTypeColors.Keys.CopyTo(generatedTypeKeys, 0); + foreach (var key in generatedTypeKeys) { + EditorGUILayout.BeginHorizontal(); + Color col = generatedTypeColors[key]; + EditorGUI.BeginDisabledGroup(true); + col = EditorGUILayout.ColorField(key, col); + EditorGUI.EndDisabledGroup(); + if (GUILayout.Toggle(false, "")) { + typeColors.Add(key, generatedTypeColors[key]); + generatedTypeColors.Remove(key); + SavePrefs(); + } + EditorGUILayout.EndHorizontal(); } } - return dict; - } - /// Return color based on type - public static Color GetTypeColor(System.Type type) { - if (!prefsLoaded) LoadPrefs(); - if (type == null) return Color.gray; - if (typeColors.ContainsKey(type.Name)) return typeColors[type.Name]; - if (generatedTypeColors.ContainsKey(type.Name)) return generatedTypeColors[type.Name]; - UnityEngine.Random.InitState(type.Name.GetHashCode()); - generatedTypeColors.Add(type.Name, new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value)); - return generatedTypeColors[type.Name]; + private static void LoadPrefs() { + generatedTypeColors = new Dictionary(); + typeColors = GetTypeColors(); + prefsLoaded = true; + } + + private static void SavePrefs() { + if (!prefsLoaded) return; + string s = ""; + foreach (var item in typeColors) { + s += item.Key + "," + ColorUtility.ToHtmlStringRGB(item.Value) + ","; + } + EditorPrefs.SetString("unec_typecolors", s); + } + + public static void SetDefaultTypeColors() { + EditorPrefs.SetString("unec_typecolors", "int,2568CA,string,CE743A,bool,00FF00"); + } + + public static Dictionary GetTypeColors() { + if (prefsLoaded) return typeColors; + if (!EditorPrefs.HasKey("unec_typecolors")) SetDefaultTypeColors(); + string[] data = EditorPrefs.GetString("unec_typecolors").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + Dictionary dict = new Dictionary(); + for (int i = 0; i < data.Length; i += 2) { + Color col; + if (ColorUtility.TryParseHtmlString("#" + data[i + 1], out col)) { + dict.Add(data[i], col); + } + } + return dict; + } + + /// Return color based on type + public static Color GetTypeColor(System.Type type) { + if (!prefsLoaded) LoadPrefs(); + if (type == null) return Color.gray; + if (typeColors.ContainsKey(type.Name)) return typeColors[type.Name]; + if (generatedTypeColors.ContainsKey(type.Name)) return generatedTypeColors[type.Name]; + UnityEngine.Random.InitState(type.Name.GetHashCode()); + generatedTypeColors.Add(type.Name, new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value)); + return generatedTypeColors[type.Name]; + } } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index e6a18e6..316ef94 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -2,49 +2,51 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using UnityEngine; +using XNode; -/// Contains reflection-related info -public partial class NodeEditorWindow { - [NonSerialized] private static Dictionary customNodeEditor; - public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } } - [NonSerialized] private static Type[] _nodeTypes = null; +namespace XNodeEditor { + /// Contains reflection-related info + public partial class NodeEditorWindow { + [NonSerialized] private static Dictionary customNodeEditor; + public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } } + [NonSerialized] private static Type[] _nodeTypes = null; - public static NodeEditor GetNodeEditor(Type node) { - if (customNodeEditor == null) CacheCustomNodeEditors(); - if (customNodeEditor.ContainsKey(node)) return customNodeEditor[node]; - return customNodeEditor[typeof(Node)]; - } + public static NodeEditor GetNodeEditor(Type node) { + if (customNodeEditor == null) CacheCustomNodeEditors(); + if (customNodeEditor.ContainsKey(node)) return customNodeEditor[node]; + return customNodeEditor[typeof(Node)]; + } - public static Type[] GetNodeTypes() { - //Get all classes deriving from Node via reflection - return GetDerivedTypes(typeof(Node)); - } + public static Type[] GetNodeTypes() { + //Get all classes deriving from Node via reflection + return GetDerivedTypes(typeof(Node)); + } - public static void CacheCustomNodeEditors() { - customNodeEditor = new Dictionary(); - customNodeEditor.Add(typeof(Node), new NodeEditor()); - //Get all classes deriving from NodeEditor via reflection - Type[] nodeEditors = GetDerivedTypes(typeof(NodeEditor)); - for (int i = 0; i < nodeEditors.Length; i++) { - var attribs = nodeEditors[i].GetCustomAttributes(typeof(CustomNodeEditorAttribute), false); - if (attribs == null || attribs.Length == 0) continue; - if (nodeEditors[i].IsAbstract) continue; - CustomNodeEditorAttribute attrib = attribs[0] as CustomNodeEditorAttribute; - customNodeEditor.Add(attrib.inspectedType, Activator.CreateInstance(nodeEditors[i]) as NodeEditor); + public static void CacheCustomNodeEditors() { + customNodeEditor = new Dictionary(); + customNodeEditor.Add(typeof(Node), new NodeEditor()); + //Get all classes deriving from NodeEditor via reflection + Type[] nodeEditors = GetDerivedTypes(typeof(NodeEditor)); + for (int i = 0; i < nodeEditors.Length; i++) { + var attribs = nodeEditors[i].GetCustomAttributes(typeof(CustomNodeEditorAttribute), false); + if (attribs == null || attribs.Length == 0) continue; + if (nodeEditors[i].IsAbstract) continue; + CustomNodeEditorAttribute attrib = attribs[0] as CustomNodeEditorAttribute; + customNodeEditor.Add(attrib.inspectedType, Activator.CreateInstance(nodeEditors[i]) as NodeEditor); + } + } + + 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(); + } + + public static object ObjectFromType(Type type) { + return Activator.CreateInstance(type); } } - - 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(); - } - - public static object ObjectFromType(Type type) { - return Activator.CreateInstance(type); - } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorResources.cs b/Scripts/Editor/NodeEditorResources.cs index cc9320e..1e1b004 100644 --- a/Scripts/Editor/NodeEditorResources.cs +++ b/Scripts/Editor/NodeEditorResources.cs @@ -1,94 +1,94 @@ -using System; -using UnityEditor; -using UnityEngine; +using UnityEngine; -public static class NodeEditorResources { - //Unec textures - public static Texture2D gridTexture { get { return _gridTexture != null ? _gridTexture : _gridTexture = GenerateGridTexture(); } } - private static Texture2D _gridTexture; - public static Texture2D crossTexture { get { return _crossTexture != null ? _crossTexture : _crossTexture = GenerateCrossTexture(); } } - private static Texture2D _crossTexture; - public static Texture2D dot { get { return _dot != null ? _dot : _dot = Resources.Load("unec_dot"); } } - private static Texture2D _dot; - public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = Resources.Load("unec_dot_outer"); } } - private static Texture2D _dotOuter; - public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load("unec_node"); } } - private static Texture2D _nodeBody; +namespace XNodeEditor { + public static class NodeEditorResources { + //Unec textures + public static Texture2D gridTexture { get { return _gridTexture != null ? _gridTexture : _gridTexture = GenerateGridTexture(); } } + private static Texture2D _gridTexture; + public static Texture2D crossTexture { get { return _crossTexture != null ? _crossTexture : _crossTexture = GenerateCrossTexture(); } } + private static Texture2D _crossTexture; + public static Texture2D dot { get { return _dot != null ? _dot : _dot = Resources.Load("unec_dot"); } } + private static Texture2D _dot; + public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = Resources.Load("unec_dot_outer"); } } + private static Texture2D _dotOuter; + public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load("unec_node"); } } + private static Texture2D _nodeBody; - //Grid colors - private static Color backgroundColor = new Color(0.18f, 0.18f, 0.18f); - private static Color veinColor = new Color(0.25f, 0.25f, 0.25f); - private static Color arteryColor = new Color(0.34f, 0.34f, 0.34f); - private static Color crossColor = new Color(0.45f, 0.45f, 0.45f); + //Grid colors + private static Color backgroundColor = new Color(0.18f, 0.18f, 0.18f); + private static Color veinColor = new Color(0.25f, 0.25f, 0.25f); + private static Color arteryColor = new Color(0.34f, 0.34f, 0.34f); + private static Color crossColor = new Color(0.45f, 0.45f, 0.45f); - //Unec styles - public static Styles styles { get { return _styles != null ? _styles : _styles = new Styles(); } } - public static Styles _styles = null; + //Unec styles + public static Styles styles { get { return _styles != null ? _styles : _styles = new Styles(); } } + public static Styles _styles = null; - public class Styles { - public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip; + public class Styles { + public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip; - public Styles() { - GUIStyle baseStyle = new GUIStyle("Label"); - baseStyle.fixedHeight = 18; + public Styles() { + GUIStyle baseStyle = new GUIStyle("Label"); + baseStyle.fixedHeight = 18; - inputPort = new GUIStyle(baseStyle); - inputPort.alignment = TextAnchor.UpperLeft; - inputPort.padding.left = 10; + inputPort = new GUIStyle(baseStyle); + inputPort.alignment = TextAnchor.UpperLeft; + inputPort.padding.left = 10; - outputPort = new GUIStyle(baseStyle); - outputPort.alignment = TextAnchor.UpperRight; - outputPort.padding.right = 10; + outputPort = new GUIStyle(baseStyle); + outputPort.alignment = TextAnchor.UpperRight; + outputPort.padding.right = 10; - nodeHeader = new GUIStyle(); - nodeHeader.alignment = TextAnchor.MiddleCenter; - nodeHeader.fontStyle = FontStyle.Bold; - nodeHeader.normal.textColor = Color.white; + nodeHeader = new GUIStyle(); + nodeHeader.alignment = TextAnchor.MiddleCenter; + nodeHeader.fontStyle = FontStyle.Bold; + nodeHeader.normal.textColor = Color.white; - nodeBody = new GUIStyle(); - nodeBody.normal.background = NodeEditorResources.nodeBody; - nodeBody.border = new RectOffset(32, 32, 32, 32); - nodeBody.padding = new RectOffset(16, 16, 4, 16); + nodeBody = new GUIStyle(); + nodeBody.normal.background = NodeEditorResources.nodeBody; + nodeBody.border = new RectOffset(32, 32, 32, 32); + nodeBody.padding = new RectOffset(16, 16, 4, 16); - tooltip = new GUIStyle("helpBox"); - tooltip.alignment = TextAnchor.MiddleCenter; - } - } - - public static Texture2D GenerateGridTexture() { - Texture2D tex = new Texture2D(64, 64); - Color[] cols = new Color[64 * 64]; - for (int y = 0; y < 64; y++) { - for (int x = 0; x < 64; x++) { - Color col = backgroundColor; - if (y % 16 == 0 || x % 16 == 0) col = veinColor; - if (y == 63 || x == 63) col = arteryColor; - cols[(y * 64) + x] = col; + tooltip = new GUIStyle("helpBox"); + tooltip.alignment = TextAnchor.MiddleCenter; } } - tex.SetPixels(cols); - tex.wrapMode = TextureWrapMode.Repeat; - tex.filterMode = FilterMode.Bilinear; - tex.name = "Grid"; - tex.Apply(); - return tex; - } - public static Texture2D GenerateCrossTexture() { - Texture2D tex = new Texture2D(64, 64); - Color[] cols = new Color[64 * 64]; - for (int y = 0; y < 64; y++) { - for (int x = 0; x < 64; x++) { - Color col = crossColor; - if (y != 31 && x != 31) col.a = 0; - cols[(y * 64) + x] = col; + public static Texture2D GenerateGridTexture() { + Texture2D tex = new Texture2D(64, 64); + Color[] cols = new Color[64 * 64]; + for (int y = 0; y < 64; y++) { + for (int x = 0; x < 64; x++) { + Color col = backgroundColor; + if (y % 16 == 0 || x % 16 == 0) col = veinColor; + if (y == 63 || x == 63) col = arteryColor; + cols[(y * 64) + x] = col; + } } + tex.SetPixels(cols); + tex.wrapMode = TextureWrapMode.Repeat; + tex.filterMode = FilterMode.Bilinear; + tex.name = "Grid"; + tex.Apply(); + return tex; + } + + public static Texture2D GenerateCrossTexture() { + Texture2D tex = new Texture2D(64, 64); + Color[] cols = new Color[64 * 64]; + for (int y = 0; y < 64; y++) { + for (int x = 0; x < 64; x++) { + Color col = crossColor; + if (y != 31 && x != 31) col.a = 0; + cols[(y * 64) + x] = col; + } + } + tex.SetPixels(cols); + tex.wrapMode = TextureWrapMode.Clamp; + tex.filterMode = FilterMode.Bilinear; + tex.name = "Grid"; + tex.Apply(); + return tex; } - tex.SetPixels(cols); - tex.wrapMode = TextureWrapMode.Clamp; - tex.filterMode = FilterMode.Bilinear; - tex.name = "Grid"; - tex.Apply(); - return tex; } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index 755f87f..12e641a 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -1,54 +1,53 @@ using System; -using System.Collections; -using System.Collections.Generic; using System.Linq; using System.Reflection; -using UnityEngine; -/// A set of editor-only utilities and extensions for UnityNodeEditorBase -public static class NodeEditorUtilities { +namespace XNodeEditor { + /// A set of editor-only utilities and extensions for UnityNodeEditorBase + public static class NodeEditorUtilities { - public static bool GetAttrib(Type classType, out T attribOut) where T : Attribute { - object[] attribs = classType.GetCustomAttributes(typeof(T), false); - return GetAttrib(attribs, out attribOut); - } - - public static bool GetAttrib(object[] attribs, out T attribOut) where T : Attribute { - for (int i = 0; i < attribs.Length; i++) { - if (attribs[i].GetType() == typeof(T)) { - attribOut = attribs[i] as T; - return true; - } + public static bool GetAttrib(Type classType, out T attribOut) where T : Attribute { + object[] attribs = classType.GetCustomAttributes(typeof(T), false); + return GetAttrib(attribs, out attribOut); } - attribOut = null; - return false; - } - public static bool HasAttrib(object[] attribs) where T : Attribute { - for (int i = 0; i < attribs.Length; i++) { - if (attribs[i].GetType() == typeof(T)) { - return true; + public static bool GetAttrib(object[] attribs, out T attribOut) where T : Attribute { + for (int i = 0; i < attribs.Length; i++) { + if (attribs[i].GetType() == typeof(T)) { + attribOut = attribs[i] as T; + return true; + } } + attribOut = null; + return false; } - return false; - } - /// Turns camelCaseString into Camel Case String - public static string PrettifyCamelCase(this string camelCase) { - if (string.IsNullOrEmpty(camelCase)) return ""; - string s = System.Text.RegularExpressions.Regex.Replace(camelCase, "([A-Z])", " $1", System.Text.RegularExpressions.RegexOptions.Compiled).Trim(); - return char.ToUpper(s[0]) + s.Substring(1); - } + public static bool HasAttrib(object[] attribs) where T : Attribute { + for (int i = 0; i < attribs.Length; i++) { + if (attribs[i].GetType() == typeof(T)) { + return true; + } + } + return false; + } - /// Returns true if this can be casted to - public static bool IsCastableTo(this Type from, Type to) { - if (to.IsAssignableFrom(from)) return true; - var methods = from.GetMethods(BindingFlags.Public | BindingFlags.Static) - .Where( - m => m.ReturnType == to && - (m.Name == "op_Implicit" || - m.Name == "op_Explicit") - ); - return methods.Count() > 0; + /// Turns camelCaseString into Camel Case String + public static string PrettifyCamelCase(this string camelCase) { + if (string.IsNullOrEmpty(camelCase)) return ""; + string s = System.Text.RegularExpressions.Regex.Replace(camelCase, "([A-Z])", " $1", System.Text.RegularExpressions.RegexOptions.Compiled).Trim(); + return char.ToUpper(s[0]) + s.Substring(1); + } + + /// Returns true if this can be casted to + public static bool IsCastableTo(this Type from, Type to) { + if (to.IsAssignableFrom(from)) return true; + var methods = from.GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where( + m => m.ReturnType == to && + (m.Name == "op_Implicit" || + m.Name == "op_Explicit") + ); + return methods.Count() > 0; + } } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 3cc305e..45df18a 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -1,97 +1,97 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; +using System.Collections.Generic; using UnityEditor; using UnityEditor.Callbacks; using UnityEngine; +using XNode; -[InitializeOnLoad] -public partial class NodeEditorWindow : EditorWindow { - public static NodeEditorWindow current; +namespace XNodeEditor { + [InitializeOnLoad] + public partial class NodeEditorWindow : EditorWindow { + public static NodeEditorWindow current; - /// Stores node positions for all nodePorts. - public Dictionary portConnectionPoints { get { return _portConnectionPoints; } } - private Dictionary _portConnectionPoints = new Dictionary(); - public Dictionary nodeWidths { get { return _nodeWidths; } } - private Dictionary _nodeWidths = new Dictionary(); - public NodeGraph graph; - public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } } - private Vector2 _panOffset; - public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, 1f, 5f); Repaint(); } } - private float _zoom = 1; + /// Stores node positions for all nodePorts. + public Dictionary portConnectionPoints { get { return _portConnectionPoints; } } + private Dictionary _portConnectionPoints = new Dictionary(); + public Dictionary nodeWidths { get { return _nodeWidths; } } + private Dictionary _nodeWidths = new Dictionary(); + public NodeGraph graph; + public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } } + private Vector2 _panOffset; + public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, 1f, 5f); Repaint(); } } + private float _zoom = 1; - void OnFocus() { - AssetDatabase.SaveAssets(); - current = this; - } - - partial void OnEnable(); - /// Create editor window - //[MenuItem("Window/UNEC")] - public static NodeEditorWindow Init() { - NodeEditorWindow w = CreateInstance(); - w.titleContent = new GUIContent("UNEC"); - w.wantsMouseMove = true; - w.Show(); - return w; - } - - public void Save() { - if (AssetDatabase.Contains(graph)) { - EditorUtility.SetDirty(graph); - AssetDatabase.SaveAssets(); - } else SaveAs(); - } - - public void SaveAs() { - string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", ""); - if (string.IsNullOrEmpty(path)) return; - else { - NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath(path); - if (existingGraph != null) AssetDatabase.DeleteAsset(path); - AssetDatabase.CreateAsset(graph, path); - EditorUtility.SetDirty(graph); + void OnFocus() { AssetDatabase.SaveAssets(); + current = this; } - } - private void DraggableWindow(int windowID) { - GUI.DragWindow(); - } - - public Vector2 WindowToGridPosition(Vector2 windowPosition) { - return (windowPosition - (position.size * 0.5f) - (panOffset / zoom)) * zoom; - } - - public Vector2 GridToWindowPosition(Vector2 gridPosition) { - return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition / zoom); - } - - public Rect GridToWindowRect(Rect gridRect) { - gridRect.position = GridToWindowPositionNoClipped(gridRect.position); - return gridRect; - } - - public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) { - Vector2 center = position.size * 0.5f; - float xOffset = (center.x * zoom + (panOffset.x + gridPosition.x)); - float yOffset = (center.y * zoom + (panOffset.y + gridPosition.y)); - return new Vector2(xOffset, yOffset); - } - - public void SelectNode(Node node) { - selectedNode = node; - } - - [OnOpenAsset(0)] - public static bool OnOpen(int instanceID, int line) { - NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as NodeGraph; - if (nodeGraph != null) { - NodeEditorWindow w = Init(); - w.graph = nodeGraph; - return true; + partial void OnEnable(); + /// Create editor window + //[MenuItem("Window/UNEC")] + public static NodeEditorWindow Init() { + NodeEditorWindow w = CreateInstance(); + w.titleContent = new GUIContent("UNEC"); + w.wantsMouseMove = true; + w.Show(); + return w; + } + + public void Save() { + if (AssetDatabase.Contains(graph)) { + EditorUtility.SetDirty(graph); + AssetDatabase.SaveAssets(); + } else SaveAs(); + } + + public void SaveAs() { + string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", ""); + if (string.IsNullOrEmpty(path)) return; + else { + NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath(path); + if (existingGraph != null) AssetDatabase.DeleteAsset(path); + AssetDatabase.CreateAsset(graph, path); + EditorUtility.SetDirty(graph); + AssetDatabase.SaveAssets(); + } + } + + private void DraggableWindow(int windowID) { + GUI.DragWindow(); + } + + public Vector2 WindowToGridPosition(Vector2 windowPosition) { + return (windowPosition - (position.size * 0.5f) - (panOffset / zoom)) * zoom; + } + + public Vector2 GridToWindowPosition(Vector2 gridPosition) { + return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition / zoom); + } + + public Rect GridToWindowRect(Rect gridRect) { + gridRect.position = GridToWindowPositionNoClipped(gridRect.position); + return gridRect; + } + + public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) { + Vector2 center = position.size * 0.5f; + float xOffset = (center.x * zoom + (panOffset.x + gridPosition.x)); + float yOffset = (center.y * zoom + (panOffset.y + gridPosition.y)); + return new Vector2(xOffset, yOffset); + } + + public void SelectNode(Node node) { + selectedNode = node; + } + + [OnOpenAsset(0)] + public static bool OnOpen(int instanceID, int line) { + NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as NodeGraph; + if (nodeGraph != null) { + NodeEditorWindow w = Init(); + w.graph = nodeGraph; + return true; + } + return false; } - return false; } } \ No newline at end of file diff --git a/Scripts/Node.cs b/Scripts/Node.cs index eab0c09..d7592a3 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -2,198 +2,200 @@ using System.Collections.Generic; using UnityEngine; -/// Base class for all nodes -[Serializable] -public abstract class Node : ScriptableObject { - public enum ShowBackingValue { - /// Never show the backing value - Never, - /// Show the backing value only when the port does not have any active connections - Unconnected, - /// Always show the backing value - Always - } - - /// Iterate over all ports on this node. - public IEnumerable Ports { get { foreach (NodePort port in ports.Values) yield return port; } } - /// Iterate over all outputs on this node. - public IEnumerable Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } } - /// Iterate over all inputs on this node. - public IEnumerable Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } } - /// Iterate over all instane ports on this node. - public IEnumerable InstancePorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } } - /// Iterate over all instance outputs on this node. - public IEnumerable InstanceOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } } - /// Iterate over all instance inputs on this node. - public IEnumerable InstanceInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } } - /// Parent - [SerializeField] public NodeGraph graph; - /// Position on the - [SerializeField] public Vector2 position; - /// Input s. It is recommended not to modify these at hand. Instead, see - [SerializeField] private NodePortDictionary ports = new NodePortDictionary(); - - protected void OnEnable() { - NodeDataCache.UpdatePorts(this, ports); - Init(); - } - - /// Initialize node. Called on creation. - protected virtual void Init() { name = GetType().Name; } - - /// Checks all connections for invalid references, and removes them. - public void VerifyConnections() { - foreach (NodePort port in Ports) port.VerifyConnections(); - } - - #region Instance Ports - /// Returns input port at index - public NodePort AddInstanceInput(Type type, string fieldName = null) { - return AddInstancePort(type, NodePort.IO.Input, fieldName); - } - - /// Returns input port at index - public NodePort AddInstanceOutput(Type type, string fieldName = null) { - return AddInstancePort(type, NodePort.IO.Output, fieldName); - } - - private NodePort AddInstancePort(Type type, NodePort.IO direction, string fieldName = null) { - if (fieldName == null) { - fieldName = "instanceInput_0"; - int i = 0; - while (HasPort(fieldName)) fieldName = "instanceInput_" + (++i); - } else if (HasPort(fieldName)) { - Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this); - return ports[fieldName]; +namespace XNode { + /// Base class for all nodes + [Serializable] + public abstract class Node : ScriptableObject { + public enum ShowBackingValue { + /// Never show the backing value + Never, + /// Show the backing value only when the port does not have any active connections + Unconnected, + /// Always show the backing value + Always } - NodePort port = new NodePort(fieldName, type, direction, this); - ports.Add(fieldName, port); - return port; - } - public bool RemoveInstancePort(string fieldName) { - NodePort port = GetPort(fieldName); - if (port == null || port.IsStatic) return false; - port.ClearConnections(); - ports.Remove(fieldName); - return true; - } - #endregion + /// Iterate over all ports on this node. + public IEnumerable Ports { get { foreach (NodePort port in ports.Values) yield return port; } } + /// Iterate over all outputs on this node. + public IEnumerable Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } } + /// Iterate over all inputs on this node. + public IEnumerable Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } } + /// Iterate over all instane ports on this node. + public IEnumerable InstancePorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } } + /// Iterate over all instance outputs on this node. + public IEnumerable InstanceOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } } + /// Iterate over all instance inputs on this node. + public IEnumerable InstanceInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } } + /// Parent + [SerializeField] public NodeGraph graph; + /// Position on the + [SerializeField] public Vector2 position; + /// Input s. It is recommended not to modify these at hand. Instead, see + [SerializeField] private NodePortDictionary ports = new NodePortDictionary(); - #region Ports - /// Returns output port which matches fieldName - public NodePort GetOutputPort(string fieldName) { - NodePort port = GetPort(fieldName); - if (port == null || port.direction != NodePort.IO.Output) return null; - else return port; - } + protected void OnEnable() { + NodeDataCache.UpdatePorts(this, ports); + Init(); + } - /// Returns input port which matches fieldName - public NodePort GetInputPort(string fieldName) { - NodePort port = GetPort(fieldName); - if (port == null || port.direction != NodePort.IO.Input) return null; - else return port; - } + /// Initialize node. Called on creation. + protected virtual void Init() { name = GetType().Name; } - /// Returns port which matches fieldName - public NodePort GetPort(string fieldName) { - if (ports.ContainsKey(fieldName)) return ports[fieldName]; - else return null; - } + /// Checks all connections for invalid references, and removes them. + public void VerifyConnections() { + foreach (NodePort port in Ports) port.VerifyConnections(); + } - public bool HasPort(string fieldName) { - return ports.ContainsKey(fieldName); - } - #endregion + #region Instance Ports + /// Returns input port at index + public NodePort AddInstanceInput(Type type, string fieldName = null) { + return AddInstancePort(type, NodePort.IO.Input, fieldName); + } - #region Inputs/Outputs - /// Return input value for a specified port. Returns fallback value if no ports are connected - /// Field name of requested input port - /// If no ports are connected, this value will be returned - public T GetInputValue(string fieldName, T fallback = default(T)) { - NodePort port = GetPort(fieldName); - if (port != null && port.IsConnected) return port.GetInputValue(); - else return fallback; - } + /// Returns input port at index + public NodePort AddInstanceOutput(Type type, string fieldName = null) { + return AddInstancePort(type, NodePort.IO.Output, fieldName); + } - /// Return all input values for a specified port. Returns fallback value if no ports are connected - /// Field name of requested input port - /// If no ports are connected, this value will be returned - public T[] GetInputValues(string fieldName, params T[] fallback) { - NodePort port = GetPort(fieldName); - if (port != null && port.IsConnected) return port.GetInputValues(); - else return fallback; - } + private NodePort AddInstancePort(Type type, NodePort.IO direction, string fieldName = null) { + if (fieldName == null) { + fieldName = "instanceInput_0"; + int i = 0; + while (HasPort(fieldName)) fieldName = "instanceInput_" + (++i); + } else if (HasPort(fieldName)) { + Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this); + return ports[fieldName]; + } + NodePort port = new NodePort(fieldName, type, direction, this); + ports.Add(fieldName, port); + return port; + } - /// Returns a value based on requested port output. Should be overridden before used. - /// The requested port. - public virtual object GetValue(NodePort port) { - Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType()); - return null; - } - #endregion + public bool RemoveInstancePort(string fieldName) { + NodePort port = GetPort(fieldName); + if (port == null || port.IsStatic) return false; + port.ClearConnections(); + ports.Remove(fieldName); + return true; + } + #endregion - /// Called whenever a connection is being made between two s - /// Output Input - public virtual void OnCreateConnection(NodePort from, NodePort to) { } + #region Ports + /// Returns output port which matches fieldName + public NodePort GetOutputPort(string fieldName) { + NodePort port = GetPort(fieldName); + if (port == null || port.direction != NodePort.IO.Output) return null; + else return port; + } - /// Disconnect everything from this node - public void ClearConnections() { - foreach (NodePort port in Ports) port.ClearConnections(); - } + /// Returns input port which matches fieldName + public NodePort GetInputPort(string fieldName) { + NodePort port = GetPort(fieldName); + if (port == null || port.direction != NodePort.IO.Input) return null; + else return port; + } - public override int GetHashCode() { - return JsonUtility.ToJson(this).GetHashCode(); - } + /// Returns port which matches fieldName + public NodePort GetPort(string fieldName) { + if (ports.ContainsKey(fieldName)) return ports[fieldName]; + else return null; + } - /// Mark a serializable field as an input port. You can access this through - [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] - public class InputAttribute : Attribute { - public ShowBackingValue backingValue; + public bool HasPort(string fieldName) { + return ports.ContainsKey(fieldName); + } + #endregion + + #region Inputs/Outputs + /// Return input value for a specified port. Returns fallback value if no ports are connected + /// Field name of requested input port + /// If no ports are connected, this value will be returned + public T GetInputValue(string fieldName, T fallback = default(T)) { + NodePort port = GetPort(fieldName); + if (port != null && port.IsConnected) return port.GetInputValue(); + else return fallback; + } + + /// Return all input values for a specified port. Returns fallback value if no ports are connected + /// Field name of requested input port + /// If no ports are connected, this value will be returned + public T[] GetInputValues(string fieldName, params T[] fallback) { + NodePort port = GetPort(fieldName); + if (port != null && port.IsConnected) return port.GetInputValues(); + else return fallback; + } + + /// Returns a value based on requested port output. Should be overridden before used. + /// The requested port. + public virtual object GetValue(NodePort port) { + Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType()); + return null; + } + #endregion + + /// Called whenever a connection is being made between two s + /// Output Input + public virtual void OnCreateConnection(NodePort from, NodePort to) { } + + /// Disconnect everything from this node + public void ClearConnections() { + foreach (NodePort port in Ports) port.ClearConnections(); + } + + public override int GetHashCode() { + return JsonUtility.ToJson(this).GetHashCode(); + } /// Mark a serializable field as an input port. You can access this through - /// Should we display the backing value for this port as an editor field? - public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected) { this.backingValue = backingValue; } - } + [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] + public class InputAttribute : Attribute { + public ShowBackingValue backingValue; - /// Mark a serializable field as an output port. You can access this through - [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] - public class OutputAttribute : Attribute { - /// Mark a serializable field as an output port. You can access this through - public OutputAttribute() { } - } - - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class CreateNodeMenuAttribute : Attribute { - public string menuName; - /// Manually supply node class with a context menu path - /// Path to this node in the context menu - public CreateNodeMenuAttribute(string menuName) { - this.menuName = menuName; + /// Mark a serializable field as an input port. You can access this through + /// Should we display the backing value for this port as an editor field? + public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected) { this.backingValue = backingValue; } } - } - [Serializable] private class NodePortDictionary : Dictionary, ISerializationCallbackReceiver { - [SerializeField] private List keys = new List(); - [SerializeField] private List values = new List(); + /// Mark a serializable field as an output port. You can access this through + [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] + public class OutputAttribute : Attribute { + /// Mark a serializable field as an output port. You can access this through + public OutputAttribute() { } + } - public void OnBeforeSerialize() { - keys.Clear(); - values.Clear(); - foreach (KeyValuePair pair in this) { - keys.Add(pair.Key); - values.Add(pair.Value); + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class CreateNodeMenuAttribute : Attribute { + public string menuName; + /// Manually supply node class with a context menu path + /// Path to this node in the context menu + public CreateNodeMenuAttribute(string menuName) { + this.menuName = menuName; } } - public void OnAfterDeserialize() { - this.Clear(); + [Serializable] private class NodePortDictionary : Dictionary, ISerializationCallbackReceiver { + [SerializeField] private List keys = new List(); + [SerializeField] private List values = new List(); - if (keys.Count != values.Count) - throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable.")); + public void OnBeforeSerialize() { + keys.Clear(); + values.Clear(); + foreach (KeyValuePair pair in this) { + keys.Add(pair.Key); + values.Add(pair.Value); + } + } - for (int i = 0; i < keys.Count; i++) - this.Add(keys[i], values[i]); + public void OnAfterDeserialize() { + this.Clear(); + + if (keys.Count != values.Count) + throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable.")); + + for (int i = 0; i < keys.Count; i++) + this.Add(keys[i], values[i]); + } } } } \ No newline at end of file diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index 30d98ab..ec9cc8f 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -1,100 +1,100 @@ -using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Reflection; -using UnityEditor; using UnityEngine; -/// Precaches reflection data in editor so we won't have to do it runtime -public static class NodeDataCache { - private static PortDataCache portDataCache; - private static bool Initialized { get { return portDataCache != null; } } +namespace XNode { + /// Precaches reflection data in editor so we won't have to do it runtime + public static class NodeDataCache { + private static PortDataCache portDataCache; + private static bool Initialized { get { return portDataCache != null; } } - /// Update static ports to reflect class fields. - public static void UpdatePorts(Node node, Dictionary ports) { - if (!Initialized) BuildCache(); + /// Update static ports to reflect class fields. + public static void UpdatePorts(Node node, Dictionary ports) { + if (!Initialized) BuildCache(); - Dictionary staticPorts = new Dictionary(); - System.Type nodeType = node.GetType(); + Dictionary staticPorts = new Dictionary(); + System.Type nodeType = node.GetType(); - if (!portDataCache.ContainsKey(nodeType)) return; - for (int i = 0; i < portDataCache[nodeType].Count; i++) { - staticPorts.Add(portDataCache[nodeType][i].fieldName, portDataCache[nodeType][i]); - } - - // Cleanup port dict - Remove nonexisting static ports - update static port types - foreach (NodePort port in ports.Values.ToList()) { - if (staticPorts.ContainsKey(port.fieldName)) { - NodePort staticPort = staticPorts[port.fieldName]; - if (port.IsDynamic || port.direction != staticPort.direction) ports.Remove(port.fieldName); - else port.ValueType = staticPort.ValueType; - } else if (port.IsStatic) ports.Remove(port.fieldName); - } - // Add missing ports - foreach (NodePort staticPort in staticPorts.Values) { - if (!ports.ContainsKey(staticPort.fieldName)) { - ports.Add(staticPort.fieldName, new NodePort(staticPort, node)); + if (!portDataCache.ContainsKey(nodeType)) return; + for (int i = 0; i < portDataCache[nodeType].Count; i++) { + staticPorts.Add(portDataCache[nodeType][i].fieldName, portDataCache[nodeType][i]); } - } - } - 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++) { - CachePorts(nodeTypes[i]); - } - } - - private static void CachePorts(System.Type nodeType) { - System.Reflection.FieldInfo[] fieldInfo = nodeType.GetFields(); - for (int i = 0; i < fieldInfo.Length; i++) { - - //Get InputAttribute and OutputAttribute - object[] attribs = fieldInfo[i].GetCustomAttributes(false); - Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute; - Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute; - - if (inputAttrib == null && outputAttrib == null) continue; - - if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo + " cannot be both input and output."); - else { - if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List()); - portDataCache[nodeType].Add(new NodePort(fieldInfo[i])); + // Cleanup port dict - Remove nonexisting static ports - update static port types + foreach (NodePort port in ports.Values.ToList()) { + if (staticPorts.ContainsKey(port.fieldName)) { + NodePort staticPort = staticPorts[port.fieldName]; + if (port.IsDynamic || port.direction != staticPort.direction) ports.Remove(port.fieldName); + else port.ValueType = staticPort.ValueType; + } else if (port.IsStatic) ports.Remove(port.fieldName); } - } - } - - [System.Serializable] - private class PortDataCache : Dictionary>, ISerializationCallbackReceiver { - [SerializeField] private List keys = new List(); - [SerializeField] private List> values = new List>(); - - // save the dictionary to lists - public void OnBeforeSerialize() { - keys.Clear(); - values.Clear(); - foreach (var pair in this) { - keys.Add(pair.Key); - values.Add(pair.Value); + // Add missing ports + foreach (NodePort staticPort in staticPorts.Values) { + if (!ports.ContainsKey(staticPort.fieldName)) { + ports.Add(staticPort.fieldName, new NodePort(staticPort, node)); + } } } - // load dictionary from lists - public void OnAfterDeserialize() { - this.Clear(); + 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(); - if (keys.Count != values.Count) - throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable.")); + for (int i = 0; i < nodeTypes.Length; i++) { + CachePorts(nodeTypes[i]); + } + } - for (int i = 0; i < keys.Count; i++) - this.Add(keys[i], values[i]); + private static void CachePorts(System.Type nodeType) { + System.Reflection.FieldInfo[] fieldInfo = nodeType.GetFields(); + for (int i = 0; i < fieldInfo.Length; i++) { + + //Get InputAttribute and OutputAttribute + object[] attribs = fieldInfo[i].GetCustomAttributes(false); + Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute; + Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute; + + if (inputAttrib == null && outputAttrib == null) continue; + + if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo + " cannot be both input and output."); + else { + if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List()); + portDataCache[nodeType].Add(new NodePort(fieldInfo[i])); + } + } + } + + [System.Serializable] + private class PortDataCache : Dictionary>, ISerializationCallbackReceiver { + [SerializeField] private List keys = new List(); + [SerializeField] private List> values = new List>(); + + // save the dictionary to lists + public void OnBeforeSerialize() { + keys.Clear(); + values.Clear(); + foreach (var pair in this) { + keys.Add(pair.Key); + values.Add(pair.Value); + } + } + + // load dictionary from lists + public void OnAfterDeserialize() { + this.Clear(); + + if (keys.Count != values.Count) + throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable.")); + + for (int i = 0; i < keys.Count; i++) + this.Add(keys[i], values[i]); + } } } } \ No newline at end of file diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index 2fa569b..8253a42 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -2,46 +2,48 @@ using System.Collections.Generic; using UnityEngine; -/// Base class for all node graphs -[Serializable] -public abstract class NodeGraph : ScriptableObject { +namespace XNode { + /// Base class for all node graphs + [Serializable] + public abstract class NodeGraph : ScriptableObject { - /// All nodes in the graph. - /// See: - [SerializeField] public List nodes = new List(); + /// All nodes in the graph. + /// See: + [SerializeField] public List nodes = new List(); - public T AddNode() where T : Node { - return AddNode(typeof(T)) as T; - } - - 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(); + public T AddNode() where T : Node { + return AddNode(typeof(T)) as T; } -#endif - nodes.Add(node); - node.graph = this; - return node; - } - /// Safely remove a node and all its connections - /// - public void RemoveNode(Node node) { - node.ClearConnections(); + public virtual Node AddNode(Type type) { + Node node = ScriptableObject.CreateInstance(type) as Node; #if UNITY_EDITOR - if (!Application.isPlaying) { - DestroyImmediate(node, true); - UnityEditor.AssetDatabase.SaveAssets(); - } + if (!Application.isPlaying) { + UnityEditor.AssetDatabase.AddObjectToAsset(node, this); + UnityEditor.AssetDatabase.SaveAssets(); + } #endif - nodes.Remove(node); - } + nodes.Add(node); + node.graph = this; + return node; + } - /// Remove all nodes and connections from the graph - public void Clear() { - nodes.Clear(); + /// Safely remove a node and all its connections + /// + public void RemoveNode(Node node) { + node.ClearConnections(); +#if UNITY_EDITOR + if (!Application.isPlaying) { + DestroyImmediate(node, true); + UnityEditor.AssetDatabase.SaveAssets(); + } +#endif + nodes.Remove(node); + } + + /// Remove all nodes and connections from the graph + public void Clear() { + nodes.Clear(); + } } } \ No newline at end of file diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index 6a85b78..c26ad90 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -1,246 +1,247 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; -[Serializable] -public class NodePort { - public enum IO { Input, Output } +namespace XNode { + [Serializable] + public class NodePort { + public enum IO { Input, Output } - public int ConnectionCount { get { return connections.Count; } } - /// Return the first connection - public NodePort Connection { get { return connections.Count > 0 ? connections[0].Port : null; } } + public int ConnectionCount { get { return connections.Count; } } + /// Return the first connection + public NodePort Connection { get { return connections.Count > 0 ? connections[0].Port : null; } } - public IO direction { get { return _direction; } } - /// Is this port connected to anytihng? - public bool IsConnected { get { return connections.Count != 0; } } - public bool IsInput { get { return direction == IO.Input; } } - public bool IsOutput { get { return direction == IO.Output; } } + public IO direction { get { return _direction; } } + /// Is this port connected to anytihng? + public bool IsConnected { get { return connections.Count != 0; } } + public bool IsInput { get { return direction == IO.Input; } } + public bool IsOutput { get { return direction == IO.Output; } } - public string fieldName { get { return _fieldName; } } - public Node node { get { return _node; } } - public bool IsDynamic { get { return _dynamic; } } - public bool IsStatic { get { return !_dynamic; } } - public Type ValueType { - get { - if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false); - return valueType; - } - set { - valueType = value; - if (value != null) _typeQualifiedName = value.AssemblyQualifiedName; - } - } - private Type valueType; - - [SerializeField] private string _fieldName; - [SerializeField] private Node _node; - [SerializeField] private string _typeQualifiedName; - [SerializeField] private List connections = new List(); - [SerializeField] private IO _direction; - [SerializeField] private bool _dynamic; - - /// Construct a static targetless nodeport. Used as a template. - public NodePort(FieldInfo fieldInfo) { - _fieldName = fieldInfo.Name; - ValueType = fieldInfo.FieldType; - _dynamic = false; - var attribs = fieldInfo.GetCustomAttributes(false); - for (int i = 0; i < attribs.Length; i++) { - if (attribs[i] is Node.InputAttribute) _direction = IO.Input; - else if (attribs[i] is Node.OutputAttribute) _direction = IO.Output; - } - } - - /// Copy a nodePort but assign it to another node. - public NodePort(NodePort nodePort, Node node) { - _fieldName = nodePort._fieldName; - ValueType = nodePort.valueType; - _direction = nodePort.direction; - _dynamic = nodePort._dynamic; - _node = node; - } - - /// Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. - public NodePort(string fieldName, Type type, IO direction, Node node) { - _fieldName = fieldName; - this.ValueType = type; - _direction = direction; - _node = node; - _dynamic = true; - } - - /// Checks all connections for invalid references, and removes them. - public void VerifyConnections() { - for (int i = connections.Count - 1; i >= 0; i--) { - if (connections[i].node != null && - !string.IsNullOrEmpty(connections[i].fieldName) && - connections[i].node.GetPort(connections[i].fieldName) != null) - continue; - connections.RemoveAt(i); - } - } - - /// Return the output value of this node through its parent nodes GetValue override method. - /// - public object GetOutputValue() { - if (direction == IO.Input) return null; - return node.GetValue(this); - } - - /// Return the output value of the first connected port. Returns null if none found or invalid. - /// - public object GetInputValue() { - NodePort connectedPort = Connection; - if (connectedPort == null) return null; - return connectedPort.GetOutputValue(); - } - - /// Return the output values of all connected ports. - /// - public object[] GetInputValues() { - object[] objs = new object[ConnectionCount]; - for (int i = 0; i < ConnectionCount; i++) { - NodePort connectedPort = connections[i].Port; - if (connectedPort == null) { // if we happen to find a null port, remove it and look again - connections.RemoveAt(i); - i--; - continue; + public string fieldName { get { return _fieldName; } } + public Node node { get { return _node; } } + public bool IsDynamic { get { return _dynamic; } } + public bool IsStatic { get { return !_dynamic; } } + public Type ValueType { + get { + if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false); + return valueType; + } + set { + valueType = value; + if (value != null) _typeQualifiedName = value.AssemblyQualifiedName; } - objs[i] = connectedPort.GetOutputValue(); } - return objs; - } + private Type valueType; - /// Return the output value of the first connected port. Returns null if none found or invalid. - /// - public T GetInputValue() { - object obj = GetInputValue(); - return obj is T ? (T) obj : default(T); - } + [SerializeField] private string _fieldName; + [SerializeField] private Node _node; + [SerializeField] private string _typeQualifiedName; + [SerializeField] private List connections = new List(); + [SerializeField] private IO _direction; + [SerializeField] private bool _dynamic; - /// Return the output values of all connected ports. - /// - public T[] GetInputValues() { - object[] objs = GetInputValues(); - T[] ts = new T[objs.Length]; - for (int i = 0; i < objs.Length; i++) { - if (objs[i] is T) ts[i] = (T) objs[i]; + /// Construct a static targetless nodeport. Used as a template. + public NodePort(FieldInfo fieldInfo) { + _fieldName = fieldInfo.Name; + ValueType = fieldInfo.FieldType; + _dynamic = false; + var attribs = fieldInfo.GetCustomAttributes(false); + for (int i = 0; i < attribs.Length; i++) { + if (attribs[i] is Node.InputAttribute) _direction = IO.Input; + else if (attribs[i] is Node.OutputAttribute) _direction = IO.Output; + } } - return ts; - } - /// Return true if port is connected and has a valid input. - /// - public bool TryGetInputValue(out T value) { - object obj = GetInputValue(); - if (obj is T) { - value = (T) obj; - return true; - } else { - value = default(T); + /// Copy a nodePort but assign it to another node. + public NodePort(NodePort nodePort, Node node) { + _fieldName = nodePort._fieldName; + ValueType = nodePort.valueType; + _direction = nodePort.direction; + _dynamic = nodePort._dynamic; + _node = node; + } + + /// Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. + public NodePort(string fieldName, Type type, IO direction, Node node) { + _fieldName = fieldName; + this.ValueType = type; + _direction = direction; + _node = node; + _dynamic = true; + } + + /// Checks all connections for invalid references, and removes them. + public void VerifyConnections() { + for (int i = connections.Count - 1; i >= 0; i--) { + if (connections[i].node != null && + !string.IsNullOrEmpty(connections[i].fieldName) && + connections[i].node.GetPort(connections[i].fieldName) != null) + continue; + connections.RemoveAt(i); + } + } + + /// Return the output value of this node through its parent nodes GetValue override method. + /// + public object GetOutputValue() { + if (direction == IO.Input) return null; + return node.GetValue(this); + } + + /// Return the output value of the first connected port. Returns null if none found or invalid. + /// + public object GetInputValue() { + NodePort connectedPort = Connection; + if (connectedPort == null) return null; + return connectedPort.GetOutputValue(); + } + + /// Return the output values of all connected ports. + /// + public object[] GetInputValues() { + object[] objs = new object[ConnectionCount]; + for (int i = 0; i < ConnectionCount; i++) { + NodePort connectedPort = connections[i].Port; + if (connectedPort == null) { // if we happen to find a null port, remove it and look again + connections.RemoveAt(i); + i--; + continue; + } + objs[i] = connectedPort.GetOutputValue(); + } + return objs; + } + + /// Return the output value of the first connected port. Returns null if none found or invalid. + /// + public T GetInputValue() { + object obj = GetInputValue(); + return obj is T ? (T) obj : default(T); + } + + /// Return the output values of all connected ports. + /// + public T[] GetInputValues() { + object[] objs = GetInputValues(); + T[] ts = new T[objs.Length]; + for (int i = 0; i < objs.Length; i++) { + if (objs[i] is T) ts[i] = (T) objs[i]; + } + return ts; + } + + /// Return true if port is connected and has a valid input. + /// + public bool TryGetInputValue(out T value) { + object obj = GetInputValue(); + if (obj is T) { + value = (T) obj; + return true; + } else { + value = default(T); + return false; + } + } + + /// Return the sum of all inputs. + /// + public float GetInputSum(float fallback) { + object[] objs = GetInputValues(); + if (objs.Length == 0) return fallback; + float result = 0; + for (int i = 0; i < objs.Length; i++) { + if (objs[i] is float) result += (float) objs[i]; + } + return result; + } + + /// Return the sum of all inputs. + /// + public int GetInputSum(int fallback) { + object[] objs = GetInputValues(); + if (objs.Length == 0) return fallback; + int result = 0; + for (int i = 0; i < objs.Length; i++) { + if (objs[i] is int) result += (int) objs[i]; + } + return result; + } + + /// Connect this to another + /// The to connect to + public void Connect(NodePort port) { + if (connections == null) connections = new List(); + if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; } + if (port == this) { Debug.LogWarning("Attempting to connect port to self."); return; } + if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; } + if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; } + connections.Add(new PortConnection(port)); + if (port.connections == null) port.connections = new List(); + if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this)); + node.OnCreateConnection(this, port); + port.node.OnCreateConnection(this, port); + } + + 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)) { + connections.RemoveAt(i); + return null; + } + NodePort port = connections[i].node.GetPort(connections[i].fieldName); + if (port == null) { + connections.RemoveAt(i); + return null; + } + return port; + } + + public bool IsConnectedTo(NodePort port) { + for (int i = 0; i < connections.Count; i++) { + if (connections[i].Port == port) return true; + } return false; } - } - /// Return the sum of all inputs. - /// - public float GetInputSum(float fallback) { - object[] objs = GetInputValues(); - if (objs.Length == 0) return fallback; - float result = 0; - for (int i = 0; i < objs.Length; i++) { - if (objs[i] is float) result += (float) objs[i]; - } - return result; - } - - /// Return the sum of all inputs. - /// - public int GetInputSum(int fallback) { - object[] objs = GetInputValues(); - if (objs.Length == 0) return fallback; - int result = 0; - for (int i = 0; i < objs.Length; i++) { - if (objs[i] is int) result += (int) objs[i]; - } - return result; - } - - /// Connect this to another - /// The to connect to - public void Connect(NodePort port) { - if (connections == null) connections = new List(); - if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; } - if (port == this) { Debug.LogWarning("Attempting to connect port to self."); return; } - if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; } - if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; } - connections.Add(new PortConnection(port)); - if (port.connections == null) port.connections = new List(); - if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this)); - node.OnCreateConnection(this, port); - port.node.OnCreateConnection(this, port); - } - - 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)) { - connections.RemoveAt(i); - return null; - } - NodePort port = connections[i].node.GetPort(connections[i].fieldName); - if (port == null) { - connections.RemoveAt(i); - return null; - } - return port; - } - - public bool IsConnectedTo(NodePort port) { - for (int i = 0; i < connections.Count; i++) { - if (connections[i].Port == port) return true; - } - return false; - } - - public void Disconnect(NodePort port) { - for (int i = connections.Count - 1; i >= 0; i--) { - //Remove matching ports. - if (connections[i].Port == port) { - connections.RemoveAt(i); + public void Disconnect(NodePort port) { + for (int i = connections.Count - 1; i >= 0; i--) { + //Remove matching ports. + if (connections[i].Port == port) { + connections.RemoveAt(i); + } + } + for (int i = 0; i < port.connections.Count; i++) { + if (port.connections[i].Port == this) { + port.connections.RemoveAt(i); + } } } - for (int i = 0; i < port.connections.Count; i++) { - if (port.connections[i].Port == this) { - port.connections.RemoveAt(i); + + public void ClearConnections() { + while (connections.Count > 0) { + Disconnect(connections[0].Port); } } - } - public void ClearConnections() { - while (connections.Count > 0) { - Disconnect(connections[0].Port); - } - } + [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; - [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; + public PortConnection(NodePort port) { + this.port = port; + node = port.node; + fieldName = port.fieldName; + } - 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); + /// 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