mirror of
https://github.com/Siccity/xNode.git
synced 2026-02-04 14:24:54 +08:00
Renamed to xNode
Added XNode and XNodeEDitor namespaces Removed unnecessary usings
This commit is contained in:
parent
6fcd16abd7
commit
c6a4735c71
@ -1,7 +1,7 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using XNode;
|
||||
|
||||
/// <summary> Defines an example nodegraph. </summary>
|
||||
[Serializable, CreateAssetMenu(fileName = "ExampleNodeGraph", menuName = "Node Graph/Example")]
|
||||
public class ExampleNodeGraph : NodeGraph {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
namespace BasicNodes {
|
||||
using XNode;
|
||||
|
||||
namespace BasicNodes {
|
||||
public class DisplayValue : Node {
|
||||
[Input(ShowBackingValue.Never)] public object value;
|
||||
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using XNodeEditor;
|
||||
|
||||
namespace BasicNodes {
|
||||
[CustomNodeEditor(typeof(DisplayValue))]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
namespace BasicNodes {
|
||||
public class Vector : Node {
|
||||
|
||||
@ -1,59 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
/// <summary> Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. </summary>
|
||||
public class NodeEditor {
|
||||
/// <summary> Fires every whenever a node was modified through the editor </summary>
|
||||
public static Action<Node> onUpdateNode;
|
||||
public Node target;
|
||||
public SerializedObject serializedObject;
|
||||
public static Dictionary<NodePort, Vector2> portPositions;
|
||||
namespace XNodeEditor {
|
||||
/// <summary> Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. </summary>
|
||||
public class NodeEditor {
|
||||
/// <summary> Fires every whenever a node was modified through the editor </summary>
|
||||
public static Action<Node> onUpdateNode;
|
||||
public Node target;
|
||||
public SerializedObject serializedObject;
|
||||
public static Dictionary<NodePort, Vector2> portPositions;
|
||||
|
||||
/// <summary> Draws the node GUI.</summary>
|
||||
/// <param name="portPositions">Port handle positions need to be returned to the NodeEditorWindow </param>
|
||||
public void OnNodeGUI() {
|
||||
OnHeaderGUI();
|
||||
OnBodyGUI();
|
||||
}
|
||||
/// <summary> Draws the node GUI.</summary>
|
||||
/// <param name="portPositions">Port handle positions need to be returned to the NodeEditorWindow </param>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary> Draws standard field editors for all public fields </summary>
|
||||
public virtual void OnBodyGUI() {
|
||||
string[] excludes = { "m_Script", "graph", "position", "ports" };
|
||||
portPositions = new Dictionary<NodePort, Vector2>();
|
||||
/// <summary> Draws standard field editors for all public fields </summary>
|
||||
public virtual void OnBodyGUI() {
|
||||
string[] excludes = { "m_Script", "graph", "position", "ports" };
|
||||
portPositions = new Dictionary<NodePort, Vector2>();
|
||||
|
||||
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;
|
||||
/// <summary> Tells a NodeEditor which Node type it is an editor for </summary>
|
||||
/// <param name="inspectedType">Type that this editor can edit</param>
|
||||
/// <param name="contextMenuName">Path to the node</param>
|
||||
public CustomNodeEditorAttribute(Type inspectedType) {
|
||||
_inspectedType = inspectedType;
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class CustomNodeEditorAttribute : Attribute {
|
||||
public Type inspectedType { get { return _inspectedType; } }
|
||||
private Type _inspectedType;
|
||||
/// <summary> Tells a NodeEditor which Node type it is an editor for </summary>
|
||||
/// <param name="inspectedType">Type that this editor can edit</param>
|
||||
/// <param name="contextMenuName">Path to the node</param>
|
||||
public CustomNodeEditorAttribute(Type inspectedType) {
|
||||
_inspectedType = inspectedType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Puts all nodes in focus. If no nodes are present, resets view to </summary>
|
||||
public void Home() {
|
||||
zoom = 2;
|
||||
panOffset = Vector2.zero;
|
||||
}
|
||||
|
||||
public void CreateNode(Type type, Vector2 position) {
|
||||
Node node = graph.AddNode(type);
|
||||
node.position = position;
|
||||
Repaint();
|
||||
}
|
||||
|
||||
/// <summary> Draw a connection as we are dragging it </summary>
|
||||
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);
|
||||
/// <summary> Puts all nodes in focus. If no nodes are present, resets view to </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary> Draw a connection as we are dragging it </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<UnityEngine.Object>(path);
|
||||
namespace XNodeEditor {
|
||||
public class NodeEditorAssetModProcessor : UnityEditor.AssetModificationProcessor {
|
||||
public static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options) {
|
||||
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(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;
|
||||
}
|
||||
}
|
||||
@ -1,286 +1,288 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
/// <summary> Contains GUI methods </summary>
|
||||
public partial class NodeEditorWindow {
|
||||
namespace XNodeEditor {
|
||||
/// <summary> Contains GUI methods </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary> Show right-click context menu </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary> Draw a bezier from startpoint to endpoint, both in grid coordinates </summary>
|
||||
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) {
|
||||
|
||||
/// <summary> Draws all connections </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary> Show right-click context menu </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary> Draw a bezier from startpoint to endpoint, both in grid coordinates </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary> Draws all connections </summary>
|
||||
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<NodePort, Vector2>();
|
||||
|
||||
//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<NodePort, Vector2>();
|
||||
|
||||
//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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/// <summary> UNEC-specific version of <see cref="EditorGUILayout"/> </summary>
|
||||
public static class NodeEditorGUILayout {
|
||||
namespace XNodeEditor {
|
||||
/// <summary> UNEC-specific version of <see cref="EditorGUILayout"/> </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
/// <summary> Have we loaded the prefs yet </summary>
|
||||
private static bool prefsLoaded = false;
|
||||
/// <summary> Have we loaded the prefs yet </summary>
|
||||
private static bool prefsLoaded = false;
|
||||
|
||||
private static Dictionary<string, Color> typeColors;
|
||||
private static Dictionary<string, Color> generatedTypeColors;
|
||||
private static Dictionary<string, Color> typeColors;
|
||||
private static Dictionary<string, Color> 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<string, Color>();
|
||||
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<string, Color> GetTypeColors() {
|
||||
if (prefsLoaded) return typeColors;
|
||||
if (!EditorPrefs.HasKey("unec_typecolors")) SetDefaultTypeColors();
|
||||
string[] data = EditorPrefs.GetString("unec_typecolors").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
Dictionary<string, Color> dict = new Dictionary<string, Color>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Return color based on type </summary>
|
||||
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<string, Color>();
|
||||
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<string, Color> GetTypeColors() {
|
||||
if (prefsLoaded) return typeColors;
|
||||
if (!EditorPrefs.HasKey("unec_typecolors")) SetDefaultTypeColors();
|
||||
string[] data = EditorPrefs.GetString("unec_typecolors").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
Dictionary<string, Color> dict = new Dictionary<string, Color>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Return color based on type </summary>
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,49 +2,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using XNode;
|
||||
|
||||
/// <summary> Contains reflection-related info </summary>
|
||||
public partial class NodeEditorWindow {
|
||||
[NonSerialized] private static Dictionary<Type, NodeEditor> customNodeEditor;
|
||||
public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } }
|
||||
[NonSerialized] private static Type[] _nodeTypes = null;
|
||||
namespace XNodeEditor {
|
||||
/// <summary> Contains reflection-related info </summary>
|
||||
public partial class NodeEditorWindow {
|
||||
[NonSerialized] private static Dictionary<Type, NodeEditor> 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<Type, NodeEditor>();
|
||||
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<Type, NodeEditor>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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<Texture2D>("unec_dot"); } }
|
||||
private static Texture2D _dot;
|
||||
public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = Resources.Load<Texture2D>("unec_dot_outer"); } }
|
||||
private static Texture2D _dotOuter;
|
||||
public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load<Texture2D>("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<Texture2D>("unec_dot"); } }
|
||||
private static Texture2D _dot;
|
||||
public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = Resources.Load<Texture2D>("unec_dot_outer"); } }
|
||||
private static Texture2D _dotOuter;
|
||||
public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load<Texture2D>("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;
|
||||
}
|
||||
}
|
||||
@ -1,54 +1,53 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary> A set of editor-only utilities and extensions for UnityNodeEditorBase </summary>
|
||||
public static class NodeEditorUtilities {
|
||||
namespace XNodeEditor {
|
||||
/// <summary> A set of editor-only utilities and extensions for UnityNodeEditorBase </summary>
|
||||
public static class NodeEditorUtilities {
|
||||
|
||||
public static bool GetAttrib<T>(Type classType, out T attribOut) where T : Attribute {
|
||||
object[] attribs = classType.GetCustomAttributes(typeof(T), false);
|
||||
return GetAttrib(attribs, out attribOut);
|
||||
}
|
||||
|
||||
public static bool GetAttrib<T>(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<T>(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<T>(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<T>(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;
|
||||
}
|
||||
|
||||
/// <summary> Turns camelCaseString into Camel Case String </summary>
|
||||
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<T>(object[] attribs) where T : Attribute {
|
||||
for (int i = 0; i < attribs.Length; i++) {
|
||||
if (attribs[i].GetType() == typeof(T)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary> Returns true if this can be casted to <see cref="Type"/></summary>
|
||||
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;
|
||||
/// <summary> Turns camelCaseString into Camel Case String </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary> Returns true if this can be casted to <see cref="Type"/></summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/// <summary> Stores node positions for all nodePorts. </summary>
|
||||
public Dictionary<NodePort, Rect> portConnectionPoints { get { return _portConnectionPoints; } }
|
||||
private Dictionary<NodePort, Rect> _portConnectionPoints = new Dictionary<NodePort, Rect>();
|
||||
public Dictionary<Node, float> nodeWidths { get { return _nodeWidths; } }
|
||||
private Dictionary<Node, float> _nodeWidths = new Dictionary<Node, float>();
|
||||
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;
|
||||
/// <summary> Stores node positions for all nodePorts. </summary>
|
||||
public Dictionary<NodePort, Rect> portConnectionPoints { get { return _portConnectionPoints; } }
|
||||
private Dictionary<NodePort, Rect> _portConnectionPoints = new Dictionary<NodePort, Rect>();
|
||||
public Dictionary<Node, float> nodeWidths { get { return _nodeWidths; } }
|
||||
private Dictionary<Node, float> _nodeWidths = new Dictionary<Node, float>();
|
||||
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();
|
||||
/// <summary> Create editor window </summary>
|
||||
//[MenuItem("Window/UNEC")]
|
||||
public static NodeEditorWindow Init() {
|
||||
NodeEditorWindow w = CreateInstance<NodeEditorWindow>();
|
||||
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<NodeGraph>(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();
|
||||
/// <summary> Create editor window </summary>
|
||||
//[MenuItem("Window/UNEC")]
|
||||
public static NodeEditorWindow Init() {
|
||||
NodeEditorWindow w = CreateInstance<NodeEditorWindow>();
|
||||
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<NodeGraph>(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;
|
||||
}
|
||||
}
|
||||
338
Scripts/Node.cs
338
Scripts/Node.cs
@ -2,198 +2,200 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary> Base class for all nodes </summary>
|
||||
[Serializable]
|
||||
public abstract class Node : ScriptableObject {
|
||||
public enum ShowBackingValue {
|
||||
/// <summary> Never show the backing value </summary>
|
||||
Never,
|
||||
/// <summary> Show the backing value only when the port does not have any active connections </summary>
|
||||
Unconnected,
|
||||
/// <summary> Always show the backing value </summary>
|
||||
Always
|
||||
}
|
||||
|
||||
/// <summary> Iterate over all ports on this node. </summary>
|
||||
public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
|
||||
/// <summary> Iterate over all outputs on this node. </summary>
|
||||
public IEnumerable<NodePort> Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } }
|
||||
/// <summary> Iterate over all inputs on this node. </summary>
|
||||
public IEnumerable<NodePort> Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } }
|
||||
/// <summary> Iterate over all instane ports on this node. </summary>
|
||||
public IEnumerable<NodePort> InstancePorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } }
|
||||
/// <summary> Iterate over all instance outputs on this node. </summary>
|
||||
public IEnumerable<NodePort> InstanceOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } }
|
||||
/// <summary> Iterate over all instance inputs on this node. </summary>
|
||||
public IEnumerable<NodePort> InstanceInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } }
|
||||
/// <summary> Parent <see cref="NodeGraph"/> </summary>
|
||||
[SerializeField] public NodeGraph graph;
|
||||
/// <summary> Position on the <see cref="NodeGraph"/> </summary>
|
||||
[SerializeField] public Vector2 position;
|
||||
/// <summary> Input <see cref="NodePort"/>s. It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> </summary>
|
||||
[SerializeField] private NodePortDictionary ports = new NodePortDictionary();
|
||||
|
||||
protected void OnEnable() {
|
||||
NodeDataCache.UpdatePorts(this, ports);
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary> Initialize node. Called on creation. </summary>
|
||||
protected virtual void Init() { name = GetType().Name; }
|
||||
|
||||
/// <summary> Checks all connections for invalid references, and removes them. </summary>
|
||||
public void VerifyConnections() {
|
||||
foreach (NodePort port in Ports) port.VerifyConnections();
|
||||
}
|
||||
|
||||
#region Instance Ports
|
||||
/// <summary> Returns input port at index </summary>
|
||||
public NodePort AddInstanceInput(Type type, string fieldName = null) {
|
||||
return AddInstancePort(type, NodePort.IO.Input, fieldName);
|
||||
}
|
||||
|
||||
/// <summary> Returns input port at index </summary>
|
||||
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 {
|
||||
/// <summary> Base class for all nodes </summary>
|
||||
[Serializable]
|
||||
public abstract class Node : ScriptableObject {
|
||||
public enum ShowBackingValue {
|
||||
/// <summary> Never show the backing value </summary>
|
||||
Never,
|
||||
/// <summary> Show the backing value only when the port does not have any active connections </summary>
|
||||
Unconnected,
|
||||
/// <summary> Always show the backing value </summary>
|
||||
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
|
||||
/// <summary> Iterate over all ports on this node. </summary>
|
||||
public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
|
||||
/// <summary> Iterate over all outputs on this node. </summary>
|
||||
public IEnumerable<NodePort> Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } }
|
||||
/// <summary> Iterate over all inputs on this node. </summary>
|
||||
public IEnumerable<NodePort> Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } }
|
||||
/// <summary> Iterate over all instane ports on this node. </summary>
|
||||
public IEnumerable<NodePort> InstancePorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } }
|
||||
/// <summary> Iterate over all instance outputs on this node. </summary>
|
||||
public IEnumerable<NodePort> InstanceOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } }
|
||||
/// <summary> Iterate over all instance inputs on this node. </summary>
|
||||
public IEnumerable<NodePort> InstanceInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } }
|
||||
/// <summary> Parent <see cref="NodeGraph"/> </summary>
|
||||
[SerializeField] public NodeGraph graph;
|
||||
/// <summary> Position on the <see cref="NodeGraph"/> </summary>
|
||||
[SerializeField] public Vector2 position;
|
||||
/// <summary> Input <see cref="NodePort"/>s. It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> </summary>
|
||||
[SerializeField] private NodePortDictionary ports = new NodePortDictionary();
|
||||
|
||||
#region Ports
|
||||
/// <summary> Returns output port which matches fieldName </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary> Returns input port which matches fieldName </summary>
|
||||
public NodePort GetInputPort(string fieldName) {
|
||||
NodePort port = GetPort(fieldName);
|
||||
if (port == null || port.direction != NodePort.IO.Input) return null;
|
||||
else return port;
|
||||
}
|
||||
/// <summary> Initialize node. Called on creation. </summary>
|
||||
protected virtual void Init() { name = GetType().Name; }
|
||||
|
||||
/// <summary> Returns port which matches fieldName </summary>
|
||||
public NodePort GetPort(string fieldName) {
|
||||
if (ports.ContainsKey(fieldName)) return ports[fieldName];
|
||||
else return null;
|
||||
}
|
||||
/// <summary> Checks all connections for invalid references, and removes them. </summary>
|
||||
public void VerifyConnections() {
|
||||
foreach (NodePort port in Ports) port.VerifyConnections();
|
||||
}
|
||||
|
||||
public bool HasPort(string fieldName) {
|
||||
return ports.ContainsKey(fieldName);
|
||||
}
|
||||
#endregion
|
||||
#region Instance Ports
|
||||
/// <summary> Returns input port at index </summary>
|
||||
public NodePort AddInstanceInput(Type type, string fieldName = null) {
|
||||
return AddInstancePort(type, NodePort.IO.Input, fieldName);
|
||||
}
|
||||
|
||||
#region Inputs/Outputs
|
||||
/// <summary> Return input value for a specified port. Returns fallback value if no ports are connected </summary>
|
||||
/// <param name="fieldName">Field name of requested input port</param>
|
||||
/// <param name="fallback">If no ports are connected, this value will be returned</param>
|
||||
public T GetInputValue<T>(string fieldName, T fallback = default(T)) {
|
||||
NodePort port = GetPort(fieldName);
|
||||
if (port != null && port.IsConnected) return port.GetInputValue<T>();
|
||||
else return fallback;
|
||||
}
|
||||
/// <summary> Returns input port at index </summary>
|
||||
public NodePort AddInstanceOutput(Type type, string fieldName = null) {
|
||||
return AddInstancePort(type, NodePort.IO.Output, fieldName);
|
||||
}
|
||||
|
||||
/// <summary> Return all input values for a specified port. Returns fallback value if no ports are connected </summary>
|
||||
/// <param name="fieldName">Field name of requested input port</param>
|
||||
/// <param name="fallback">If no ports are connected, this value will be returned</param>
|
||||
public T[] GetInputValues<T>(string fieldName, params T[] fallback) {
|
||||
NodePort port = GetPort(fieldName);
|
||||
if (port != null && port.IsConnected) return port.GetInputValues<T>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Returns a value based on requested port output. Should be overridden before used. </summary>
|
||||
/// <param name="port">The requested port.</param>
|
||||
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
|
||||
|
||||
/// <summary> Called whenever a connection is being made between two <see cref="NodePort"/>s</summary>
|
||||
/// <param name="from">Output</param> <param name="to">Input</param>
|
||||
public virtual void OnCreateConnection(NodePort from, NodePort to) { }
|
||||
#region Ports
|
||||
/// <summary> Returns output port which matches fieldName </summary>
|
||||
public NodePort GetOutputPort(string fieldName) {
|
||||
NodePort port = GetPort(fieldName);
|
||||
if (port == null || port.direction != NodePort.IO.Output) return null;
|
||||
else return port;
|
||||
}
|
||||
|
||||
/// <summary> Disconnect everything from this node </summary>
|
||||
public void ClearConnections() {
|
||||
foreach (NodePort port in Ports) port.ClearConnections();
|
||||
}
|
||||
/// <summary> Returns input port which matches fieldName </summary>
|
||||
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();
|
||||
}
|
||||
/// <summary> Returns port which matches fieldName </summary>
|
||||
public NodePort GetPort(string fieldName) {
|
||||
if (ports.ContainsKey(fieldName)) return ports[fieldName];
|
||||
else return null;
|
||||
}
|
||||
|
||||
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInput(string)"/> </summary>
|
||||
[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
|
||||
/// <summary> Return input value for a specified port. Returns fallback value if no ports are connected </summary>
|
||||
/// <param name="fieldName">Field name of requested input port</param>
|
||||
/// <param name="fallback">If no ports are connected, this value will be returned</param>
|
||||
public T GetInputValue<T>(string fieldName, T fallback = default(T)) {
|
||||
NodePort port = GetPort(fieldName);
|
||||
if (port != null && port.IsConnected) return port.GetInputValue<T>();
|
||||
else return fallback;
|
||||
}
|
||||
|
||||
/// <summary> Return all input values for a specified port. Returns fallback value if no ports are connected </summary>
|
||||
/// <param name="fieldName">Field name of requested input port</param>
|
||||
/// <param name="fallback">If no ports are connected, this value will be returned</param>
|
||||
public T[] GetInputValues<T>(string fieldName, params T[] fallback) {
|
||||
NodePort port = GetPort(fieldName);
|
||||
if (port != null && port.IsConnected) return port.GetInputValues<T>();
|
||||
else return fallback;
|
||||
}
|
||||
|
||||
/// <summary> Returns a value based on requested port output. Should be overridden before used. </summary>
|
||||
/// <param name="port">The requested port.</param>
|
||||
public virtual object GetValue(NodePort port) {
|
||||
Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType());
|
||||
return null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary> Called whenever a connection is being made between two <see cref="NodePort"/>s</summary>
|
||||
/// <param name="from">Output</param> <param name="to">Input</param>
|
||||
public virtual void OnCreateConnection(NodePort from, NodePort to) { }
|
||||
|
||||
/// <summary> Disconnect everything from this node </summary>
|
||||
public void ClearConnections() {
|
||||
foreach (NodePort port in Ports) port.ClearConnections();
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return JsonUtility.ToJson(this).GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInput(string)"/> </summary>
|
||||
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
|
||||
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected) { this.backingValue = backingValue; }
|
||||
}
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||
public class InputAttribute : Attribute {
|
||||
public ShowBackingValue backingValue;
|
||||
|
||||
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutput(string)"/> </summary>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||
public class OutputAttribute : Attribute {
|
||||
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutput(string)"/> </summary>
|
||||
public OutputAttribute() { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class CreateNodeMenuAttribute : Attribute {
|
||||
public string menuName;
|
||||
/// <summary> Manually supply node class with a context menu path </summary>
|
||||
/// <param name="menuName"> Path to this node in the context menu </param>
|
||||
public CreateNodeMenuAttribute(string menuName) {
|
||||
this.menuName = menuName;
|
||||
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInput(string)"/> </summary>
|
||||
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
|
||||
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected) { this.backingValue = backingValue; }
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable] private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver {
|
||||
[SerializeField] private List<string> keys = new List<string>();
|
||||
[SerializeField] private List<NodePort> values = new List<NodePort>();
|
||||
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutput(string)"/> </summary>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||
public class OutputAttribute : Attribute {
|
||||
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutput(string)"/> </summary>
|
||||
public OutputAttribute() { }
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize() {
|
||||
keys.Clear();
|
||||
values.Clear();
|
||||
foreach (KeyValuePair<string, NodePort> pair in this) {
|
||||
keys.Add(pair.Key);
|
||||
values.Add(pair.Value);
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class CreateNodeMenuAttribute : Attribute {
|
||||
public string menuName;
|
||||
/// <summary> Manually supply node class with a context menu path </summary>
|
||||
/// <param name="menuName"> Path to this node in the context menu </param>
|
||||
public CreateNodeMenuAttribute(string menuName) {
|
||||
this.menuName = menuName;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize() {
|
||||
this.Clear();
|
||||
[Serializable] private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver {
|
||||
[SerializeField] private List<string> keys = new List<string>();
|
||||
[SerializeField] private List<NodePort> values = new List<NodePort>();
|
||||
|
||||
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<string, NodePort> 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/// <summary> Precaches reflection data in editor so we won't have to do it runtime </summary>
|
||||
public static class NodeDataCache {
|
||||
private static PortDataCache portDataCache;
|
||||
private static bool Initialized { get { return portDataCache != null; } }
|
||||
namespace XNode {
|
||||
/// <summary> Precaches reflection data in editor so we won't have to do it runtime </summary>
|
||||
public static class NodeDataCache {
|
||||
private static PortDataCache portDataCache;
|
||||
private static bool Initialized { get { return portDataCache != null; } }
|
||||
|
||||
/// <summary> Update static ports to reflect class fields. </summary>
|
||||
public static void UpdatePorts(Node node, Dictionary<string, NodePort> ports) {
|
||||
if (!Initialized) BuildCache();
|
||||
/// <summary> Update static ports to reflect class fields. </summary>
|
||||
public static void UpdatePorts(Node node, Dictionary<string, NodePort> ports) {
|
||||
if (!Initialized) BuildCache();
|
||||
|
||||
Dictionary<string, NodePort> staticPorts = new Dictionary<string, NodePort>();
|
||||
System.Type nodeType = node.GetType();
|
||||
Dictionary<string, NodePort> staticPorts = new Dictionary<string, NodePort>();
|
||||
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<NodePort>());
|
||||
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<System.Type, List<NodePort>>, ISerializationCallbackReceiver {
|
||||
[SerializeField] private List<System.Type> keys = new List<System.Type>();
|
||||
[SerializeField] private List<List<NodePort>> values = new List<List<NodePort>>();
|
||||
|
||||
// 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<NodePort>());
|
||||
portDataCache[nodeType].Add(new NodePort(fieldInfo[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class PortDataCache : Dictionary<System.Type, List<NodePort>>, ISerializationCallbackReceiver {
|
||||
[SerializeField] private List<System.Type> keys = new List<System.Type>();
|
||||
[SerializeField] private List<List<NodePort>> values = new List<List<NodePort>>();
|
||||
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,46 +2,48 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary> Base class for all node graphs </summary>
|
||||
[Serializable]
|
||||
public abstract class NodeGraph : ScriptableObject {
|
||||
namespace XNode {
|
||||
/// <summary> Base class for all node graphs </summary>
|
||||
[Serializable]
|
||||
public abstract class NodeGraph : ScriptableObject {
|
||||
|
||||
/// <summary> All nodes in the graph. <para/>
|
||||
/// See: <see cref="AddNode{T}"/> </summary>
|
||||
[SerializeField] public List<Node> nodes = new List<Node>();
|
||||
/// <summary> All nodes in the graph. <para/>
|
||||
/// See: <see cref="AddNode{T}"/> </summary>
|
||||
[SerializeField] public List<Node> nodes = new List<Node>();
|
||||
|
||||
public T AddNode<T>() 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<T>() where T : Node {
|
||||
return AddNode(typeof(T)) as T;
|
||||
}
|
||||
#endif
|
||||
nodes.Add(node);
|
||||
node.graph = this;
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary> Safely remove a node and all its connections </summary>
|
||||
/// <param name="node"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Remove all nodes and connections from the graph </summary>
|
||||
public void Clear() {
|
||||
nodes.Clear();
|
||||
/// <summary> Safely remove a node and all its connections </summary>
|
||||
/// <param name="node"></param>
|
||||
public void RemoveNode(Node node) {
|
||||
node.ClearConnections();
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
DestroyImmediate(node, true);
|
||||
UnityEditor.AssetDatabase.SaveAssets();
|
||||
}
|
||||
#endif
|
||||
nodes.Remove(node);
|
||||
}
|
||||
|
||||
/// <summary> Remove all nodes and connections from the graph </summary>
|
||||
public void Clear() {
|
||||
nodes.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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; } }
|
||||
/// <summary> Return the first connection </summary>
|
||||
public NodePort Connection { get { return connections.Count > 0 ? connections[0].Port : null; } }
|
||||
public int ConnectionCount { get { return connections.Count; } }
|
||||
/// <summary> Return the first connection </summary>
|
||||
public NodePort Connection { get { return connections.Count > 0 ? connections[0].Port : null; } }
|
||||
|
||||
public IO direction { get { return _direction; } }
|
||||
/// <summary> Is this port connected to anytihng? </summary>
|
||||
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; } }
|
||||
/// <summary> Is this port connected to anytihng? </summary>
|
||||
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<PortConnection> connections = new List<PortConnection>();
|
||||
[SerializeField] private IO _direction;
|
||||
[SerializeField] private bool _dynamic;
|
||||
|
||||
/// <summary> Construct a static targetless nodeport. Used as a template. </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Copy a nodePort but assign it to another node. </summary>
|
||||
public NodePort(NodePort nodePort, Node node) {
|
||||
_fieldName = nodePort._fieldName;
|
||||
ValueType = nodePort.valueType;
|
||||
_direction = nodePort.direction;
|
||||
_dynamic = nodePort._dynamic;
|
||||
_node = node;
|
||||
}
|
||||
|
||||
/// <summary> Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. </summary>
|
||||
public NodePort(string fieldName, Type type, IO direction, Node node) {
|
||||
_fieldName = fieldName;
|
||||
this.ValueType = type;
|
||||
_direction = direction;
|
||||
_node = node;
|
||||
_dynamic = true;
|
||||
}
|
||||
|
||||
/// <summary> Checks all connections for invalid references, and removes them. </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Return the output value of this node through its parent nodes GetValue override method. </summary>
|
||||
/// <returns> <see cref="Node.GetValue(NodePort)"/> </returns>
|
||||
public object GetOutputValue() {
|
||||
if (direction == IO.Input) return null;
|
||||
return node.GetValue(this);
|
||||
}
|
||||
|
||||
/// <summary> Return the output value of the first connected port. Returns null if none found or invalid.</summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
public object GetInputValue() {
|
||||
NodePort connectedPort = Connection;
|
||||
if (connectedPort == null) return null;
|
||||
return connectedPort.GetOutputValue();
|
||||
}
|
||||
|
||||
/// <summary> Return the output values of all connected ports. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
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;
|
||||
|
||||
/// <summary> Return the output value of the first connected port. Returns null if none found or invalid. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
public T GetInputValue<T>() {
|
||||
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<PortConnection> connections = new List<PortConnection>();
|
||||
[SerializeField] private IO _direction;
|
||||
[SerializeField] private bool _dynamic;
|
||||
|
||||
/// <summary> Return the output values of all connected ports. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
public T[] GetInputValues<T>() {
|
||||
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];
|
||||
/// <summary> Construct a static targetless nodeport. Used as a template. </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Return true if port is connected and has a valid input. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
public bool TryGetInputValue<T>(out T value) {
|
||||
object obj = GetInputValue();
|
||||
if (obj is T) {
|
||||
value = (T) obj;
|
||||
return true;
|
||||
} else {
|
||||
value = default(T);
|
||||
/// <summary> Copy a nodePort but assign it to another node. </summary>
|
||||
public NodePort(NodePort nodePort, Node node) {
|
||||
_fieldName = nodePort._fieldName;
|
||||
ValueType = nodePort.valueType;
|
||||
_direction = nodePort.direction;
|
||||
_dynamic = nodePort._dynamic;
|
||||
_node = node;
|
||||
}
|
||||
|
||||
/// <summary> Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. </summary>
|
||||
public NodePort(string fieldName, Type type, IO direction, Node node) {
|
||||
_fieldName = fieldName;
|
||||
this.ValueType = type;
|
||||
_direction = direction;
|
||||
_node = node;
|
||||
_dynamic = true;
|
||||
}
|
||||
|
||||
/// <summary> Checks all connections for invalid references, and removes them. </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Return the output value of this node through its parent nodes GetValue override method. </summary>
|
||||
/// <returns> <see cref="Node.GetValue(NodePort)"/> </returns>
|
||||
public object GetOutputValue() {
|
||||
if (direction == IO.Input) return null;
|
||||
return node.GetValue(this);
|
||||
}
|
||||
|
||||
/// <summary> Return the output value of the first connected port. Returns null if none found or invalid.</summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
public object GetInputValue() {
|
||||
NodePort connectedPort = Connection;
|
||||
if (connectedPort == null) return null;
|
||||
return connectedPort.GetOutputValue();
|
||||
}
|
||||
|
||||
/// <summary> Return the output values of all connected ports. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Return the output value of the first connected port. Returns null if none found or invalid. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
public T GetInputValue<T>() {
|
||||
object obj = GetInputValue();
|
||||
return obj is T ? (T) obj : default(T);
|
||||
}
|
||||
|
||||
/// <summary> Return the output values of all connected ports. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
public T[] GetInputValues<T>() {
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Return true if port is connected and has a valid input. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
public bool TryGetInputValue<T>(out T value) {
|
||||
object obj = GetInputValue();
|
||||
if (obj is T) {
|
||||
value = (T) obj;
|
||||
return true;
|
||||
} else {
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Return the sum of all inputs. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Return the sum of all inputs. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Connect this <see cref="NodePort"/> to another </summary>
|
||||
/// <param name="port">The <see cref="NodePort"/> to connect to</param>
|
||||
public void Connect(NodePort port) {
|
||||
if (connections == null) connections = new List<PortConnection>();
|
||||
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<PortConnection>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Return the sum of all inputs. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Return the sum of all inputs. </summary>
|
||||
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Connect this <see cref="NodePort"/> to another </summary>
|
||||
/// <param name="port">The <see cref="NodePort"/> to connect to</param>
|
||||
public void Connect(NodePort port) {
|
||||
if (connections == null) connections = new List<PortConnection>();
|
||||
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<PortConnection>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary> Returns the port that this <see cref="PortConnection"/> points to </summary>
|
||||
private NodePort GetPort() {
|
||||
if (node == null || string.IsNullOrEmpty(fieldName)) return null;
|
||||
return node.GetPort(fieldName);
|
||||
/// <summary> Returns the port that this <see cref="PortConnection"/> points to </summary>
|
||||
private NodePort GetPort() {
|
||||
if (node == null || string.IsNullOrEmpty(fieldName)) return null;
|
||||
return node.GetPort(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user