diff --git a/README.md b/README.md
index e9db741..e0544a7 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,10 @@ With a minimal footprint, it is ideal as a base for custom state machines, dialo
* Does not rely on any 3rd party plugins
* Custom node inspector code is very similar to regular custom inspector code
+### Wiki
+* [Getting started](https://github.com/Siccity/xNode/wiki/Getting%20Started) - create your very first node node and graph
+* [Examples branch](https://github.com/Siccity/xNode/tree/examples) - look at other small projects
+
### Node example:
```csharp
[System.Serializable]
diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs
index 39febe9..8a64663 100644
--- a/Scripts/Editor/NodeEditor.cs
+++ b/Scripts/Editor/NodeEditor.cs
@@ -13,6 +13,7 @@ namespace XNodeEditor {
/// Fires every whenever a node was modified through the editor
public static Action onUpdateNode;
public static Dictionary portPositions;
+ public static int renaming;
/// Draws the node GUI.
/// Port handle positions need to be returned to the NodeEditorWindow
@@ -24,7 +25,21 @@ namespace XNodeEditor {
public virtual void OnHeaderGUI() {
GUI.color = Color.white;
string title = target.name;
- GUILayout.Label(title, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30));
+ if (renaming != 0 && Selection.Contains(target)) {
+ int controlID = EditorGUIUtility.GetControlID(FocusType.Keyboard) + 1;
+ if (renaming == 1) {
+ EditorGUIUtility.keyboardControl = controlID;
+ EditorGUIUtility.editingTextField = true;
+ renaming = 2;
+ }
+ target.name = EditorGUILayout.TextField(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30));
+ if (!EditorGUIUtility.editingTextField) {
+ Rename(target.name);
+ renaming = 0;
+ }
+ } else {
+ GUILayout.Label(title, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30));
+ }
}
/// Draws standard field editors for all public fields
@@ -52,6 +67,15 @@ namespace XNodeEditor {
else return Color.white;
}
+ public void InitiateRename() {
+ renaming = 1;
+ }
+
+ public void Rename(string newName) {
+ target.name = newName;
+ AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
+ }
+
[AttributeUsage(AttributeTargets.Class)]
public class CustomNodeEditorAttribute : Attribute,
XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib {
diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs
index 7b43574..651824a 100644
--- a/Scripts/Editor/NodeEditorAction.cs
+++ b/Scripts/Editor/NodeEditorAction.cs
@@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using UnityEditor;
using UnityEngine;
namespace XNodeEditor {
public partial class NodeEditorWindow {
- public enum NodeActivity { Idle, HoldHeader, DragHeader, HoldGrid, DragGrid }
+ public enum NodeActivity { Idle, HoldNode, DragNode, HoldGrid, DragGrid }
public static NodeActivity currentActivity = NodeActivity.Idle;
public static bool isPanning { get; private set; }
public static Vector2[] dragOffset;
@@ -13,13 +14,36 @@ namespace XNodeEditor {
private bool IsDraggingPort { get { return draggedOutput != null; } }
private bool IsHoveringPort { get { return hoveredPort != null; } }
private bool IsHoveringNode { get { return hoveredNode != null; } }
+ private bool IsHoveringReroute { get { return hoveredReroute.port != null; } }
private XNode.Node hoveredNode = null;
[NonSerialized] private XNode.NodePort hoveredPort = null;
[NonSerialized] private XNode.NodePort draggedOutput = null;
[NonSerialized] private XNode.NodePort draggedOutputTarget = null;
+ [NonSerialized] private List draggedOutputReroutes = new List();
+ private RerouteReference hoveredReroute = new RerouteReference();
+ private List selectedReroutes = new List();
private Rect nodeRects;
private Vector2 dragBoxStart;
private UnityEngine.Object[] preBoxSelection;
+ private RerouteReference[] preBoxSelectionReroute;
+ private Rect selectionBox;
+
+ private struct RerouteReference {
+ public XNode.NodePort port;
+ public int connectionIndex;
+ public int pointIndex;
+
+ public RerouteReference(XNode.NodePort port, int connectionIndex, int pointIndex) {
+ this.port = port;
+ this.connectionIndex = connectionIndex;
+ this.pointIndex = pointIndex;
+ }
+
+ public void InsertPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex).Insert(pointIndex, pos); }
+ public void SetPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex) [pointIndex] = pos; }
+ public void RemovePoint() { port.GetReroutePoints(connectionIndex).RemoveAt(pointIndex); }
+ public Vector2 GetPoint() { return port.GetReroutePoints(connectionIndex) [pointIndex]; }
+ }
public void Controls() {
wantsMouseMove = true;
@@ -42,32 +66,52 @@ namespace XNodeEditor {
draggedOutputTarget = null;
}
Repaint();
- } else if (currentActivity == NodeActivity.HoldHeader || currentActivity == NodeActivity.DragHeader) {
+ } else if (currentActivity == NodeActivity.HoldNode) {
+ RecalculateDragOffsets(e);
+ currentActivity = NodeActivity.DragNode;
+ Repaint();
+ }
+ if (currentActivity == NodeActivity.DragNode) {
+ // Holding ctrl inverts grid snap
+ bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap;
+ if (e.control) gridSnap = !gridSnap;
+
+ Vector2 mousePos = WindowToGridPosition(e.mousePosition);
+ // Move selected nodes with offset
for (int i = 0; i < Selection.objects.Length; i++) {
if (Selection.objects[i] is XNode.Node) {
XNode.Node node = Selection.objects[i] as XNode.Node;
- node.position = WindowToGridPosition(e.mousePosition) + dragOffset[i];
- bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap;
- if (e.control) {
- gridSnap = !gridSnap;
- }
+ node.position = mousePos + dragOffset[i];
if (gridSnap) {
node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8;
node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8;
}
}
}
- currentActivity = NodeActivity.DragHeader;
+ // Move selected reroutes with offset
+ for (int i = 0; i < selectedReroutes.Count; i++) {
+ Vector2 pos = mousePos + dragOffset[Selection.objects.Length + i];
+ pos.x -= 8;
+ pos.y -= 8;
+ if (gridSnap) {
+ pos.x = (Mathf.Round((pos.x + 8) / 16) * 16);
+ pos.y = (Mathf.Round((pos.y + 8) / 16) * 16);
+ }
+ selectedReroutes[i].SetPoint(pos);
+ }
Repaint();
} else if (currentActivity == NodeActivity.HoldGrid) {
currentActivity = NodeActivity.DragGrid;
preBoxSelection = Selection.objects;
+ preBoxSelectionReroute = selectedReroutes.ToArray();
dragBoxStart = WindowToGridPosition(e.mousePosition);
Repaint();
} else if (currentActivity == NodeActivity.DragGrid) {
- foreach (XNode.Node node in graph.nodes) {
-
- }
+ Vector2 boxStartPos = GridToWindowPosition(dragBoxStart);
+ Vector2 boxSize = e.mousePosition - boxStartPos;
+ if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); }
+ if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); }
+ selectionBox = new Rect(boxStartPos, boxSize);
Repaint();
}
} else if (e.button == 1 || e.button == 2) {
@@ -83,6 +127,7 @@ namespace XNodeEditor {
case EventType.MouseDown:
Repaint();
if (e.button == 0) {
+ draggedOutputReroutes.Clear();
if (IsHoveringPort) {
if (hoveredPort.IsOutput) {
@@ -92,6 +137,8 @@ namespace XNodeEditor {
if (hoveredPort.IsConnected) {
XNode.Node node = hoveredPort.node;
XNode.NodePort output = hoveredPort.Connection;
+ int outputConnectionIndex = output.GetConnectionIndex(hoveredPort);
+ draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex);
hoveredPort.Disconnect(output);
draggedOutput = output;
draggedOutputTarget = hoveredPort;
@@ -100,22 +147,36 @@ namespace XNodeEditor {
}
} else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
// If mousedown on node header, select or deselect
- if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, e.control || e.shift);
- else if (e.control || e.shift) DeselectNode(hoveredNode);
+ if (!Selection.Contains(hoveredNode)) {
+ SelectNode(hoveredNode, e.control || e.shift);
+ if (!e.control && !e.shift) selectedReroutes.Clear();
+ } else if (e.control || e.shift) DeselectNode(hoveredNode);
e.Use();
- currentActivity = NodeActivity.HoldHeader;
- dragOffset = new Vector2[Selection.objects.Length];
- for (int i = 0; i < dragOffset.Length; i++) {
- if (Selection.objects[i] is XNode.Node) {
- XNode.Node node = Selection.objects[i] as XNode.Node;
- dragOffset[i] = node.position - WindowToGridPosition(e.mousePosition);
+ currentActivity = NodeActivity.HoldNode;
+ } else if (IsHoveringReroute) {
+ // If reroute isn't selected
+ if (!selectedReroutes.Contains(hoveredReroute)) {
+ // Add it
+ if (e.control || e.shift) selectedReroutes.Add(hoveredReroute);
+ // Select it
+ else {
+ selectedReroutes = new List() { hoveredReroute };
+ Selection.activeObject = null;
}
+
}
+ // Deselect
+ else if (e.control || e.shift) selectedReroutes.Remove(hoveredReroute);
+ e.Use();
+ currentActivity = NodeActivity.HoldNode;
}
// If mousedown on grid background, deselect all
else if (!IsHoveringNode) {
currentActivity = NodeActivity.HoldGrid;
- if (!e.control && !e.shift) Selection.activeObject = null;
+ if (!e.control && !e.shift) {
+ selectedReroutes.Clear();
+ Selection.activeObject = null;
+ }
}
}
break;
@@ -127,16 +188,22 @@ namespace XNodeEditor {
if (draggedOutputTarget != null) {
XNode.Node node = draggedOutputTarget.node;
if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget);
- if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
- EditorUtility.SetDirty(graph);
+
+ // ConnectionIndex can be -1 if the connection is removed instantly after creation
+ int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget);
+ if (connectionIndex != -1) {
+ draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes);
+ if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
+ EditorUtility.SetDirty(graph);
+ }
}
//Release dragged connection
draggedOutput = null;
draggedOutputTarget = null;
EditorUtility.SetDirty(graph);
- AssetDatabase.SaveAssets();
- } else if (currentActivity == NodeActivity.DragHeader) {
- AssetDatabase.SaveAssets();
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
+ } else if (currentActivity == NodeActivity.DragNode) {
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} else if (!IsHoveringNode) {
// If click outside node, release field focus
if (!isPanning) {
@@ -147,19 +214,35 @@ namespace XNodeEditor {
EditorGUIUtility.keyboardControl = 0;
EditorGUIUtility.hotControl = 0;
}
- AssetDatabase.SaveAssets();
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
}
- // If click node header, select single node.
- if (currentActivity == NodeActivity.HoldHeader && !(e.control || e.shift)) {
+ // If click node header, select it.
+ if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift)) {
+ selectedReroutes.Clear();
SelectNode(hoveredNode, false);
}
+ // If click reroute, select it.
+ if (IsHoveringReroute && !(e.control || e.shift)) {
+ selectedReroutes = new List() { hoveredReroute };
+ Selection.activeObject = null;
+ }
+
Repaint();
currentActivity = NodeActivity.Idle;
} else if (e.button == 1) {
if (!isPanning) {
- if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
+ if (IsDraggingPort) {
+ draggedOutputReroutes.Add(WindowToGridPosition(e.mousePosition));
+ } else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null && selectedReroutes.Count == 1) {
+ selectedReroutes[0].InsertPoint(selectedReroutes[0].GetPoint());
+ selectedReroutes[0] = new RerouteReference(selectedReroutes[0].port, selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1);
+ } else if (IsHoveringReroute) {
+ ShowRerouteContextMenu(hoveredReroute);
+ } else if (IsHoveringPort) {
+ ShowPortContextMenu(hoveredPort);
+ } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false);
ShowNodeContextMenu();
} else if (!IsHoveringNode) {
@@ -188,6 +271,22 @@ namespace XNodeEditor {
}
}
+ private void RecalculateDragOffsets(Event current) {
+ dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count];
+ // Selected nodes
+ for (int i = 0; i < Selection.objects.Length; i++) {
+ if (Selection.objects[i] is XNode.Node) {
+ XNode.Node node = Selection.objects[i] as XNode.Node;
+ dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition);
+ }
+ }
+
+ // Selected reroutes
+ for (int i = 0; i < selectedReroutes.Count; i++) {
+ dragOffset[Selection.objects.Length + i] = selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition);
+ }
+ }
+
/// Puts all nodes in focus. If no nodes are present, resets view to
public void Home() {
zoom = 2;
@@ -197,19 +296,37 @@ namespace XNodeEditor {
public void CreateNode(Type type, Vector2 position) {
XNode.Node node = graph.AddNode(type);
node.position = position;
+ node.name = UnityEditor.ObjectNames.NicifyVariableName(type.ToString());
+ AssetDatabase.AddObjectToAsset(node, graph);
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
Repaint();
}
/// Remove nodes in the graph in Selection.objects
public void RemoveSelectedNodes() {
+ // We need to delete reroutes starting at the highest point index to avoid shifting indices
+ selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList();
+ for (int i = 0; i < selectedReroutes.Count; i++) {
+ selectedReroutes[i].RemovePoint();
+ }
+ selectedReroutes.Clear();
foreach (UnityEngine.Object item in Selection.objects) {
if (item is XNode.Node) {
XNode.Node node = item as XNode.Node;
- graph.RemoveNode(node);
+ graphEditor.RemoveNode(node);
}
}
}
+ /// Draw this node on top of other nodes by placing it last in the graph.nodes list
+ public void MoveNodeToTop(XNode.Node node) {
+ int index;
+ while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) {
+ graph.nodes[index] = graph.nodes[index + 1];
+ graph.nodes[index + 1] = node;
+ }
+ }
+
/// Dublicate selected nodes and select the dublicates
public void DublicateSelectedNodes() {
UnityEngine.Object[] newNodes = new UnityEngine.Object[Selection.objects.Length];
@@ -218,7 +335,7 @@ namespace XNodeEditor {
if (Selection.objects[i] is XNode.Node) {
XNode.Node srcNode = Selection.objects[i] as XNode.Node;
if (srcNode.graph != graph) continue; // ignore nodes selected in another graph
- XNode.Node newNode = graph.CopyNode(srcNode);
+ XNode.Node newNode = graphEditor.CopyNode(srcNode);
substitutes.Add(srcNode, newNode);
newNode.position = srcNode.position + new Vector2(30, 30);
newNodes[i] = newNode;
@@ -254,12 +371,34 @@ namespace XNodeEditor {
/// Draw a connection as we are dragging it
public void DrawDraggedConnection() {
if (IsDraggingPort) {
- if (!_portConnectionPoints.ContainsKey(draggedOutput)) return;
- Vector2 from = _portConnectionPoints[draggedOutput].center;
- Vector2 to = draggedOutputTarget != null ? portConnectionPoints[draggedOutputTarget].center : WindowToGridPosition(Event.current.mousePosition);
Color col = NodeEditorPreferences.GetTypeColor(draggedOutput.ValueType);
+
+ if (!_portConnectionPoints.ContainsKey(draggedOutput)) return;
col.a = 0.6f;
+ Vector2 from = _portConnectionPoints[draggedOutput].center;
+ Vector2 to = Vector2.zero;
+ for (int i = 0; i < draggedOutputReroutes.Count; i++) {
+ to = draggedOutputReroutes[i];
+ DrawConnection(from, to, col);
+ from = to;
+ }
+ to = draggedOutputTarget != null ? portConnectionPoints[draggedOutputTarget].center : WindowToGridPosition(Event.current.mousePosition);
DrawConnection(from, to, col);
+
+ Color bgcol = Color.black;
+ Color frcol = col;
+ bgcol.a = 0.6f;
+ frcol.a = 0.6f;
+
+ // Loop through reroute points again and draw the points
+ for (int i = 0; i < draggedOutputReroutes.Count; i++) {
+ // Draw reroute point at position
+ Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16));
+ rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8);
+ rect = GridToWindowRect(rect);
+
+ NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol);
+ }
}
}
diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs b/Scripts/Editor/NodeEditorAssetModProcessor.cs
index 61a2518..bd76116 100644
--- a/Scripts/Editor/NodeEditorAssetModProcessor.cs
+++ b/Scripts/Editor/NodeEditorAssetModProcessor.cs
@@ -17,7 +17,7 @@ namespace XNodeEditor {
// Check script type. Return if deleting a non-node script
UnityEditor.MonoScript script = obj as UnityEditor.MonoScript;
System.Type scriptType = script.GetClass ();
- if (scriptType != typeof (XNode.Node) && !scriptType.IsSubclassOf (typeof (XNode.Node))) return AssetDeleteResult.DidNotDelete;
+ if (scriptType == null || (scriptType != typeof (XNode.Node) && !scriptType.IsSubclassOf (typeof (XNode.Node)))) return AssetDeleteResult.DidNotDelete;
// Find all ScriptableObjects using this script
string[] guids = AssetDatabase.FindAssets ("t:" + scriptType);
diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs
index 4db162b..34cd210 100644
--- a/Scripts/Editor/NodeEditorGUI.cs
+++ b/Scripts/Editor/NodeEditorGUI.cs
@@ -14,6 +14,7 @@ namespace XNodeEditor {
Matrix4x4 m = GUI.matrix;
if (graph == null) return;
graphEditor = NodeGraphEditor.GetEditor(graph);
+ graphEditor.position = position;
Controls();
@@ -21,8 +22,9 @@ namespace XNodeEditor {
DrawConnections();
DrawDraggedConnection();
DrawNodes();
- DrawBox();
+ DrawSelectionBox();
DrawTooltip();
+ graphEditor.OnGUI();
GUI.matrix = m;
}
@@ -72,7 +74,7 @@ namespace XNodeEditor {
GUI.DrawTextureWithTexCoords(rect, crossTex, new Rect(tileOffset + new Vector2(0.5f, 0.5f), tileAmount));
}
- public void DrawBox() {
+ public void DrawSelectionBox() {
if (currentActivity == NodeActivity.DragGrid) {
Vector2 curPos = WindowToGridPosition(Event.current.mousePosition);
Vector2 size = curPos - dragBoxStart;
@@ -87,19 +89,30 @@ namespace XNodeEditor {
return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width));
}
+ /// Show right-click context menu for hovered reroute
+ void ShowRerouteContextMenu(RerouteReference reroute) {
+ GenericMenu contextMenu = new GenericMenu();
+ contextMenu.AddItem(new GUIContent("Remove"), false, () => reroute.RemovePoint());
+ contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
+ }
+
+ /// Show right-click context menu for hovered port
+ void ShowPortContextMenu(XNode.NodePort hoveredPort) {
+ GenericMenu contextMenu = new GenericMenu();
+ contextMenu.AddItem(new GUIContent("Clear Connections"), false, () => hoveredPort.ClearConnections());
+ contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
+ }
+
/// Show right-click context menu for selected nodes
public void ShowNodeContextMenu() {
GenericMenu contextMenu = new GenericMenu();
// If only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node;
- contextMenu.AddItem(new GUIContent("Move To Top"), false, () => {
- int index;
- while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) {
- graph.nodes[index] = graph.nodes[index + 1];
- graph.nodes[index + 1] = node;
- }
- });
+ contextMenu.AddItem(new GUIContent("Move To Top"), false, () => MoveNodeToTop(node));
+ contextMenu.AddItem(new GUIContent("Rename"), false, NodeEditor.GetEditor(node).InitiateRename);
}
contextMenu.AddItem(new GUIContent("Duplicate"), false, DublicateSelectedNodes);
@@ -199,26 +212,67 @@ namespace XNodeEditor {
/// Draws all connections
public void DrawConnections() {
+ Vector2 mousePos = Event.current.mousePosition;
+ List selection = preBoxSelectionReroute != null ? new List(preBoxSelectionReroute) : new List();
+ hoveredReroute = new RerouteReference();
+
+ Color col = GUI.color;
foreach (XNode.Node node in graph.nodes) {
//If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset.
if (node == null) continue;
+ // Draw full connections and output > reroute
foreach (XNode.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++) {
+ Color connectionColor = graphEditor.GetTypeColor(output.ValueType);
+
+ for (int k = 0; k < output.ConnectionCount; k++) {
XNode.NodePort input = output.GetConnection(k);
+
+ // Error handling
if (input == null) continue; //If a script has been updated and the port doesn't exist, it is removed and null is returned. If this happens, return.
if (!input.IsConnectedTo(output)) input.Connect(output);
if (!_portConnectionPoints.ContainsKey(input)) continue;
- Vector2 to = _portConnectionPoints[input].center;
- Color connectionColor = graphEditor.GetTypeColor(output.ValueType);
+
+ Vector2 from = _portConnectionPoints[output].center;
+ Vector2 to = Vector2.zero;
+ List reroutePoints = output.GetReroutePoints(k);
+ // Loop through reroute points and draw the path
+ for (int i = 0; i < reroutePoints.Count; i++) {
+ to = reroutePoints[i];
+ DrawConnection(from, to, connectionColor);
+ from = to;
+ }
+ to = _portConnectionPoints[input].center;
DrawConnection(from, to, connectionColor);
+
+ // Loop through reroute points again and draw the points
+ for (int i = 0; i < reroutePoints.Count; i++) {
+ RerouteReference rerouteRef = new RerouteReference(output, k, i);
+ // Draw reroute point at position
+ Rect rect = new Rect(reroutePoints[i], new Vector2(12, 12));
+ rect.position = new Vector2(rect.position.x - 6, rect.position.y - 6);
+ rect = GridToWindowRect(rect);
+
+ // Draw selected reroute points with an outline
+ if (selectedReroutes.Contains(rerouteRef)) {
+ GUI.color = NodeEditorPreferences.GetSettings().highlightColor;
+ GUI.DrawTexture(rect, NodeEditorResources.dotOuter);
+ }
+
+ GUI.color = connectionColor;
+ GUI.DrawTexture(rect, NodeEditorResources.dot);
+ if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef);
+ if (rect.Contains(mousePos)) hoveredReroute = rerouteRef;
+
+ }
}
}
}
+ GUI.color = col;
+ if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = selection;
}
private void DrawNodes() {
@@ -250,6 +304,13 @@ namespace XNodeEditor {
List preSelection = preBoxSelection != null ? new List(preBoxSelection) : new List();
+ // Selection box stuff
+ Vector2 boxStartPos = GridToWindowPositionNoClipped(dragBoxStart);
+ Vector2 boxSize = mousePos - boxStartPos;
+ if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); }
+ if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); }
+ Rect selectionBox = new Rect(boxStartPos, boxSize);
+
//Save guiColor so we can revert it
Color guiColor = GUI.color;
for (int n = 0; n < graph.nodes.Count; n++) {
@@ -319,12 +380,7 @@ namespace XNodeEditor {
//If dragging a selection box, add nodes inside to selection
if (currentActivity == NodeActivity.DragGrid) {
- Vector2 startPos = GridToWindowPositionNoClipped(dragBoxStart);
- Vector2 size = mousePos - startPos;
- if (size.x < 0) { startPos.x += size.x; size.x = Mathf.Abs(size.x); }
- if (size.y < 0) { startPos.y += size.y; size.y = Mathf.Abs(size.y); }
- Rect r = new Rect(startPos, size);
- if (windowRect.Overlaps(r)) preSelection.Add(node);
+ if (windowRect.Overlaps(selectionBox)) preSelection.Add(node);
}
//Check if we are hovering any of this nodes ports
@@ -332,14 +388,14 @@ namespace XNodeEditor {
foreach (XNode.NodePort input in node.Inputs) {
//Check if port rect is available
if (!portConnectionPoints.ContainsKey(input)) continue;
- Rect r = GridToWindowRect(portConnectionPoints[input]);
+ Rect r = GridToWindowRectNoClipped(portConnectionPoints[input]);
if (r.Contains(mousePos)) hoveredPort = input;
}
//Check all output ports
foreach (XNode.NodePort output in node.Outputs) {
//Check if port rect is available
if (!portConnectionPoints.ContainsKey(output)) continue;
- Rect r = GridToWindowRect(portConnectionPoints[output]);
+ Rect r = GridToWindowRectNoClipped(portConnectionPoints[output]);
if (r.Contains(mousePos)) hoveredPort = output;
}
}
diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs
index d173abf..8a629e8 100644
--- a/Scripts/Editor/NodeEditorGUILayout.cs
+++ b/Scripts/Editor/NodeEditorGUILayout.cs
@@ -128,7 +128,7 @@ namespace XNodeEditor {
else NodeEditor.portPositions.Add(port, portPos);
}
- private static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) {
+ public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) {
Color col = GUI.color;
GUI.color = backgroundColor;
GUI.DrawTexture(rect, NodeEditorResources.dotOuter);
diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs
index aa87785..210a619 100644
--- a/Scripts/Editor/NodeEditorPreferences.cs
+++ b/Scripts/Editor/NodeEditorPreferences.cs
@@ -25,6 +25,7 @@ namespace XNodeEditor {
public Color32 highlightColor = new Color32(255, 255, 255, 255);
public bool gridSnap = true;
+ public bool autoSave = true;
[SerializeField] private string typeColorsData = "";
[NonSerialized] public Dictionary typeColors = new Dictionary();
public NoodleType noodleType = NoodleType.Curve;
@@ -73,9 +74,9 @@ namespace XNodeEditor {
XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute attrib = attribs[0] as XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute;
lastEditor = XNodeEditor.NodeEditorWindow.current.graphEditor;
lastKey = attrib.editorPrefsKey;
- VerifyLoaded();
} else return null;
}
+ if (!settings.ContainsKey(lastKey)) VerifyLoaded();
return settings[lastKey];
}
@@ -86,6 +87,7 @@ namespace XNodeEditor {
NodeSettingsGUI(lastKey, settings);
GridSettingsGUI(lastKey, settings);
+ SystemSettingsGUI(lastKey, settings);
TypeColorsGUI(lastKey, settings);
if (GUILayout.Button(new GUIContent("Set Default", "Reset all values to default"), GUILayout.Width(120))) {
ResetPrefs();
@@ -95,7 +97,7 @@ namespace XNodeEditor {
private static void GridSettingsGUI(string key, Settings settings) {
//Label
EditorGUILayout.LabelField("Grid", EditorStyles.boldLabel);
- settings.gridSnap = EditorGUILayout.Toggle("Snap", settings.gridSnap);
+ settings.gridSnap = EditorGUILayout.Toggle(new GUIContent("Snap", "Hold CTRL in editor to invert"), settings.gridSnap);
settings.gridLineColor = EditorGUILayout.ColorField("Color", settings.gridLineColor);
settings.gridBgColor = EditorGUILayout.ColorField(" ", settings.gridBgColor);
@@ -107,6 +109,14 @@ namespace XNodeEditor {
EditorGUILayout.Space();
}
+ private static void SystemSettingsGUI(string key, Settings settings) {
+ //Label
+ EditorGUILayout.LabelField("System", EditorStyles.boldLabel);
+ settings.autoSave = EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"), settings.autoSave);
+ if (GUI.changed) SavePrefs(key, settings);
+ EditorGUILayout.Space();
+ }
+
private static void NodeSettingsGUI(string key, Settings settings) {
//Label
EditorGUILayout.LabelField("Node", EditorStyles.boldLabel);
diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs
index d1f3c0a..6cdbc42 100644
--- a/Scripts/Editor/NodeEditorReflection.cs
+++ b/Scripts/Editor/NodeEditorReflection.cs
@@ -11,10 +11,12 @@ namespace XNodeEditor {
public partial class NodeEditorWindow {
/// Custom node tint colors defined with [NodeColor(r, g, b)]
public static Dictionary nodeTint { get { return _nodeTint != null ? _nodeTint : _nodeTint = GetNodeTint(); } }
- [NonSerialized] private static Dictionary _nodeTint;
+
+ [NonSerialized] private static Dictionary _nodeTint;
/// All available node types
public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } }
- [NonSerialized] private static Type[] _nodeTypes = null;
+
+ [NonSerialized] private static Type[] _nodeTypes = null;
public static Type[] GetNodeTypes() {
//Get all classes deriving from Node via reflection
@@ -32,13 +34,14 @@ namespace XNodeEditor {
return tints;
}
+ /// Get all classes deriving from baseType via reflection
public static Type[] GetDerivedTypes(Type baseType) {
- //Get all classes deriving from baseType via reflection
- Assembly assembly = Assembly.GetAssembly(baseType);
- return assembly.GetTypes().Where(t =>
- !t.IsAbstract &&
- baseType.IsAssignableFrom(t)
- ).ToArray();
+ List types = new List();
+ System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
+ foreach (Assembly assembly in assemblies) {
+ types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
+ }
+ return types.ToArray();
}
public static object ObjectFromType(Type type) {
@@ -71,10 +74,10 @@ namespace XNodeEditor {
kvp.Add(new KeyValuePair(attribs[k], methods[i]));
}
}
- #if UNITY_5_5_OR_NEWER
+#if UNITY_5_5_OR_NEWER
//Sort menu items
kvp.Sort((x, y) => x.Key.priority.CompareTo(y.Key.priority));
- #endif
+#endif
return kvp.ToArray();
}
diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs
index cf35616..db16033 100644
--- a/Scripts/Editor/NodeEditorWindow.cs
+++ b/Scripts/Editor/NodeEditorWindow.cs
@@ -20,8 +20,9 @@ namespace XNodeEditor {
private float _zoom = 1;
void OnFocus() {
- AssetDatabase.SaveAssets();
current = this;
+ graphEditor = NodeGraphEditor.GetEditor(graph);
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
}
partial void OnEnable();
@@ -37,7 +38,7 @@ namespace XNodeEditor {
public void Save() {
if (AssetDatabase.Contains(graph)) {
EditorUtility.SetDirty(graph);
- AssetDatabase.SaveAssets();
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} else SaveAs();
}
@@ -49,7 +50,7 @@ namespace XNodeEditor {
if (existingGraph != null) AssetDatabase.DeleteAsset(path);
AssetDatabase.CreateAsset(graph, path);
EditorUtility.SetDirty(graph);
- AssetDatabase.SaveAssets();
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
}
}
@@ -65,11 +66,17 @@ namespace XNodeEditor {
return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition / zoom);
}
- public Rect GridToWindowRect(Rect gridRect) {
+ public Rect GridToWindowRectNoClipped(Rect gridRect) {
gridRect.position = GridToWindowPositionNoClipped(gridRect.position);
return gridRect;
}
+ public Rect GridToWindowRect(Rect gridRect) {
+ gridRect.position = GridToWindowPosition(gridRect.position);
+ gridRect.size /= zoom;
+ return gridRect;
+ }
+
public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) {
Vector2 center = position.size * 0.5f;
float xOffset = (center.x * zoom + (panOffset.x + gridPosition.x));
diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs
index 3fa9406..8d81a07 100644
--- a/Scripts/Editor/NodeGraphEditor.cs
+++ b/Scripts/Editor/NodeGraphEditor.cs
@@ -8,8 +8,12 @@ namespace XNodeEditor {
/// Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor.
[CustomNodeGraphEditor(typeof(XNode.NodeGraph))]
public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase {
- /// Custom node editors defined with [CustomNodeGraphEditor]
- [NonSerialized] private static Dictionary editors;
+ /// The position of the window in screen space.
+ public Rect position;
+ /// Are we currently renaming a node?
+ protected bool isRenaming;
+
+ public virtual void OnGUI() { }
public virtual Texture2D GetGridTexture() {
return NodeEditorPreferences.GetSettings().gridTexture;
@@ -38,6 +42,22 @@ namespace XNodeEditor {
return NodeEditorPreferences.GetTypeColor(type);
}
+ /// Creates a copy of the original node in the graph
+ public XNode.Node CopyNode(XNode.Node original) {
+ XNode.Node node = target.CopyNode(original);
+ node.name = original.name;
+ AssetDatabase.AddObjectToAsset(node, target);
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
+ return node;
+ }
+
+ /// Safely remove a node and all its connections.
+ public void RemoveNode(XNode.Node node) {
+ UnityEngine.Object.DestroyImmediate(node, true);
+ target.RemoveNode(node);
+ if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
+ }
+
[AttributeUsage(AttributeTargets.Class)]
public class CustomNodeGraphEditorAttribute : Attribute,
XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib {
diff --git a/Scripts/Node.cs b/Scripts/Node.cs
index ddc43ea..be3d5a7 100644
--- a/Scripts/Node.cs
+++ b/Scripts/Node.cs
@@ -71,7 +71,7 @@ namespace XNode {
}
/// Initialize node. Called on creation.
- protected virtual void Init() { name = GetType().Name; }
+ protected virtual void Init() { }
/// Checks all connections for invalid references, and removes them.
public void VerifyConnections() {
@@ -127,7 +127,7 @@ namespace XNode {
}
/// Removes all instance ports from the node
- [ContextMenu("Clear instance ports")]
+ [ContextMenu("Clear Instance Ports")]
public void ClearInstancePorts() {
List instancePorts = new List(InstancePorts);
foreach (NodePort port in instancePorts) {
diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs
index acffe69..23507a6 100644
--- a/Scripts/NodeDataCache.cs
+++ b/Scripts/NodeDataCache.cs
@@ -46,13 +46,19 @@ namespace XNode {
private static void BuildCache() {
portDataCache = new PortDataCache();
System.Type baseType = typeof(Node);
- Assembly assembly = Assembly.GetAssembly(baseType);
- System.Type[] nodeTypes = assembly.GetTypes().Where(t =>
- !t.IsAbstract &&
- baseType.IsAssignableFrom(t)
- ).ToArray();
-
- for (int i = 0; i < nodeTypes.Length; i++) {
+ List nodeTypes = new List();
+ System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
+ Assembly selfAssembly = Assembly.GetAssembly(baseType);
+ if (selfAssembly.FullName.StartsWith("Assembly-CSharp")) {
+ // If xNode is not used as a DLL, check only CSharp (fast)
+ nodeTypes.AddRange(selfAssembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)));
+ } else {
+ // Else, check all DDLs (slow)
+ foreach (Assembly assembly in assemblies) {
+ nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
+ }
+ }
+ for (int i = 0; i < nodeTypes.Count; i++) {
CachePorts(nodeTypes[i]);
}
}
@@ -103,4 +109,4 @@ namespace XNode {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs
index c2092e0..32310d2 100644
--- a/Scripts/NodeGraph.cs
+++ b/Scripts/NodeGraph.cs
@@ -19,13 +19,6 @@ namespace XNode {
/// Add a node to the graph by type
public virtual Node AddNode(Type type) {
Node node = ScriptableObject.CreateInstance(type) as Node;
-#if UNITY_EDITOR
- if (!Application.isPlaying) {
- UnityEditor.AssetDatabase.AddObjectToAsset(node, this);
- UnityEditor.AssetDatabase.SaveAssets();
- node.name = UnityEditor.ObjectNames.NicifyVariableName(node.name);
- }
-#endif
nodes.Add(node);
node.graph = this;
return node;
@@ -35,13 +28,6 @@ namespace XNode {
public virtual Node CopyNode(Node original) {
Node node = ScriptableObject.Instantiate(original);
node.ClearConnections();
-#if UNITY_EDITOR
- if (!Application.isPlaying) {
- UnityEditor.AssetDatabase.AddObjectToAsset(node, this);
- UnityEditor.AssetDatabase.SaveAssets();
- node.name = UnityEditor.ObjectNames.NicifyVariableName(node.name);
- }
-#endif
nodes.Add(node);
node.graph = this;
return node;
@@ -51,12 +37,6 @@ namespace XNode {
///
public void RemoveNode(Node node) {
node.ClearConnections();
-#if UNITY_EDITOR
- if (!Application.isPlaying) {
- DestroyImmediate(node, true);
- UnityEditor.AssetDatabase.SaveAssets();
- }
-#endif
nodes.Remove(node);
}
@@ -71,6 +51,7 @@ namespace XNode {
NodeGraph graph = Instantiate(this);
// Instantiate all nodes inside the graph
for (int i = 0; i < nodes.Count; i++) {
+ if (nodes[i] == null) continue;
Node node = Instantiate(nodes[i]) as Node;
node.graph = graph;
graph.nodes[i] = node;
@@ -78,6 +59,7 @@ namespace XNode {
// Redirect all connections
for (int i = 0; i < graph.nodes.Count; i++) {
+ if (graph.nodes[i] == null) continue;
foreach (NodePort port in graph.nodes[i].Ports) {
port.Redirect(nodes, graph.nodes);
}
diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs
index b609066..65ad4be 100644
--- a/Scripts/NodePort.cs
+++ b/Scripts/NodePort.cs
@@ -216,6 +216,14 @@ namespace XNode {
return port;
}
+ /// Get index of the connection connecting this and specified ports
+ public int GetConnectionIndex(NodePort port) {
+ for (int i = 0; i < ConnectionCount; i++) {
+ if (connections[i].Port == port) return i;
+ }
+ return -1;
+ }
+
public bool IsConnectedTo(NodePort port) {
for (int i = 0; i < connections.Count; i++) {
if (connections[i].Port == port) return true;
@@ -250,6 +258,11 @@ namespace XNode {
}
}
+ /// Get reroute points for a given connection. This is used for organization
+ public List GetReroutePoints(int index) {
+ return connections[index].reroutePoints;
+ }
+
/// Swap connected nodes from the old list with nodes from the new list
public void Redirect(List oldNodes, List newNodes) {
foreach (PortConnection connection in connections) {
@@ -265,6 +278,8 @@ namespace XNode {
public NodePort Port { get { return port != null ? port : port = GetPort(); } }
[NonSerialized] private NodePort port;
+ /// Extra connection path points for organization
+ [SerializeField] public List reroutePoints = new List();
public PortConnection(NodePort port) {
this.port = port;