1
0
mirror of https://github.com/Siccity/xNode.git synced 2025-12-20 17:26:02 +08:00
This commit is contained in:
Thor Brigsted 2019-05-08 22:53:15 +02:00
parent f03dbeb159
commit e89671480c
16 changed files with 112 additions and 93 deletions

View File

@ -7,11 +7,11 @@ using UnityEngine;
namespace XNodeEditor { 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> /// <summary> Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. </summary>
[CustomNodeEditor(typeof(XNode.Node))] [CustomNodeEditor(typeof(XNode.INode))]
public class NodeEditor : XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node> { public class NodeEditor : XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.INode> {
/// <summary> Fires every whenever a node was modified through the editor </summary> /// <summary> Fires every whenever a node was modified through the editor </summary>
public static Action<XNode.Node> onUpdateNode; public static Action<XNode.INode> onUpdateNode;
public static Dictionary<XNode.NodePort, Vector2> portPositions; public static Dictionary<XNode.NodePort, Vector2> portPositions;
public virtual void OnHeaderGUI() { public virtual void OnHeaderGUI() {
@ -67,9 +67,9 @@ namespace XNodeEditor {
/// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary> /// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary>
public virtual void AddContextMenuItems(GenericMenu menu) { public virtual void AddContextMenuItems(GenericMenu menu) {
// Actions if only one node is selected // Actions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { if (Selection.objects.Length == 1 && Selection.activeObject is XNode.INode) {
XNode.Node node = Selection.activeObject as XNode.Node; XNode.INode node = Selection.activeObject as XNode.INode;
menu.AddItem(new GUIContent("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node)); menu.AddItem(new GUIContent("Move To Top"), false, () => node.Graph.MoveNodeToTop(node));
menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode); menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode);
} }
@ -78,8 +78,8 @@ namespace XNodeEditor {
menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes); menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes);
// Custom sctions if only one node is selected // Custom sctions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { if (Selection.objects.Length == 1 && Selection.activeObject is XNode.INode) {
XNode.Node node = Selection.activeObject as XNode.Node; XNode.INode node = Selection.activeObject as XNode.INode;
NodeEditorWindow.AddCustomContextMenuItems(menu, node); NodeEditorWindow.AddCustomContextMenuItems(menu, node);
} }
} }
@ -93,7 +93,7 @@ namespace XNodeEditor {
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class CustomNodeEditorAttribute : Attribute, public class CustomNodeEditorAttribute : Attribute,
XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node>.INodeEditorAttrib { XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.INode>.INodeEditorAttrib {
private Type inspectedType; private Type inspectedType;
/// <summary> Tells a NodeEditor which Node type it is an editor for </summary> /// <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="inspectedType">Type that this editor can edit</param>

View File

@ -83,15 +83,17 @@ namespace XNodeEditor {
for (int i = 0; i < Selection.objects.Length; i++) { for (int i = 0; i < Selection.objects.Length; i++) {
if (Selection.objects[i] is XNode.INode) { if (Selection.objects[i] is XNode.INode) {
XNode.INode node = Selection.objects[i] as XNode.INode; XNode.INode node = Selection.objects[i] as XNode.INode;
Vector2 initial = node.position; Vector2 initial = node.Position;
node.position = mousePos + dragOffset[i]; node.Position = mousePos + dragOffset[i];
if (gridSnap) { if (gridSnap) {
node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8; Vector2 position = node.Position;
node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8; position.x = (Mathf.Round((node.Position.x + 8) / 16) * 16) - 8;
position.y = (Mathf.Round((node.Position.y + 8) / 16) * 16) - 8;
node.Position = position;
} }
// Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame. // Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame.
Vector2 offset = node.position - initial; Vector2 offset = node.Position - initial;
if (offset.sqrMagnitude > 0) { if (offset.sqrMagnitude > 0) {
foreach (XNode.NodePort output in node.Outputs) { foreach (XNode.NodePort output in node.Outputs) {
Rect rect; Rect rect;
@ -163,7 +165,7 @@ namespace XNodeEditor {
} }
} else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
// If mousedown on node header, select or deselect // If mousedown on node header, select or deselect
if (!Selection.Contains(hoveredNode)) { if (!Selection.Contains((UnityEngine.Object)hoveredNode)) {
SelectNode(hoveredNode, e.control || e.shift); SelectNode(hoveredNode, e.control || e.shift);
if (!e.control && !e.shift) selectedReroutes.Clear(); if (!e.control && !e.shift) selectedReroutes.Clear();
} else if (e.control || e.shift) DeselectNode(hoveredNode); } else if (e.control || e.shift) DeselectNode(hoveredNode);
@ -243,7 +245,7 @@ namespace XNodeEditor {
// Double click to center node // Double click to center node
if (isDoubleClick) { if (isDoubleClick) {
Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero; Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero;
panOffset = -hoveredNode.position - nodeDimension; panOffset = -hoverednode.Position - nodeDimension;
} }
} }
@ -267,7 +269,7 @@ namespace XNodeEditor {
} else if (IsHoveringPort) { } else if (IsHoveringPort) {
ShowPortContextMenu(hoveredPort); ShowPortContextMenu(hoveredPort);
} else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false); if (!Selection.Contains((UnityEngine.Object)hoveredNode)) SelectNode(hoveredNode, false);
GenericMenu menu = new GenericMenu(); GenericMenu menu = new GenericMenu();
NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu); NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
@ -330,7 +332,7 @@ namespace XNodeEditor {
for (int i = 0; i < Selection.objects.Length; i++) { for (int i = 0; i < Selection.objects.Length; i++) {
if (Selection.objects[i] is XNode.INode) { if (Selection.objects[i] is XNode.INode) {
XNode.INode node = Selection.objects[i] as XNode.INode; XNode.INode node = Selection.objects[i] as XNode.INode;
dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition); dragOffset[i] = node.Position - WindowToGridPosition(current.mousePosition);
} }
} }
@ -355,8 +357,8 @@ namespace XNodeEditor {
} }
selectedReroutes.Clear(); selectedReroutes.Clear();
foreach (UnityEngine.Object item in Selection.objects) { foreach (UnityEngine.Object item in Selection.objects) {
if (item is XNode.Node) { if (item is XNode.INode) {
XNode.Node node = item as XNode.Node; XNode.INode node = item as XNode.INode;
graphEditor.RemoveNode(node); graphEditor.RemoveNode(node);
} }
} }
@ -364,8 +366,8 @@ namespace XNodeEditor {
/// <summary> Initiate a rename on the currently selected node </summary> /// <summary> Initiate a rename on the currently selected node </summary>
public void RenameSelectedNode() { public void RenameSelectedNode() {
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { if (Selection.objects.Length == 1 && Selection.activeObject is XNode.INode) {
XNode.Node node = Selection.activeObject as XNode.Node; XNode.INode node = Selection.activeObject as XNode.INode;
Vector2 size; Vector2 size;
if (nodeSizes.TryGetValue(node, out size)) { if (nodeSizes.TryGetValue(node, out size)) {
RenamePopup.Show(Selection.activeObject, size.x); RenamePopup.Show(Selection.activeObject, size.x);
@ -375,41 +377,32 @@ namespace XNodeEditor {
} }
} }
/// <summary> Draw this node on top of other nodes by placing it last in the graph.nodes list </summary>
public void MoveNodeToTop(XNode.Node node) {
int index;
while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) {
graph.nodes[index] = graph.nodes[index + 1];
graph.nodes[index + 1] = node;
}
}
/// <summary> Duplicate selected nodes and select the duplicates </summary> /// <summary> Duplicate selected nodes and select the duplicates </summary>
public void DuplicateSelectedNodes() { public void DuplicateSelectedNodes() {
UnityEngine.Object[] newNodes = new UnityEngine.Object[Selection.objects.Length]; UnityEngine.Object[] newNodes = new UnityEngine.Object[Selection.objects.Length];
Dictionary<XNode.Node, XNode.Node> substitutes = new Dictionary<XNode.Node, XNode.Node>(); Dictionary<XNode.INode, XNode.INode> substitutes = new Dictionary<XNode.INode, XNode.INode>();
for (int i = 0; i < Selection.objects.Length; i++) { for (int i = 0; i < Selection.objects.Length; i++) {
if (Selection.objects[i] is XNode.Node) { if (Selection.objects[i] is XNode.INode) {
XNode.Node srcNode = Selection.objects[i] as XNode.Node; XNode.INode srcNode = Selection.objects[i] as XNode.INode;
if (srcNode.graph != graph) continue; // ignore nodes selected in another graph if (srcNode.Graph != graph) continue; // ignore nodes selected in another graph
XNode.Node newNode = graphEditor.CopyNode(srcNode); XNode.INode newNode = graphEditor.CopyNode(srcNode);
substitutes.Add(srcNode, newNode); substitutes.Add(srcNode, newNode);
newNode.position = srcNode.position + new Vector2(30, 30); newnode.Position = srcnode.Position + new Vector2(30, 30);
newNodes[i] = newNode; newNodes[i] = newNode;
} }
} }
// Walk through the selected nodes again, recreate connections, using the new nodes // Walk through the selected nodes again, recreate connections, using the new nodes
for (int i = 0; i < Selection.objects.Length; i++) { for (int i = 0; i < Selection.objects.Length; i++) {
if (Selection.objects[i] is XNode.Node) { if (Selection.objects[i] is XNode.INode) {
XNode.Node srcNode = Selection.objects[i] as XNode.Node; XNode.INode srcNode = Selection.objects[i] as XNode.INode;
if (srcNode.graph != graph) continue; // ignore nodes selected in another graph if (srcNode.graph != graph) continue; // ignore nodes selected in another graph
foreach (XNode.NodePort port in srcNode.Ports) { foreach (XNode.NodePort port in srcNode.Ports) {
for (int c = 0; c < port.ConnectionCount; c++) { for (int c = 0; c < port.ConnectionCount; c++) {
XNode.NodePort inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c); XNode.NodePort inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c);
XNode.NodePort outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c); XNode.NodePort outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c);
XNode.Node newNodeIn, newNodeOut; XNode.INode newNodeIn, newNodeOut;
if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) { if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) {
newNodeIn.UpdateStaticPorts(); newNodeIn.UpdateStaticPorts();
newNodeOut.UpdateStaticPorts(); newNodeOut.UpdateStaticPorts();
@ -459,10 +452,10 @@ namespace XNodeEditor {
} }
} }
bool IsHoveringTitle(XNode.Node node) { bool IsHoveringTitle(XNode.INode node) {
Vector2 mousePos = Event.current.mousePosition; Vector2 mousePos = Event.current.mousePosition;
//Get node position //Get node position
Vector2 nodePos = GridToWindowPosition(node.position); Vector2 nodePos = GridToWindowPosition(node.Position);
float width; float width;
Vector2 size; Vector2 size;
if (nodeSizes.TryGetValue(node, out size)) width = size.x; if (nodeSizes.TryGetValue(node, out size)) width = size.x;

View File

@ -17,7 +17,7 @@ namespace XNodeEditor {
// Check script type. Return if deleting a non-node script // Check script type. Return if deleting a non-node script
UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; UnityEditor.MonoScript script = obj as UnityEditor.MonoScript;
System.Type scriptType = script.GetClass (); System.Type scriptType = script.GetClass ();
if (scriptType == null || (scriptType != typeof (XNode.Node) && !scriptType.IsSubclassOf (typeof (XNode.Node)))) return AssetDeleteResult.DidNotDelete; if (scriptType == null || (scriptType != typeof (XNode.INode) && !scriptType.IsSubclassOf (typeof (XNode.INode)))) return AssetDeleteResult.DidNotDelete;
// Find all ScriptableObjects using this script // Find all ScriptableObjects using this script
string[] guids = AssetDatabase.FindAssets ("t:" + scriptType); string[] guids = AssetDatabase.FindAssets ("t:" + scriptType);
@ -25,7 +25,7 @@ namespace XNodeEditor {
string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]); string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]);
Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath); Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath);
for (int k = 0; k < objs.Length; k++) { for (int k = 0; k < objs.Length; k++) {
XNode.Node node = objs[k] as XNode.Node; XNode.INode node = objs[k] as XNode.INode;
if (node.GetType () == scriptType) { if (node.GetType () == scriptType) {
if (node != null && node.graph != null) { if (node != null && node.graph != null) {
// Delete the node and notify the user // Delete the node and notify the user
@ -51,7 +51,7 @@ namespace XNodeEditor {
Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath); Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath);
// Ensure that all sub node assets are present in the graph node list // Ensure that all sub node assets are present in the graph node list
for (int u = 0; u < objs.Length; u++) { for (int u = 0; u < objs.Length; u++) {
if (!graph.nodes.Contains (objs[u] as XNode.Node)) graph.nodes.Add(objs[u] as XNode.Node); if (!graph.nodes.Contains (objs[u] as XNode.INode)) graph.nodes.Add(objs[u] as XNode.INode);
} }
} }
} }

View File

@ -9,8 +9,8 @@ namespace XNodeEditor.Internal {
/// <summary> Handles caching of custom editor classes and their target types. Accessible with GetEditor(Type type) </summary> /// <summary> Handles caching of custom editor classes and their target types. Accessible with GetEditor(Type type) </summary>
/// <typeparam name="T">Editor Type. Should be the type of the deriving script itself (eg. NodeEditor) </typeparam> /// <typeparam name="T">Editor Type. Should be the type of the deriving script itself (eg. NodeEditor) </typeparam>
/// <typeparam name="A">Attribute Type. The attribute used to connect with the runtime type (eg. CustomNodeEditorAttribute) </typeparam> /// <typeparam name="A">Attribute Type. The attribute used to connect with the runtime type (eg. CustomNodeEditorAttribute) </typeparam>
/// <typeparam name="K">Runtime Type. The ScriptableObject this can be an editor for (eg. Node) </typeparam> /// <typeparam name="K">Runtime Type. The Object this can be an editor for (eg. Node) </typeparam>
public abstract class NodeEditorBase<T, A, K> : Editor where A : Attribute, NodeEditorBase<T, A, K>.INodeEditorAttrib where T : NodeEditorBase<T, A, K> where K : ScriptableObject { public abstract class NodeEditorBase<T, A, K> : Editor where A : Attribute, NodeEditorBase<T, A, K>.INodeEditorAttrib where T : NodeEditorBase<T, A, K> where K : UnityEngine.Object {
/// <summary> Custom editors defined with [CustomNodeEditor] </summary> /// <summary> Custom editors defined with [CustomNodeEditor] </summary>
private static Dictionary<Type, Type> editorTypes; private static Dictionary<Type, Type> editorTypes;
private static Dictionary<K, T> editors = new Dictionary<K, T>(); private static Dictionary<K, T> editors = new Dictionary<K, T>();

View File

@ -303,8 +303,8 @@ namespace XNodeEditor {
NodeEditor.portPositions = new Dictionary<XNode.NodePort, Vector2>(); NodeEditor.portPositions = new Dictionary<XNode.NodePort, Vector2>();
//Get node position //Get node.Position
Vector2 nodePos = GridToWindowPositionNoClipped(node.position); Vector2 nodePos = GridToWindowPositionNoClipped(node.Position);
GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000)));
@ -349,7 +349,7 @@ namespace XNodeEditor {
foreach (var kvp in NodeEditor.portPositions) { foreach (var kvp in NodeEditor.portPositions) {
Vector2 portHandlePos = kvp.Value; Vector2 portHandlePos = kvp.Value;
portHandlePos += node.position; portHandlePos += node.Position;
Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16); Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16);
if (portConnectionPoints.ContainsKey(kvp.Key)) portConnectionPoints[kvp.Key] = rect; if (portConnectionPoints.ContainsKey(kvp.Key)) portConnectionPoints[kvp.Key] = rect;
else portConnectionPoints.Add(kvp.Key, rect); else portConnectionPoints.Add(kvp.Key, rect);
@ -400,7 +400,7 @@ namespace XNodeEditor {
private bool ShouldBeCulled(XNode.Node node) { private bool ShouldBeCulled(XNode.Node node) {
Vector2 nodePos = GridToWindowPositionNoClipped(node.position); Vector2 nodePos = GridToWindowPositionNoClipped(node.Position);
if (nodePos.x / _zoom > position.width) return true; // Right if (nodePos.x / _zoom > position.width) return true; // Right
else if (nodePos.y / _zoom > position.height) return true; // Bottom else if (nodePos.y / _zoom > position.height) return true; // Bottom
else if (nodeSizes.ContainsKey(node)) { else if (nodeSizes.ContainsKey(node)) {

View File

@ -22,7 +22,7 @@ namespace XNodeEditor {
/// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary> /// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary>
public static void PropertyField(SerializedProperty property, GUIContent label, bool includeChildren = true, params GUILayoutOption[] options) { public static void PropertyField(SerializedProperty property, GUIContent label, bool includeChildren = true, params GUILayoutOption[] options) {
if (property == null) throw new NullReferenceException(); if (property == null) throw new NullReferenceException();
XNode.Node node = property.serializedObject.targetObject as XNode.Node; XNode.INode node = property.serializedObject.targetObject as XNode.INode;
XNode.NodePort port = node.GetPort(property.name); XNode.NodePort port = node.GetPort(property.name);
PropertyField(property, label, port, includeChildren); PropertyField(property, label, port, includeChildren);
} }
@ -285,7 +285,7 @@ namespace XNodeEditor {
/// <param name="connectionType">Connection type of added dynamic ports</param> /// <param name="connectionType">Connection type of added dynamic ports</param>
/// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param> /// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param>
public static void DynamicPortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) { public static void DynamicPortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) {
XNode.Node node = serializedObject.targetObject as XNode.Node; XNode.INode node = serializedObject.targetObject as XNode.INode;
var indexedPorts = node.DynamicPorts.Select(x => { var indexedPorts = node.DynamicPorts.Select(x => {
string[] split = x.fieldName.Split(' '); string[] split = x.fieldName.Split(' ');
@ -315,9 +315,9 @@ namespace XNodeEditor {
list.DoLayoutList(); list.DoLayoutList();
} }
private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action<ReorderableList> onCreation) { private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.INode.ConnectionType connectionType, XNode.INode.TypeConstraint typeConstraint, Action<ReorderableList> onCreation) {
bool hasArrayData = arrayData != null && arrayData.isArray; bool hasArrayData = arrayData != null && arrayData.isArray;
XNode.Node node = serializedObject.targetObject as XNode.Node; XNode.INode node = serializedObject.targetObject as XNode.INode;
ReorderableList list = new ReorderableList(dynamicPorts, null, true, true, true, true); ReorderableList list = new ReorderableList(dynamicPorts, null, true, true, true, true);
string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName); string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName);
@ -404,7 +404,7 @@ namespace XNodeEditor {
int i = 0; int i = 0;
while (node.HasPort(newName)) newName = fieldName + " " + (++i); while (node.HasPort(newName)) newName = fieldName + " " + (++i);
if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName); if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, XNode.INode.TypeConstraint.None, newName);
else node.AddDynamicInput(type, connectionType, typeConstraint, newName); else node.AddDynamicInput(type, connectionType, typeConstraint, newName);
serializedObject.Update(); serializedObject.Update();
EditorUtility.SetDirty(node); EditorUtility.SetDirty(node);

View File

@ -47,7 +47,7 @@ namespace XNodeEditor {
UnityEngine.Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetpath); UnityEngine.Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetpath);
// Loop through graph asset and search for nodes (nodes exist inside the graph asset as sub-assets) // Loop through graph asset and search for nodes (nodes exist inside the graph asset as sub-assets)
for (int k = 0; k < objs.Length; k++) { for (int k = 0; k < objs.Length; k++) {
XNode.Node node = objs[k] as XNode.Node; XNode.INode node = objs[k] as XNode.INode;
if (node != null) node.UpdateStaticPorts(); if (node != null) node.UpdateStaticPorts();
} }
} }
@ -55,15 +55,15 @@ namespace XNodeEditor {
public static Type[] GetNodeTypes() { public static Type[] GetNodeTypes() {
//Get all classes deriving from Node via reflection //Get all classes deriving from Node via reflection
return GetDerivedTypes(typeof(XNode.Node)); return GetDerivedTypes(typeof(XNode.INode));
} }
public static Dictionary<Type, Color> GetNodeTint() { public static Dictionary<Type, Color> GetNodeTint() {
Dictionary<Type, Color> tints = new Dictionary<Type, Color>(); Dictionary<Type, Color> tints = new Dictionary<Type, Color>();
for (int i = 0; i < nodeTypes.Length; i++) { for (int i = 0; i < nodeTypes.Length; i++) {
var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeTintAttribute), true); var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.INode.NodeTintAttribute), true);
if (attribs == null || attribs.Length == 0) continue; if (attribs == null || attribs.Length == 0) continue;
XNode.Node.NodeTintAttribute attrib = attribs[0] as XNode.Node.NodeTintAttribute; XNode.INode.NodeTintAttribute attrib = attribs[0] as XNode.INode.NodeTintAttribute;
tints.Add(nodeTypes[i], attrib.color); tints.Add(nodeTypes[i], attrib.color);
} }
return tints; return tints;
@ -72,9 +72,9 @@ namespace XNodeEditor {
public static Dictionary<Type, int> GetNodeWidth() { public static Dictionary<Type, int> GetNodeWidth() {
Dictionary<Type, int> widths = new Dictionary<Type, int>(); Dictionary<Type, int> widths = new Dictionary<Type, int>();
for (int i = 0; i < nodeTypes.Length; i++) { for (int i = 0; i < nodeTypes.Length; i++) {
var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeWidthAttribute), true); var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.INode.NodeWidthAttribute), true);
if (attribs == null || attribs.Length == 0) continue; if (attribs == null || attribs.Length == 0) continue;
XNode.Node.NodeWidthAttribute attrib = attribs[0] as XNode.Node.NodeWidthAttribute; XNode.INode.NodeWidthAttribute attrib = attribs[0] as XNode.INode.NodeWidthAttribute;
widths.Add(nodeTypes[i], attrib.width); widths.Add(nodeTypes[i], attrib.width);
} }
return widths; return widths;
@ -85,7 +85,7 @@ namespace XNodeEditor {
// If we can't find field in the first run, it's probably a private field in a base class. // If we can't find field in the first run, it's probably a private field in a base class.
FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
// Search base classes for private fields only. Public fields are found above // Search base classes for private fields only. Public fields are found above
while (field == null && (type = type.BaseType) != typeof(XNode.Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); while (field == null && (type = type.BaseType) != typeof(XNode.INode)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
return field; return field;
} }

View File

@ -8,7 +8,7 @@ namespace XNodeEditor {
private static Texture2D _dot; private static Texture2D _dot;
public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = Resources.Load<Texture2D>("xnode_dot_outer"); } } public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = Resources.Load<Texture2D>("xnode_dot_outer"); } }
private static Texture2D _dotOuter; private static Texture2D _dotOuter;
public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load<Texture2D>("xnode_node"); } } public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load<Texture2D>("XNode.INode"); } }
private static Texture2D _nodeBody; private static Texture2D _nodeBody;
public static Texture2D nodeHighlight { get { return _nodeHighlight != null ? _nodeHighlight : _nodeHighlight = Resources.Load<Texture2D>("xnode_node_highlight"); } } public static Texture2D nodeHighlight { get { return _nodeHighlight != null ? _nodeHighlight : _nodeHighlight = Resources.Load<Texture2D>("xnode_node_highlight"); } }
private static Texture2D _nodeHighlight; private static Texture2D _nodeHighlight;

View File

@ -134,7 +134,7 @@ namespace XNodeEditor {
} }
/// <summary>Creates a new C# Class.</summary> /// <summary>Creates a new C# Class.</summary>
[MenuItem("Assets/Create/xNode/Node C# Script", false, 89)] [MenuItem("Assets/Create/XNode.INode C# Script", false, 89)]
private static void CreateNode() { private static void CreateNode() {
string[] guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs"); string[] guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs");
if (guids.Length == 0) { if (guids.Length == 0) {

View File

@ -8,7 +8,7 @@ namespace XNodeEditor {
public partial class NodeEditorWindow : EditorWindow { public partial class NodeEditorWindow : EditorWindow {
public static NodeEditorWindow current; public static NodeEditorWindow current;
/// <summary> Stores node positions for all nodePorts. </summary> /// <summary> Stores node.Positions for all nodePorts. </summary>
public Dictionary<XNode.NodePort, Rect> portConnectionPoints { get { return _portConnectionPoints; } } public Dictionary<XNode.NodePort, Rect> portConnectionPoints { get { return _portConnectionPoints; } }
private Dictionary<XNode.NodePort, Rect> _portConnectionPoints = new Dictionary<XNode.NodePort, Rect>(); private Dictionary<XNode.NodePort, Rect> _portConnectionPoints = new Dictionary<XNode.NodePort, Rect>();
[SerializeField] private NodePortReference[] _references = new NodePortReference[0]; [SerializeField] private NodePortReference[] _references = new NodePortReference[0];
@ -56,9 +56,9 @@ namespace XNodeEditor {
} }
} }
public Dictionary<XNode.Node, Vector2> nodeSizes { get { return _nodeSizes; } } public Dictionary<XNode.INode, Vector2> nodeSizes { get { return _nodeSizes; } }
private Dictionary<XNode.Node, Vector2> _nodeSizes = new Dictionary<XNode.Node, Vector2>(); private Dictionary<XNode.INode, Vector2> _nodeSizes = new Dictionary<XNode.INode, Vector2>();
public XNode.NodeGraph graph; public XNode.INodeGraph graph;
public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } } public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } }
private Vector2 _panOffset; private Vector2 _panOffset;
public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, 1f, NodeEditorPreferences.GetSettings().zoomOutLimit); Repaint(); } } public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, 1f, NodeEditorPreferences.GetSettings().zoomOutLimit); Repaint(); } }
@ -78,7 +78,7 @@ namespace XNodeEditor {
/// <summary> Handle Selection Change events</summary> /// <summary> Handle Selection Change events</summary>
private static void OnSelectionChanged() { private static void OnSelectionChanged() {
XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph; XNode.INodeGraph nodeGraph = Selection.activeObject as XNode.INodeGraph;
if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) { if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) {
Open(nodeGraph); Open(nodeGraph);
} }
@ -113,7 +113,7 @@ namespace XNodeEditor {
string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", ""); string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", "");
if (string.IsNullOrEmpty(path)) return; if (string.IsNullOrEmpty(path)) return;
else { else {
XNode.NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath<XNode.NodeGraph>(path); XNode.INodeGraph existingGraph = AssetDatabase.LoadAssetAtPath<XNode.INodeGraph>(path);
if (existingGraph != null) AssetDatabase.DeleteAsset(path); if (existingGraph != null) AssetDatabase.DeleteAsset(path);
AssetDatabase.CreateAsset(graph, path); AssetDatabase.CreateAsset(graph, path);
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
@ -152,7 +152,7 @@ namespace XNodeEditor {
return new Vector2(xOffset, yOffset); return new Vector2(xOffset, yOffset);
} }
public void SelectNode(XNode.Node node, bool add) { public void SelectNode(XNode.INode node, bool add) {
if (add) { if (add) {
List<Object> selection = new List<Object>(Selection.objects); List<Object> selection = new List<Object>(Selection.objects);
selection.Add(node); selection.Add(node);
@ -160,7 +160,7 @@ namespace XNodeEditor {
} else Selection.objects = new Object[] { node }; } else Selection.objects = new Object[] { node };
} }
public void DeselectNode(XNode.Node node) { public void DeselectNode(XNode.INode node) {
List<Object> selection = new List<Object>(Selection.objects); List<Object> selection = new List<Object>(Selection.objects);
selection.Remove(node); selection.Remove(node);
Selection.objects = selection.ToArray(); Selection.objects = selection.ToArray();
@ -168,7 +168,7 @@ namespace XNodeEditor {
[OnOpenAsset(0)] [OnOpenAsset(0)]
public static bool OnOpen(int instanceID, int line) { public static bool OnOpen(int instanceID, int line) {
XNode.NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.NodeGraph; XNode.INodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.INodeGraph;
if (nodeGraph != null) { if (nodeGraph != null) {
Open(nodeGraph); Open(nodeGraph);
return true; return true;
@ -177,7 +177,7 @@ namespace XNodeEditor {
} }
/// <summary>Open the provided graph in the NodeEditor</summary> /// <summary>Open the provided graph in the NodeEditor</summary>
public static void Open(XNode.NodeGraph graph) { public static void Open(XNode.INodeGraph graph) {
if (!graph) return; if (!graph) return;
NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow; NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow;

View File

@ -34,7 +34,7 @@ namespace XNodeEditor {
/// <summary> Returns context node menu path. Null or empty strings for hidden nodes. </summary> /// <summary> Returns context node menu path. Null or empty strings for hidden nodes. </summary>
public virtual string GetNodeMenuName(Type type) { public virtual string GetNodeMenuName(Type type) {
//Check if type has the CreateNodeMenuAttribute //Check if type has the CreateNodeMenuAttribute
XNode.Node.CreateNodeMenuAttribute attrib; XNode.INode.CreateNodeMenuAttribute attrib;
if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path
return attrib.menuName; return attrib.menuName;
else // Return generated path else // Return generated path
@ -70,8 +70,8 @@ namespace XNodeEditor {
/// <summary> Create a node and save it in the graph asset </summary> /// <summary> Create a node and save it in the graph asset </summary>
public virtual void CreateNode(Type type, Vector2 position) { public virtual void CreateNode(Type type, Vector2 position) {
XNode.Node node = target.AddNode(type); XNode.INode node = target.AddNode(type);
node.position = position; node.Position = position;
if (string.IsNullOrEmpty(node.name)) node.name = UnityEditor.ObjectNames.NicifyVariableName(type.Name); if (string.IsNullOrEmpty(node.name)) node.name = UnityEditor.ObjectNames.NicifyVariableName(type.Name);
AssetDatabase.AddObjectToAsset(node, target); AssetDatabase.AddObjectToAsset(node, target);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
@ -79,8 +79,8 @@ namespace XNodeEditor {
} }
/// <summary> Creates a copy of the original node in the graph </summary> /// <summary> Creates a copy of the original node in the graph </summary>
public XNode.Node CopyNode(XNode.Node original) { public XNode.INode CopyNode(XNode.INode original) {
XNode.Node node = target.CopyNode(original); XNode.INode node = target.CopyNode(original);
node.name = original.name; node.name = original.name;
AssetDatabase.AddObjectToAsset(node, target); AssetDatabase.AddObjectToAsset(node, target);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
@ -88,7 +88,7 @@ namespace XNodeEditor {
} }
/// <summary> Safely remove a node and all its connections. </summary> /// <summary> Safely remove a node and all its connections. </summary>
public void RemoveNode(XNode.Node node) { public void RemoveNode(XNode.INode node) {
UnityEngine.Object.DestroyImmediate(node, true); UnityEngine.Object.DestroyImmediate(node, true);
target.RemoveNode(node); target.RemoveNode(node);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();

View File

@ -5,7 +5,12 @@ using XNode;
public class #SCRIPTNAME# : Node { public class #SCRIPTNAME# : Node {
// Use OnEnable for initialization // Reset is called on creation
private void Reset() {
#NOTRIM#
}
// OnEnable is called on load
private void OnEnable() { private void OnEnable() {
#NOTRIM# #NOTRIM#
} }

View File

@ -4,7 +4,7 @@ using UnityEngine;
namespace XNode { namespace XNode {
public interface INode { public interface INode {
string name { get; set; } string Name { get; set; }
INodeGraph Graph { get; } INodeGraph Graph { get; }
Vector2 Position { get; set; } Vector2 Position { get; set; }
object GetValue(NodePort port); object GetValue(NodePort port);
@ -15,8 +15,8 @@ namespace XNode {
IEnumerable<NodePort> Outputs { get; } IEnumerable<NodePort> Outputs { get; }
IEnumerable<NodePort> Inputs { get; } IEnumerable<NodePort> Inputs { get; }
IEnumerable<NodePort> InstancePorts { get; } IEnumerable<NodePort> InstancePorts { get; }
NodePort AddDynamicOutput(Type type, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = Node.TypeConstraint.None, string fieldName = null); NodePort AddDynamicPort(Type type, NodePort.IO direction, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = Node.TypeConstraint.None, string fieldName = null);
NodePort AddDynamicInput(Type type, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = Node.TypeConstraint.None, string fieldName = null); NodePort RemoveDynamicPort(string fieldName);
NodePort GetInputPort(string fieldName); NodePort GetInputPort(string fieldName);
NodePort GetOutputPort(string fieldName); NodePort GetOutputPort(string fieldName);
void OnCreateConnection(NodePort from, NodePort to); void OnCreateConnection(NodePort from, NodePort to);

View File

@ -1,11 +1,11 @@
using System; using System;
using System.Collections.Generic;
namespace XNode { namespace XNode {
/// <summary> Used by advanced extensions that need to alter the base classes of NodeGraphs </summary> /// <summary> Used by advanced extensions that need to alter the base classes of NodeGraphs </summary>
public interface INodeGraph { public interface INodeGraph {
int NodesCount { get; }
void MoveNodeToTop(INode node); void MoveNodeToTop(INode node);
INode[] GetNodes(); IEnumerable<INode> Nodes { get; }
INode AddNode(Type type); INode AddNode(Type type);
INode CopyNode(INode original); INode CopyNode(INode original);
void RemoveNode(INode node); void RemoveNode(INode node);

View File

@ -92,6 +92,12 @@ namespace XNode {
} }
#endregion #endregion
#region Interface implementation
string INode.Name { get { return name; } set { name = value; } }
INodeGraph INode.Graph { get { return graph; } }
Vector2 INode.Position { get { return position; } set { position = value; } }
#endregion
/// <summary> Iterate over all ports on this node. </summary> /// <summary> Iterate over all ports on this node. </summary>
public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } } public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
/// <summary> Iterate over all outputs on this node. </summary> /// <summary> Iterate over all outputs on this node. </summary>
@ -105,9 +111,7 @@ namespace XNode {
/// <summary> Iterate over all dynamic inputs on this node. </summary> /// <summary> Iterate over all dynamic inputs on this node. </summary>
public IEnumerable<NodePort> DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } } public IEnumerable<NodePort> DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } }
/// <summary> Parent <see cref="NodeGraph"/> </summary> /// <summary> Parent <see cref="NodeGraph"/> </summary>
public INodeGraph Graph { get { return graph; } } [SerializeField] public NodeGraph graph;
/// <summary> Parent <see cref="NodeGraph"/> </summary>
[SerializeField] private NodeGraph graph;
/// <summary> Position on the <see cref="NodeGraph"/> </summary> /// <summary> Position on the <see cref="NodeGraph"/> </summary>
[SerializeField] public Vector2 position; [SerializeField] public Vector2 position;
/// <summary> It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> </summary> /// <summary> It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> </summary>
@ -136,20 +140,20 @@ namespace XNode {
/// <seealso cref="AddInstancePort"/> /// <seealso cref="AddInstancePort"/>
/// <seealso cref="AddInstanceOutput"/> /// <seealso cref="AddInstanceOutput"/>
public NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { public NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName); return ((INode)this).AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName);
} }
/// <summary> Convenience function. </summary> /// <summary> Convenience function. </summary>
/// <seealso cref="AddInstancePort"/> /// <seealso cref="AddInstancePort"/>
/// <seealso cref="AddInstanceInput"/> /// <seealso cref="AddInstanceInput"/>
public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName); return ((INode)this).AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName);
} }
/// <summary> Add a dynamic, serialized port to this node. </summary> /// <summary> Add a dynamic, serialized port to this node. </summary>
/// <seealso cref="AddDynamicInput"/> /// <seealso cref="AddDynamicInput"/>
/// <seealso cref="AddDynamicOutput"/> /// <seealso cref="AddDynamicOutput"/>
private NodePort AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { NodePort INode.AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
if (fieldName == null) { if (fieldName == null) {
fieldName = "dynamicInput_0"; fieldName = "dynamicInput_0";
int i = 0; int i = 0;

View File

@ -11,11 +11,28 @@ namespace XNode {
/// See: <see cref="AddNode{T}"/> </summary> /// See: <see cref="AddNode{T}"/> </summary>
[SerializeField] public List<Node> nodes = new List<Node>(); [SerializeField] public List<Node> nodes = new List<Node>();
#region Interface implementation
IEnumerable<INode> INodeGraph.Nodes { get { foreach (Node node in nodes) yield return node; } }
INode INodeGraph.AddNode(Type type) { return AddNode(type); }
void INodeGraph.MoveNodeToTop(INode node) { MoveNodeToTop(node as Node); }
INode INodeGraph.CopyNode(INode original) { return CopyNode(original as Node); }
void INodeGraph.RemoveNode(INode node) { RemoveNode(node as Node); }
#endregion
/// <summary> Add a node to the graph by type (convenience method - will call the System.Type version) </summary> /// <summary> Add a node to the graph by type (convenience method - will call the System.Type version) </summary>
public T AddNode<T>() where T : Node { public T AddNode<T>() where T : Node {
return AddNode(typeof(T)) as T; return AddNode(typeof(T)) as T;
} }
/// <summary> Draw this node on top of other nodes by placing it last in the graph.nodes list </summary>
public void MoveNodeToTop(XNode.Node node) {
int index;
while ((index = nodes.IndexOf(node as Node)) != nodes.Count - 1) {
nodes[index] = nodes[index + 1];
nodes[index + 1] = node as Node;
}
}
/// <summary> Add a node to the graph by type </summary> /// <summary> Add a node to the graph by type </summary>
public virtual Node AddNode(Type type) { public virtual Node AddNode(Type type) {
Node.graphHotfix = this; Node.graphHotfix = this;