1
0
mirror of https://github.com/Siccity/xNode.git synced 2026-03-26 22:49:02 +08:00

Added Drop event to Node Graph

This commit is contained in:
ReDDarKwh 2019-06-15 13:55:01 -04:00
parent 53bba68ae0
commit 598c75fb3a
3 changed files with 308 additions and 126 deletions

View File

@ -4,8 +4,10 @@ using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
namespace XNodeEditor { namespace XNodeEditor
public partial class NodeEditorWindow { {
public partial class NodeEditorWindow
{
public enum NodeActivity { Idle, HoldNode, DragNode, HoldGrid, DragGrid } public enum NodeActivity { Idle, HoldNode, DragNode, HoldGrid, DragGrid }
public static NodeActivity currentActivity = NodeActivity.Idle; public static NodeActivity currentActivity = NodeActivity.Idle;
public static bool isPanning { get; private set; } public static bool isPanning { get; private set; }
@ -28,12 +30,14 @@ namespace XNodeEditor {
private Rect selectionBox; private Rect selectionBox;
private bool isDoubleClick = false; private bool isDoubleClick = false;
private struct RerouteReference { private struct RerouteReference
{
public XNode.NodePort port; public XNode.NodePort port;
public int connectionIndex; public int connectionIndex;
public int pointIndex; public int pointIndex;
public RerouteReference(XNode.NodePort port, int connectionIndex, int pointIndex) { public RerouteReference(XNode.NodePort port, int connectionIndex, int pointIndex)
{
this.port = port; this.port = port;
this.connectionIndex = connectionIndex; this.connectionIndex = connectionIndex;
this.pointIndex = pointIndex; this.pointIndex = pointIndex;
@ -45,10 +49,31 @@ namespace XNodeEditor {
public Vector2 GetPoint() { return port.GetReroutePoints(connectionIndex)[pointIndex]; } public Vector2 GetPoint() { return port.GetReroutePoints(connectionIndex)[pointIndex]; }
} }
public void Controls() { public void Controls()
{
wantsMouseMove = true; wantsMouseMove = true;
Event e = Event.current; Event e = Event.current;
switch (e.type) { switch (e.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (e.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
foreach (object droppedItem in DragAndDrop.objectReferences)
{
graphEditor.DropItem(droppedItem);
}
}
break;
case EventType.MouseMove: case EventType.MouseMove:
break; break;
case EventType.ScrollWheel: case EventType.ScrollWheel:
@ -58,52 +83,69 @@ namespace XNodeEditor {
if (NodeEditorPreferences.GetSettings().zoomToMouse) panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset); if (NodeEditorPreferences.GetSettings().zoomToMouse) panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset);
break; break;
case EventType.MouseDrag: case EventType.MouseDrag:
if (e.button == 0) { if (e.button == 0)
if (IsDraggingPort) { {
if (IsHoveringPort && hoveredPort.IsInput && draggedOutput.CanConnectTo(hoveredPort)) { if (IsDraggingPort)
if (!draggedOutput.IsConnectedTo(hoveredPort)) { {
if (IsHoveringPort && hoveredPort.IsInput && draggedOutput.CanConnectTo(hoveredPort))
{
if (!draggedOutput.IsConnectedTo(hoveredPort))
{
draggedOutputTarget = hoveredPort; draggedOutputTarget = hoveredPort;
} }
} else { }
else
{
draggedOutputTarget = null; draggedOutputTarget = null;
} }
Repaint(); Repaint();
} else if (currentActivity == NodeActivity.HoldNode) { }
else if (currentActivity == NodeActivity.HoldNode)
{
RecalculateDragOffsets(e); RecalculateDragOffsets(e);
currentActivity = NodeActivity.DragNode; currentActivity = NodeActivity.DragNode;
Repaint(); Repaint();
} }
if (currentActivity == NodeActivity.DragNode) { if (currentActivity == NodeActivity.DragNode)
{
// Holding ctrl inverts grid snap // Holding ctrl inverts grid snap
bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap; bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap;
if (e.control) gridSnap = !gridSnap; if (e.control) gridSnap = !gridSnap;
Vector2 mousePos = WindowToGridPosition(e.mousePosition); Vector2 mousePos = WindowToGridPosition(e.mousePosition);
// Move selected nodes with offset // Move selected nodes with offset
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.Node)
{
XNode.Node node = Selection.objects[i] as XNode.Node; XNode.Node node = Selection.objects[i] as XNode.Node;
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; node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8;
node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8; node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8;
} }
// 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;
if (portConnectionPoints.TryGetValue(output, out rect)) { if (portConnectionPoints.TryGetValue(output, out rect))
{
rect.position += offset; rect.position += offset;
portConnectionPoints[output] = rect; portConnectionPoints[output] = rect;
} }
} }
foreach (XNode.NodePort input in node.Inputs) { foreach (XNode.NodePort input in node.Inputs)
{
Rect rect; Rect rect;
if (portConnectionPoints.TryGetValue(input, out rect)) { if (portConnectionPoints.TryGetValue(input, out rect))
{
rect.position += offset; rect.position += offset;
portConnectionPoints[input] = rect; portConnectionPoints[input] = rect;
} }
@ -112,22 +154,28 @@ namespace XNodeEditor {
} }
} }
// Move selected reroutes with offset // Move selected reroutes with offset
for (int i = 0; i < selectedReroutes.Count; i++) { for (int i = 0; i < selectedReroutes.Count; i++)
{
Vector2 pos = mousePos + dragOffset[Selection.objects.Length + i]; Vector2 pos = mousePos + dragOffset[Selection.objects.Length + i];
if (gridSnap) { if (gridSnap)
{
pos.x = (Mathf.Round(pos.x / 16) * 16); pos.x = (Mathf.Round(pos.x / 16) * 16);
pos.y = (Mathf.Round(pos.y / 16) * 16); pos.y = (Mathf.Round(pos.y / 16) * 16);
} }
selectedReroutes[i].SetPoint(pos); selectedReroutes[i].SetPoint(pos);
} }
Repaint(); Repaint();
} else if (currentActivity == NodeActivity.HoldGrid) { }
else if (currentActivity == NodeActivity.HoldGrid)
{
currentActivity = NodeActivity.DragGrid; currentActivity = NodeActivity.DragGrid;
preBoxSelection = Selection.objects; preBoxSelection = Selection.objects;
preBoxSelectionReroute = selectedReroutes.ToArray(); preBoxSelectionReroute = selectedReroutes.ToArray();
dragBoxStart = WindowToGridPosition(e.mousePosition); dragBoxStart = WindowToGridPosition(e.mousePosition);
Repaint(); Repaint();
} else if (currentActivity == NodeActivity.DragGrid) { }
else if (currentActivity == NodeActivity.DragGrid)
{
Vector2 boxStartPos = GridToWindowPosition(dragBoxStart); Vector2 boxStartPos = GridToWindowPosition(dragBoxStart);
Vector2 boxSize = e.mousePosition - boxStartPos; Vector2 boxSize = e.mousePosition - boxStartPos;
if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); }
@ -135,22 +183,30 @@ namespace XNodeEditor {
selectionBox = new Rect(boxStartPos, boxSize); selectionBox = new Rect(boxStartPos, boxSize);
Repaint(); Repaint();
} }
} else if (e.button == 1 || e.button == 2) { }
else if (e.button == 1 || e.button == 2)
{
panOffset += e.delta * zoom; panOffset += e.delta * zoom;
isPanning = true; isPanning = true;
} }
break; break;
case EventType.MouseDown: case EventType.MouseDown:
Repaint(); Repaint();
if (e.button == 0) { if (e.button == 0)
{
draggedOutputReroutes.Clear(); draggedOutputReroutes.Clear();
if (IsHoveringPort) { if (IsHoveringPort)
if (hoveredPort.IsOutput) { {
if (hoveredPort.IsOutput)
{
draggedOutput = hoveredPort; draggedOutput = hoveredPort;
} else { }
else
{
hoveredPort.VerifyConnections(); hoveredPort.VerifyConnections();
if (hoveredPort.IsConnected) { if (hoveredPort.IsConnected)
{
XNode.Node node = hoveredPort.node; XNode.Node node = hoveredPort.node;
XNode.NodePort output = hoveredPort.Connection; XNode.NodePort output = hoveredPort.Connection;
int outputConnectionIndex = output.GetConnectionIndex(hoveredPort); int outputConnectionIndex = output.GetConnectionIndex(hoveredPort);
@ -161,25 +217,33 @@ namespace XNodeEditor {
if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
} }
} }
} 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(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);
// Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown. // Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown.
isDoubleClick = (e.clickCount == 2); isDoubleClick = (e.clickCount == 2);
e.Use(); e.Use();
currentActivity = NodeActivity.HoldNode; currentActivity = NodeActivity.HoldNode;
} else if (IsHoveringReroute) { }
else if (IsHoveringReroute)
{
// If reroute isn't selected // If reroute isn't selected
if (!selectedReroutes.Contains(hoveredReroute)) { if (!selectedReroutes.Contains(hoveredReroute))
{
// Add it // Add it
if (e.control || e.shift) selectedReroutes.Add(hoveredReroute); if (e.control || e.shift) selectedReroutes.Add(hoveredReroute);
// Select it // Select it
else { else
{
selectedReroutes = new List<RerouteReference>() { hoveredReroute }; selectedReroutes = new List<RerouteReference>() { hoveredReroute };
Selection.activeObject = null; Selection.activeObject = null;
} }
@ -191,9 +255,11 @@ namespace XNodeEditor {
currentActivity = NodeActivity.HoldNode; currentActivity = NodeActivity.HoldNode;
} }
// If mousedown on grid background, deselect all // If mousedown on grid background, deselect all
else if (!IsHoveringNode) { else if (!IsHoveringNode)
{
currentActivity = NodeActivity.HoldGrid; currentActivity = NodeActivity.HoldGrid;
if (!e.control && !e.shift) { if (!e.control && !e.shift)
{
selectedReroutes.Clear(); selectedReroutes.Clear();
Selection.activeObject = null; Selection.activeObject = null;
} }
@ -201,17 +267,21 @@ namespace XNodeEditor {
} }
break; break;
case EventType.MouseUp: case EventType.MouseUp:
if (e.button == 0) { if (e.button == 0)
{
//Port drag release //Port drag release
if (IsDraggingPort) { if (IsDraggingPort)
{
//If connection is valid, save it //If connection is valid, save it
if (draggedOutputTarget != null) { if (draggedOutputTarget != null)
{
XNode.Node node = draggedOutputTarget.node; XNode.Node node = draggedOutputTarget.node;
if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget); if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget);
// ConnectionIndex can be -1 if the connection is removed instantly after creation // ConnectionIndex can be -1 if the connection is removed instantly after creation
int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget); int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget);
if (connectionIndex != -1) { if (connectionIndex != -1)
{
draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes); draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes);
if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
@ -222,13 +292,18 @@ namespace XNodeEditor {
draggedOutputTarget = null; draggedOutputTarget = null;
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} else if (currentActivity == NodeActivity.DragNode) { }
else if (currentActivity == NodeActivity.DragNode)
{
IEnumerable<XNode.Node> nodes = Selection.objects.Where(x => x is XNode.Node).Select(x => x as XNode.Node); IEnumerable<XNode.Node> nodes = Selection.objects.Where(x => x is XNode.Node).Select(x => x as XNode.Node);
foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node); foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} else if (!IsHoveringNode) { }
else if (!IsHoveringNode)
{
// If click outside node, release field focus // If click outside node, release field focus
if (!isPanning) { if (!isPanning)
{
EditorGUI.FocusTextInControl(null); EditorGUI.FocusTextInControl(null);
EditorGUIUtility.editingTextField = false; EditorGUIUtility.editingTextField = false;
} }
@ -236,43 +311,60 @@ namespace XNodeEditor {
} }
// If click node header, select it. // If click node header, select it.
if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift)) { if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift))
{
selectedReroutes.Clear(); selectedReroutes.Clear();
SelectNode(hoveredNode, false); SelectNode(hoveredNode, false);
// 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;
} }
} }
// If click reroute, select it. // If click reroute, select it.
if (IsHoveringReroute && !(e.control || e.shift)) { if (IsHoveringReroute && !(e.control || e.shift))
{
selectedReroutes = new List<RerouteReference>() { hoveredReroute }; selectedReroutes = new List<RerouteReference>() { hoveredReroute };
Selection.activeObject = null; Selection.activeObject = null;
} }
Repaint(); Repaint();
currentActivity = NodeActivity.Idle; currentActivity = NodeActivity.Idle;
} else if (e.button == 1 || e.button == 2) { }
if (!isPanning) { else if (e.button == 1 || e.button == 2)
if (IsDraggingPort) { {
if (!isPanning)
{
if (IsDraggingPort)
{
draggedOutputReroutes.Add(WindowToGridPosition(e.mousePosition)); draggedOutputReroutes.Add(WindowToGridPosition(e.mousePosition));
} else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null && selectedReroutes.Count == 1) { }
else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null && selectedReroutes.Count == 1)
{
selectedReroutes[0].InsertPoint(selectedReroutes[0].GetPoint()); selectedReroutes[0].InsertPoint(selectedReroutes[0].GetPoint());
selectedReroutes[0] = new RerouteReference(selectedReroutes[0].port, selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1); selectedReroutes[0] = new RerouteReference(selectedReroutes[0].port, selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1);
} else if (IsHoveringReroute) { }
else if (IsHoveringReroute)
{
ShowRerouteContextMenu(hoveredReroute); ShowRerouteContextMenu(hoveredReroute);
} 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(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));
e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places. e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places.
} else if (!IsHoveringNode) { }
else if (!IsHoveringNode)
{
GenericMenu menu = new GenericMenu(); GenericMenu menu = new GenericMenu();
graphEditor.AddContextMenuItems(menu); graphEditor.AddContextMenuItems(menu);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
@ -286,21 +378,29 @@ namespace XNodeEditor {
case EventType.KeyDown: case EventType.KeyDown:
if (EditorGUIUtility.editingTextField) break; if (EditorGUIUtility.editingTextField) break;
else if (e.keyCode == KeyCode.F) Home(); else if (e.keyCode == KeyCode.F) Home();
if (IsMac()) { if (IsMac())
{
if (e.keyCode == KeyCode.Return) RenameSelectedNode(); if (e.keyCode == KeyCode.Return) RenameSelectedNode();
} else { }
else
{
if (e.keyCode == KeyCode.F2) RenameSelectedNode(); if (e.keyCode == KeyCode.F2) RenameSelectedNode();
} }
break; break;
case EventType.ValidateCommand: case EventType.ValidateCommand:
case EventType.ExecuteCommand: case EventType.ExecuteCommand:
if (e.commandName == "SoftDelete") { if (e.commandName == "SoftDelete")
{
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
e.Use(); e.Use();
} else if (IsMac() && e.commandName == "Delete") { }
else if (IsMac() && e.commandName == "Delete")
{
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
e.Use(); e.Use();
} else if (e.commandName == "Duplicate") { }
else if (e.commandName == "Duplicate")
{
if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes(); if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes();
e.Use(); e.Use();
} }
@ -308,7 +408,8 @@ namespace XNodeEditor {
break; break;
case EventType.Ignore: case EventType.Ignore:
// If release mouse outside window // If release mouse outside window
if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid) { if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid)
{
Repaint(); Repaint();
currentActivity = NodeActivity.Idle; currentActivity = NodeActivity.Idle;
} }
@ -316,7 +417,8 @@ namespace XNodeEditor {
} }
} }
public bool IsMac() { public bool IsMac()
{
#if UNITY_2017_1_OR_NEWER #if UNITY_2017_1_OR_NEWER
return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX; return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX;
#else #else
@ -324,38 +426,47 @@ namespace XNodeEditor {
#endif #endif
} }
private void RecalculateDragOffsets(Event current) { private void RecalculateDragOffsets(Event current)
{
dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count]; dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count];
// Selected nodes // Selected 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.Node)
{
XNode.Node node = Selection.objects[i] as XNode.Node; XNode.Node node = Selection.objects[i] as XNode.Node;
dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition); dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition);
} }
} }
// Selected reroutes // Selected reroutes
for (int i = 0; i < selectedReroutes.Count; i++) { for (int i = 0; i < selectedReroutes.Count; i++)
{
dragOffset[Selection.objects.Length + i] = selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition); dragOffset[Selection.objects.Length + i] = selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition);
} }
} }
/// <summary> Puts all nodes in focus. If no nodes are present, resets view to </summary> /// <summary> Puts all nodes in focus. If no nodes are present, resets view to </summary>
public void Home() { public void Home()
{
zoom = 2; zoom = 2;
panOffset = Vector2.zero; panOffset = Vector2.zero;
} }
/// <summary> Remove nodes in the graph in Selection.objects</summary> /// <summary> Remove nodes in the graph in Selection.objects</summary>
public void RemoveSelectedNodes() { public void RemoveSelectedNodes()
{
// We need to delete reroutes starting at the highest point index to avoid shifting indices // We need to delete reroutes starting at the highest point index to avoid shifting indices
selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList(); selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList();
for (int i = 0; i < selectedReroutes.Count; i++) { for (int i = 0; i < selectedReroutes.Count; i++)
{
selectedReroutes[i].RemovePoint(); selectedReroutes[i].RemovePoint();
} }
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.Node)
{
XNode.Node node = item as XNode.Node; XNode.Node node = item as XNode.Node;
graphEditor.RemoveNode(node); graphEditor.RemoveNode(node);
} }
@ -363,33 +474,43 @@ 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.Node)
{
XNode.Node node = Selection.activeObject as XNode.Node; XNode.Node node = Selection.activeObject as XNode.Node;
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);
} else { }
else
{
RenamePopup.Show(Selection.activeObject); RenamePopup.Show(Selection.activeObject);
} }
} }
} }
/// <summary> Draw this node on top of other nodes by placing it last in the graph.nodes list </summary> /// <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) { public void MoveNodeToTop(XNode.Node node)
{
int index; int index;
while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) { while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1)
{
graph.nodes[index] = graph.nodes[index + 1]; graph.nodes[index] = graph.nodes[index + 1];
graph.nodes[index + 1] = node; 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.Node, XNode.Node> substitutes = new Dictionary<XNode.Node, XNode.Node>();
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.Node)
{
XNode.Node srcNode = Selection.objects[i] as XNode.Node; XNode.Node srcNode = Selection.objects[i] as XNode.Node;
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.Node newNode = graphEditor.CopyNode(srcNode);
@ -400,17 +521,22 @@ namespace XNodeEditor {
} }
// 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.Node)
{
XNode.Node srcNode = Selection.objects[i] as XNode.Node; XNode.Node srcNode = Selection.objects[i] as XNode.Node;
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.Node 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();
inputPort = newNodeIn.GetInputPort(inputPort.fieldName); inputPort = newNodeIn.GetInputPort(inputPort.fieldName);
@ -425,8 +551,10 @@ namespace XNodeEditor {
} }
/// <summary> Draw a connection as we are dragging it </summary> /// <summary> Draw a connection as we are dragging it </summary>
public void DrawDraggedConnection() { public void DrawDraggedConnection()
if (IsDraggingPort) { {
if (IsDraggingPort)
{
Color col = NodeEditorPreferences.GetTypeColor(draggedOutput.ValueType); Color col = NodeEditorPreferences.GetTypeColor(draggedOutput.ValueType);
col.a = draggedOutputTarget != null ? 1.0f : 0.6f; col.a = draggedOutputTarget != null ? 1.0f : 0.6f;
@ -434,7 +562,8 @@ namespace XNodeEditor {
if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return; if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return;
List<Vector2> gridPoints = new List<Vector2>(); List<Vector2> gridPoints = new List<Vector2>();
gridPoints.Add(fromRect.center); gridPoints.Add(fromRect.center);
for (int i = 0; i < draggedOutputReroutes.Count; i++) { for (int i = 0; i < draggedOutputReroutes.Count; i++)
{
gridPoints.Add(draggedOutputReroutes[i]); gridPoints.Add(draggedOutputReroutes[i]);
} }
if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center); if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center);
@ -448,7 +577,8 @@ namespace XNodeEditor {
frcol.a = 0.6f; frcol.a = 0.6f;
// Loop through reroute points again and draw the points // Loop through reroute points again and draw the points
for (int i = 0; i < draggedOutputReroutes.Count; i++) { for (int i = 0; i < draggedOutputReroutes.Count; i++)
{
// Draw reroute point at position // Draw reroute point at position
Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16)); Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16));
rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8);
@ -459,7 +589,8 @@ namespace XNodeEditor {
} }
} }
bool IsHoveringTitle(XNode.Node node) { bool IsHoveringTitle(XNode.Node 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);

View File

@ -4,10 +4,12 @@ using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
namespace XNodeEditor { namespace XNodeEditor
{
/// <summary> Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. </summary> /// <summary> Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. </summary>
[CustomNodeGraphEditor(typeof(XNode.NodeGraph))] [CustomNodeGraphEditor(typeof(XNode.NodeGraph))]
public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase<NodeGraphEditor, NodeGraphEditor.CustomNodeGraphEditorAttribute, XNode.NodeGraph> { public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase<NodeGraphEditor, NodeGraphEditor.CustomNodeGraphEditorAttribute, XNode.NodeGraph>
{
[Obsolete("Use window.position instead")] [Obsolete("Use window.position instead")]
public Rect position { get { return window.position; } set { window.position = value; } } public Rect position { get { return window.position; } set { window.position = value; } }
/// <summary> Are we currently renaming a node? </summary> /// <summary> Are we currently renaming a node? </summary>
@ -18,21 +20,25 @@ namespace XNodeEditor {
/// <summary> Called when opened by NodeEditorWindow </summary> /// <summary> Called when opened by NodeEditorWindow </summary>
public virtual void OnOpen() { } public virtual void OnOpen() { }
public virtual Texture2D GetGridTexture() { public virtual Texture2D GetGridTexture()
{
return NodeEditorPreferences.GetSettings().gridTexture; return NodeEditorPreferences.GetSettings().gridTexture;
} }
public virtual Texture2D GetSecondaryGridTexture() { public virtual Texture2D GetSecondaryGridTexture()
{
return NodeEditorPreferences.GetSettings().crossTexture; return NodeEditorPreferences.GetSettings().crossTexture;
} }
/// <summary> Return default settings for this graph type. This is the settings the user will load if no previous settings have been saved. </summary> /// <summary> Return default settings for this graph type. This is the settings the user will load if no previous settings have been saved. </summary>
public virtual NodeEditorPreferences.Settings GetDefaultPreferences() { public virtual NodeEditorPreferences.Settings GetDefaultPreferences()
{
return new NodeEditorPreferences.Settings(); return new NodeEditorPreferences.Settings();
} }
/// <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.Node.CreateNodeMenuAttribute attrib;
if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path
@ -42,16 +48,19 @@ 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)
{
Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition); Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition);
for (int i = 0; i < NodeEditorWindow.nodeTypes.Length; i++) { for (int i = 0; i < NodeEditorWindow.nodeTypes.Length; i++)
{
Type type = NodeEditorWindow.nodeTypes[i]; Type type = NodeEditorWindow.nodeTypes[i];
//Get node context menu path //Get node context menu path
string path = GetNodeMenuName(type); string path = GetNodeMenuName(type);
if (string.IsNullOrEmpty(path)) continue; if (string.IsNullOrEmpty(path)) continue;
menu.AddItem(new GUIContent(path), false, () => { menu.AddItem(new GUIContent(path), false, () =>
{
CreateNode(type, pos); CreateNode(type, pos);
}); });
} }
@ -60,19 +69,31 @@ namespace XNodeEditor {
NodeEditorWindow.AddCustomContextMenuItems(menu, target); NodeEditorWindow.AddCustomContextMenuItems(menu, target);
} }
public virtual Color GetPortColor(XNode.NodePort port) { public virtual Color GetPortColor(XNode.NodePort port)
{
return GetTypeColor(port.ValueType); return GetTypeColor(port.ValueType);
} }
public virtual Color GetTypeColor(Type type) { public virtual Color GetTypeColor(Type type)
{
return NodeEditorPreferences.GetTypeColor(type); return NodeEditorPreferences.GetTypeColor(type);
} }
public virtual void DropItem(object droppedItem)
{
target.OnDrop(droppedItem, NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition));
}
/// <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.Node node = target.AddNode(type);
node.position = position; node.position = position;
if (string.IsNullOrEmpty(node.name)) { if (string.IsNullOrEmpty(node.name))
{
// Automatically remove redundant 'Node' postfix // Automatically remove redundant 'Node' postfix
string typeName = type.Name; string typeName = type.Name;
if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node")); if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node"));
@ -84,7 +105,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.Node CopyNode(XNode.Node original)
{
XNode.Node node = target.CopyNode(original); XNode.Node node = target.CopyNode(original);
node.name = original.name; node.name = original.name;
AssetDatabase.AddObjectToAsset(node, target); AssetDatabase.AddObjectToAsset(node, target);
@ -93,26 +115,32 @@ namespace XNodeEditor {
} }
/// <summary> Safely remove a node and all its connections. </summary> /// <summary> Safely remove a node and all its connections. </summary>
public virtual void RemoveNode(XNode.Node node) { public virtual void RemoveNode(XNode.Node node)
{
target.RemoveNode(node); target.RemoveNode(node);
UnityEngine.Object.DestroyImmediate(node, true); UnityEngine.Object.DestroyImmediate(node, true);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} }
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class CustomNodeGraphEditorAttribute : Attribute, public class CustomNodeGraphEditorAttribute : Attribute,
XNodeEditor.Internal.NodeEditorBase<NodeGraphEditor, NodeGraphEditor.CustomNodeGraphEditorAttribute, XNode.NodeGraph>.INodeEditorAttrib { XNodeEditor.Internal.NodeEditorBase<NodeGraphEditor, NodeGraphEditor.CustomNodeGraphEditorAttribute, XNode.NodeGraph>.INodeEditorAttrib
{
private Type inspectedType; private Type inspectedType;
public string editorPrefsKey; public string editorPrefsKey;
/// <summary> Tells a NodeGraphEditor which Graph type it is an editor for </summary> /// <summary> Tells a NodeGraphEditor which Graph 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>
/// <param name="editorPrefsKey">Define unique key for unique layout settings instance</param> /// <param name="editorPrefsKey">Define unique key for unique layout settings instance</param>
public CustomNodeGraphEditorAttribute(Type inspectedType, string editorPrefsKey = "xNode.Settings") { public CustomNodeGraphEditorAttribute(Type inspectedType, string editorPrefsKey = "xNode.Settings")
{
this.inspectedType = inspectedType; this.inspectedType = inspectedType;
this.editorPrefsKey = editorPrefsKey; this.editorPrefsKey = editorPrefsKey;
} }
public Type GetInspectedType() { public Type GetInspectedType()
{
return inspectedType; return inspectedType;
} }
} }

View File

@ -2,22 +2,26 @@
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace XNode { namespace XNode
{
/// <summary> Base class for all node graphs </summary> /// <summary> Base class for all node graphs </summary>
[Serializable] [Serializable]
public abstract class NodeGraph : ScriptableObject { public abstract class NodeGraph : ScriptableObject
{
/// <summary> All nodes in the graph. <para/> /// <summary> All nodes in the graph. <para/>
/// 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>();
/// <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> 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;
Node node = ScriptableObject.CreateInstance(type) as Node; Node node = ScriptableObject.CreateInstance(type) as Node;
node.graph = this; node.graph = this;
@ -26,7 +30,8 @@ namespace XNode {
} }
/// <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 virtual Node CopyNode(Node original) { public virtual Node CopyNode(Node original)
{
Node.graphHotfix = this; Node.graphHotfix = this;
Node node = ScriptableObject.Instantiate(original); Node node = ScriptableObject.Instantiate(original);
node.graph = this; node.graph = this;
@ -37,16 +42,20 @@ namespace XNode {
/// <summary> Safely remove a node and all its connections </summary> /// <summary> Safely remove a node and all its connections </summary>
/// <param name="node"> The node to remove </param> /// <param name="node"> The node to remove </param>
public virtual void RemoveNode(Node node) { public virtual void RemoveNode(Node node)
{
node.ClearConnections(); node.ClearConnections();
nodes.Remove(node); nodes.Remove(node);
if (Application.isPlaying) Destroy(node); if (Application.isPlaying) Destroy(node);
} }
/// <summary> Remove all nodes and connections from the graph </summary> /// <summary> Remove all nodes and connections from the graph </summary>
public virtual void Clear() { public virtual void Clear()
if (Application.isPlaying) { {
for (int i = 0; i < nodes.Count; i++) { if (Application.isPlaying)
{
for (int i = 0; i < nodes.Count; i++)
{
Destroy(nodes[i]); Destroy(nodes[i]);
} }
} }
@ -54,11 +63,13 @@ namespace XNode {
} }
/// <summary> Create a new deep copy of this graph </summary> /// <summary> Create a new deep copy of this graph </summary>
public virtual XNode.NodeGraph Copy() { public virtual XNode.NodeGraph Copy()
{
// Instantiate a new nodegraph instance // Instantiate a new nodegraph instance
NodeGraph graph = Instantiate(this); NodeGraph graph = Instantiate(this);
// Instantiate all nodes inside the graph // Instantiate all nodes inside the graph
for (int i = 0; i < nodes.Count; i++) { for (int i = 0; i < nodes.Count; i++)
{
if (nodes[i] == null) continue; if (nodes[i] == null) continue;
Node.graphHotfix = graph; Node.graphHotfix = graph;
Node node = Instantiate(nodes[i]) as Node; Node node = Instantiate(nodes[i]) as Node;
@ -67,9 +78,11 @@ namespace XNode {
} }
// Redirect all connections // Redirect all connections
for (int i = 0; i < graph.nodes.Count; i++) { for (int i = 0; i < graph.nodes.Count; i++)
{
if (graph.nodes[i] == null) continue; if (graph.nodes[i] == null) continue;
foreach (NodePort port in graph.nodes[i].Ports) { foreach (NodePort port in graph.nodes[i].Ports)
{
port.Redirect(nodes, graph.nodes); port.Redirect(nodes, graph.nodes);
} }
} }
@ -77,9 +90,19 @@ namespace XNode {
return graph; return graph;
} }
protected virtual void OnDestroy() { protected virtual void OnDestroy()
{
// Remove all nodes prior to graph destruction // Remove all nodes prior to graph destruction
Clear(); Clear();
} }
/// <summary> Is called when something is dragged in the editor </summary>
/// <param name="droppedObject">The dropped object</param>
public virtual void OnDrop(object droppedObject, Vector2 dropPosition)
{
Debug.LogWarning("No OnDrop(NodePort port) override defined for " + GetType());
}
} }
} }