1
0
mirror of https://github.com/Siccity/xNode.git synced 2025-12-20 17:26:02 +08:00

Added Virtual portStyle

- Added the GetPortStyle(...) as virtual method at GraphEditor.
With this, users can customize the texture for different ports, and they can also modify the padding without having to overwrite the default xNode style.

This prevents many problems when working with multiple graphics.

The style properties used from Style is:
- padding-left.
- padding-right.
- normal.background, to border texture.
- active.background, to dot texture.
This commit is contained in:
juliocp 2020-10-12 21:17:53 -03:00
parent e3127a9135
commit fdd9b24eca
5 changed files with 524 additions and 236 deletions

View File

@ -5,8 +5,10 @@ using UnityEditor;
using UnityEngine; using UnityEngine;
using XNodeEditor.Internal; using XNodeEditor.Internal;
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; }
@ -43,14 +45,17 @@ namespace XNodeEditor {
private Vector2 lastMousePosition; private Vector2 lastMousePosition;
private float dragThreshold = 1f; private float dragThreshold = 1f;
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.DragUpdated:
case EventType.DragPerform: case EventType.DragPerform:
DragAndDrop.visualMode = DragAndDropVisualMode.Generic; DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
if (e.type == EventType.DragPerform) { if (e.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag(); DragAndDrop.AcceptDrag();
graphEditor.OnDropObjects(DragAndDrop.objectReferences); graphEditor.OnDropObjects(DragAndDrop.objectReferences);
} }
@ -66,52 +71,68 @@ 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 (IsDraggingPort)
{
// Set target even if we can't connect, so as to prevent auto-conn menu from opening erroneously // Set target even if we can't connect, so as to prevent auto-conn menu from opening erroneously
if (IsHoveringPort && hoveredPort.IsInput && !draggedOutput.IsConnectedTo(hoveredPort)) { if (IsHoveringPort && hoveredPort.IsInput && !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;
Undo.RecordObject(node, "Moved Node"); Undo.RecordObject(node, "Moved 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;
} }
@ -120,22 +141,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); }
@ -143,9 +170,12 @@ 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)
{
//check drag threshold for larger screens //check drag threshold for larger screens
if (e.delta.magnitude > dragThreshold) { if (e.delta.magnitude > dragThreshold)
{
panOffset += e.delta * zoom; panOffset += e.delta * zoom;
isPanning = true; isPanning = true;
} }
@ -153,17 +183,23 @@ namespace XNodeEditor {
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;
autoConnectOutput = hoveredPort; autoConnectOutput = hoveredPort;
} else { }
else
{
hoveredPort.VerifyConnections(); hoveredPort.VerifyConnections();
autoConnectOutput = null; autoConnectOutput = null;
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);
@ -174,25 +210,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;
} }
@ -204,9 +248,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;
} }
@ -214,24 +260,29 @@ 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 && draggedOutput.CanConnectTo(draggedOutputTarget)) { if (draggedOutputTarget != null && draggedOutput.CanConnectTo(draggedOutputTarget))
{
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);
} }
} }
// Open context menu for auto-connection if there is no target node // Open context menu for auto-connection if there is no target node
else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null) { else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null)
{
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));
@ -241,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;
} }
@ -255,44 +311,61 @@ 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);
autoConnectOutput = null; autoConnectOutput = null;
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)
{
autoConnectOutput = null; autoConnectOutput = null;
GenericMenu menu = new GenericMenu(); GenericMenu menu = new GenericMenu();
graphEditor.AddContextMenuItems(menu); graphEditor.AddContextMenuItems(menu);
@ -307,18 +380,27 @@ 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 (NodeEditorUtilities.IsMac()) { if (NodeEditorUtilities.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();
} }
if (e.keyCode == KeyCode.A) { if (e.keyCode == KeyCode.A)
if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node))) { {
foreach (XNode.Node node in graph.nodes) { if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node)))
{
foreach (XNode.Node node in graph.nodes)
{
DeselectNode(node); DeselectNode(node);
} }
} else { }
foreach (XNode.Node node in graph.nodes) { else
{
foreach (XNode.Node node in graph.nodes)
{
SelectNode(node, true); SelectNode(node, true);
} }
} }
@ -327,19 +409,28 @@ namespace XNodeEditor {
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 (NodeEditorUtilities.IsMac() && e.commandName == "Delete") { }
else if (NodeEditorUtilities.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();
} else if (e.commandName == "Copy") { }
else if (e.commandName == "Copy")
{
if (e.type == EventType.ExecuteCommand) CopySelectedNodes(); if (e.type == EventType.ExecuteCommand) CopySelectedNodes();
e.Use(); e.Use();
} else if (e.commandName == "Paste") { }
else if (e.commandName == "Paste")
{
if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition)); if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition));
e.Use(); e.Use();
} }
@ -347,7 +438,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;
} }
@ -355,45 +447,57 @@ namespace XNodeEditor {
} }
} }
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 selected nodes in focus. If no nodes are present, resets view and zoom to to origin </summary> /// <summary> Puts all selected nodes in focus. If no nodes are present, resets view and zoom to to origin </summary>
public void Home() { public void Home()
{
var nodes = Selection.objects.Where(o => o is XNode.Node).Cast<XNode.Node>().ToList(); var nodes = Selection.objects.Where(o => o is XNode.Node).Cast<XNode.Node>().ToList();
if (nodes.Count > 0) { if (nodes.Count > 0)
{
Vector2 minPos = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); Vector2 minPos = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
Vector2 maxPos = nodes.Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero)).Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y))); Vector2 maxPos = nodes.Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero)).Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y)));
panOffset = -(minPos + (maxPos - minPos) / 2f); panOffset = -(minPos + (maxPos - minPos) / 2f);
} else { }
else
{
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);
} }
@ -401,29 +505,37 @@ 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()
{
// Get selected nodes which are part of this graph // Get selected nodes which are part of this graph
XNode.Node[] selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); XNode.Node[] selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray();
if (selectedNodes == null || selectedNodes.Length == 0) return; if (selectedNodes == null || selectedNodes.Length == 0) return;
@ -432,15 +544,18 @@ namespace XNodeEditor {
InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30)); InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30));
} }
public void CopySelectedNodes() { public void CopySelectedNodes()
{
copyBuffer = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); copyBuffer = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray();
} }
public void PasteNodes(Vector2 pos) { public void PasteNodes(Vector2 pos)
{
InsertDuplicateNodes(copyBuffer, pos); InsertDuplicateNodes(copyBuffer, pos);
} }
private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft) { private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft)
{
if (nodes == null || nodes.Length == 0) return; if (nodes == null || nodes.Length == 0) return;
// Get top-left node // Get top-left node
@ -449,14 +564,16 @@ namespace XNodeEditor {
UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.Length]; UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.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 < nodes.Length; i++) { for (int i = 0; i < nodes.Length; i++)
{
XNode.Node srcNode = nodes[i]; XNode.Node srcNode = nodes[i];
if (srcNode == null) continue; if (srcNode == null) continue;
// Check if user is allowed to add more of given node type // Check if user is allowed to add more of given node type
XNode.Node.DisallowMultipleNodesAttribute disallowAttrib; XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
Type nodeType = srcNode.GetType(); Type nodeType = srcNode.GetType();
if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib)) { if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib))
{
int typeCount = graph.nodes.Count(x => x.GetType() == nodeType); int typeCount = graph.nodes.Count(x => x.GetType() == nodeType);
if (typeCount >= disallowAttrib.max) continue; if (typeCount >= disallowAttrib.max) continue;
} }
@ -468,16 +585,20 @@ 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 < nodes.Length; i++) { for (int i = 0; i < nodes.Length; i++)
{
XNode.Node srcNode = nodes[i]; XNode.Node srcNode = nodes[i];
if (srcNode == null) continue; if (srcNode == null) continue;
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.UpdatePorts(); newNodeIn.UpdatePorts();
newNodeOut.UpdatePorts(); newNodeOut.UpdatePorts();
inputPort = newNodeIn.GetInputPort(inputPort.fieldName); inputPort = newNodeIn.GetInputPort(inputPort.fieldName);
@ -493,8 +614,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)
{
Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null); Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null);
float thickness = graphEditor.GetNoodleThickness(draggedOutput, null); float thickness = graphEditor.GetNoodleThickness(draggedOutput, null);
NoodlePath path = graphEditor.GetNoodlePath(draggedOutput, null); NoodlePath path = graphEditor.GetNoodlePath(draggedOutput, null);
@ -504,7 +627,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);
@ -512,24 +636,27 @@ namespace XNodeEditor {
DrawNoodle(gradient, path, stroke, thickness, gridPoints); DrawNoodle(gradient, path, stroke, thickness, gridPoints);
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(draggedOutput);
Color bgcol = Color.black; Color bgcol = Color.black;
Color frcol = gradient.colorKeys[0].color; Color frcol = gradient.colorKeys[0].color;
bgcol.a = 0.6f; bgcol.a = 0.6f;
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);
rect = GridToWindowRect(rect); rect = GridToWindowRect(rect);
NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol); NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol, portStyle.normal.background, portStyle.active.background);
} }
} }
} }
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);
@ -542,7 +669,8 @@ namespace XNodeEditor {
} }
/// <summary> Attempt to connect dragged output to target node </summary> /// <summary> Attempt to connect dragged output to target node </summary>
public void AutoConnect(XNode.Node node) { public void AutoConnect(XNode.Node node)
{
if (autoConnectOutput == null) return; if (autoConnectOutput == null) return;
// Find input port of same type // Find input port of same type

View File

@ -334,6 +334,8 @@ namespace XNodeEditor {
if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue; if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue;
Color portColor = graphEditor.GetPortColor(output); Color portColor = graphEditor.GetPortColor(output);
GUIStyle portStyle = graphEditor.GetPortStyle(output);
for (int k = 0; k < output.ConnectionCount; k++) { for (int k = 0; k < output.ConnectionCount; k++) {
XNode.NodePort input = output.GetConnection(k); XNode.NodePort input = output.GetConnection(k);
@ -367,11 +369,11 @@ namespace XNodeEditor {
// Draw selected reroute points with an outline // Draw selected reroute points with an outline
if (selectedReroutes.Contains(rerouteRef)) { if (selectedReroutes.Contains(rerouteRef)) {
GUI.color = NodeEditorPreferences.GetSettings().highlightColor; GUI.color = NodeEditorPreferences.GetSettings().highlightColor;
GUI.DrawTexture(rect, NodeEditorResources.dotOuter); GUI.DrawTexture(rect, portStyle.normal.background);
} }
GUI.color = portColor; GUI.color = portColor;
GUI.DrawTexture(rect, NodeEditorResources.dot); GUI.DrawTexture(rect, portStyle.active.background);
if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef); if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef);
if (rect.Contains(mousePos)) hoveredReroute = rerouteRef; if (rect.Contains(mousePos)) hoveredReroute = rerouteRef;

View File

@ -7,20 +7,24 @@ using UnityEditor;
using UnityEditorInternal; using UnityEditorInternal;
using UnityEngine; using UnityEngine;
namespace XNodeEditor { namespace XNodeEditor
{
/// <summary> xNode-specific version of <see cref="EditorGUILayout"/> </summary> /// <summary> xNode-specific version of <see cref="EditorGUILayout"/> </summary>
public static class NodeEditorGUILayout { public static class NodeEditorGUILayout
{
private static readonly Dictionary<UnityEngine.Object, Dictionary<string, ReorderableList>> reorderableListCache = new Dictionary<UnityEngine.Object, Dictionary<string, ReorderableList>>(); private static readonly Dictionary<UnityEngine.Object, Dictionary<string, ReorderableList>> reorderableListCache = new Dictionary<UnityEngine.Object, Dictionary<string, ReorderableList>>();
private static int reorderableListIndex = -1; private static int reorderableListIndex = -1;
/// <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, bool includeChildren = true, params GUILayoutOption[] options) { public static void PropertyField(SerializedProperty property, bool includeChildren = true, params GUILayoutOption[] options)
{
PropertyField(property, (GUIContent)null, includeChildren, options); PropertyField(property, (GUIContent)null, includeChildren, options);
} }
/// <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.Node node = property.serializedObject.targetObject as XNode.Node;
XNode.NodePort port = node.GetPort(property.name); XNode.NodePort port = node.GetPort(property.name);
@ -28,28 +32,33 @@ namespace XNodeEditor {
} }
/// <summary> Make a field for a serialized property. Manual node port override. </summary> /// <summary> Make a field for a serialized property. Manual node port override. </summary>
public static void PropertyField(SerializedProperty property, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { public static void PropertyField(SerializedProperty property, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options)
{
PropertyField(property, null, port, includeChildren, options); PropertyField(property, null, port, includeChildren, options);
} }
/// <summary> Make a field for a serialized property. Manual node port override. </summary> /// <summary> Make a field for a serialized property. Manual node port override. </summary>
public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options)
{
if (property == null) throw new NullReferenceException(); if (property == null) throw new NullReferenceException();
// If property is not a port, display a regular property field // If property is not a port, display a regular property field
if (port == null) EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); if (port == null) EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
else { else
{
Rect rect = new Rect(); Rect rect = new Rect();
List<PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name); List<PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name);
// If property is an input, display a regular property field and put a port handle on the left side // If property is an input, display a regular property field and put a port handle on the left side
if (port.direction == XNode.NodePort.IO.Input) { if (port.direction == XNode.NodePort.IO.Input)
{
// Get data from [Input] attribute // Get data from [Input] attribute
XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected;
XNode.Node.InputAttribute inputAttribute; XNode.Node.InputAttribute inputAttribute;
bool dynamicPortList = false; bool dynamicPortList = false;
if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute)) { if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute))
{
dynamicPortList = inputAttribute.dynamicPortList; dynamicPortList = inputAttribute.dynamicPortList;
showBacking = inputAttribute.backingValue; showBacking = inputAttribute.backingValue;
} }
@ -60,30 +69,40 @@ namespace XNodeEditor {
float spacePadding = 0; float spacePadding = 0;
string tooltip = null; string tooltip = null;
foreach (var attr in propertyAttributes) { foreach (var attr in propertyAttributes)
if (attr is SpaceAttribute) { {
if (attr is SpaceAttribute)
{
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height); if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
else spacePadding += (attr as SpaceAttribute).height; else spacePadding += (attr as SpaceAttribute).height;
} else if (attr is HeaderAttribute) { }
if (usePropertyAttributes) { else if (attr is HeaderAttribute)
{
if (usePropertyAttributes)
{
//GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs
Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it. Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it.
position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; position.yMin += EditorGUIUtility.singleLineHeight * 0.5f;
position = EditorGUI.IndentedRect(position); position = EditorGUI.IndentedRect(position);
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; }
} else if (attr is TooltipAttribute) { else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
}
else if (attr is TooltipAttribute)
{
tooltip = (attr as TooltipAttribute).tooltip; tooltip = (attr as TooltipAttribute).tooltip;
} }
} }
if (dynamicPortList) { if (dynamicPortList)
{
Type type = GetType(property); Type type = GetType(property);
XNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; XNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple;
DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType);
return; return;
} }
switch (showBacking) { switch (showBacking)
{
case XNode.Node.ShowBackingValue.Unconnected: case XNode.Node.ShowBackingValue.Unconnected:
// Display a label if port is connected // Display a label if port is connected
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip)); if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip));
@ -101,15 +120,18 @@ namespace XNodeEditor {
} }
rect = GUILayoutUtility.GetLastRect(); rect = GUILayoutUtility.GetLastRect();
float paddingLeft = NodeEditorResources.styles.inputPort.padding.left; float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left;
rect.position = rect.position - new Vector2(16 + paddingLeft, -spacePadding); rect.position = rect.position - new Vector2(16 + paddingLeft, -spacePadding);
// If property is an output, display a text label and put a port handle on the right side // If property is an output, display a text label and put a port handle on the right side
} else if (port.direction == XNode.NodePort.IO.Output) { }
else if (port.direction == XNode.NodePort.IO.Output)
{
// Get data from [Output] attribute // Get data from [Output] attribute
XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected;
XNode.Node.OutputAttribute outputAttribute; XNode.Node.OutputAttribute outputAttribute;
bool dynamicPortList = false; bool dynamicPortList = false;
if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute)) { if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute))
{
dynamicPortList = outputAttribute.dynamicPortList; dynamicPortList = outputAttribute.dynamicPortList;
showBacking = outputAttribute.backingValue; showBacking = outputAttribute.backingValue;
} }
@ -120,30 +142,40 @@ namespace XNodeEditor {
float spacePadding = 0; float spacePadding = 0;
string tooltip = null; string tooltip = null;
foreach (var attr in propertyAttributes) { foreach (var attr in propertyAttributes)
if (attr is SpaceAttribute) { {
if (attr is SpaceAttribute)
{
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height); if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
else spacePadding += (attr as SpaceAttribute).height; else spacePadding += (attr as SpaceAttribute).height;
} else if (attr is HeaderAttribute) { }
if (usePropertyAttributes) { else if (attr is HeaderAttribute)
{
if (usePropertyAttributes)
{
//GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs
Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it. Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it.
position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; position.yMin += EditorGUIUtility.singleLineHeight * 0.5f;
position = EditorGUI.IndentedRect(position); position = EditorGUI.IndentedRect(position);
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; }
} else if (attr is TooltipAttribute) { else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
}
else if (attr is TooltipAttribute)
{
tooltip = (attr as TooltipAttribute).tooltip; tooltip = (attr as TooltipAttribute).tooltip;
} }
} }
if (dynamicPortList) { if (dynamicPortList)
{
Type type = GetType(property); Type type = GetType(property);
XNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; XNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple;
DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType);
return; return;
} }
switch (showBacking) { switch (showBacking)
{
case XNode.Node.ShowBackingValue.Unconnected: case XNode.Node.ShowBackingValue.Unconnected:
// Display a label if port is connected // Display a label if port is connected
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30));
@ -161,7 +193,7 @@ namespace XNodeEditor {
} }
rect = GUILayoutUtility.GetLastRect(); rect = GUILayoutUtility.GetLastRect();
rect.width += NodeEditorResources.styles.outputPort.padding.right; rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
rect.position = rect.position + new Vector2(rect.width, spacePadding); rect.position = rect.position + new Vector2(rect.width, spacePadding);
} }
@ -169,7 +201,8 @@ namespace XNodeEditor {
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
DrawPortHandle(rect, backgroundColor, col); GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position // Register the handle position
Vector2 portPos = rect.center; Vector2 portPos = rect.center;
@ -177,54 +210,62 @@ namespace XNodeEditor {
} }
} }
private static System.Type GetType(SerializedProperty property) { private static System.Type GetType(SerializedProperty property)
{
System.Type parentType = property.serializedObject.targetObject.GetType(); System.Type parentType = property.serializedObject.targetObject.GetType();
System.Reflection.FieldInfo fi = parentType.GetFieldInfo(property.name); System.Reflection.FieldInfo fi = parentType.GetFieldInfo(property.name);
return fi.FieldType; return fi.FieldType;
} }
/// <summary> Make a simple port field. </summary> /// <summary> Make a simple port field. </summary>
public static void PortField(XNode.NodePort port, params GUILayoutOption[] options) { public static void PortField(XNode.NodePort port, params GUILayoutOption[] options)
{
PortField(null, port, options); PortField(null, port, options);
} }
/// <summary> Make a simple port field. </summary> /// <summary> Make a simple port field. </summary>
public static void PortField(GUIContent label, XNode.NodePort port, params GUILayoutOption[] options) { public static void PortField(GUIContent label, XNode.NodePort port, params GUILayoutOption[] options)
{
if (port == null) return; if (port == null) return;
if (options == null) options = new GUILayoutOption[] { GUILayout.MinWidth(30) }; if (options == null) options = new GUILayoutOption[] { GUILayout.MinWidth(30) };
Vector2 position = Vector3.zero; Vector2 position = Vector3.zero;
GUIContent content = label != null ? label : new GUIContent(ObjectNames.NicifyVariableName(port.fieldName)); GUIContent content = label != null ? label : new GUIContent(ObjectNames.NicifyVariableName(port.fieldName));
// If property is an input, display a regular property field and put a port handle on the left side // If property is an input, display a regular property field and put a port handle on the left side
if (port.direction == XNode.NodePort.IO.Input) { if (port.direction == XNode.NodePort.IO.Input)
{
// Display a label // Display a label
EditorGUILayout.LabelField(content, options); EditorGUILayout.LabelField(content, options);
Rect rect = GUILayoutUtility.GetLastRect(); Rect rect = GUILayoutUtility.GetLastRect();
float paddingLeft = NodeEditorResources.styles.inputPort.padding.left; float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left;
position = rect.position - new Vector2(16 + paddingLeft, 0); position = rect.position - new Vector2(16 + paddingLeft, 0);
} }
// If property is an output, display a text label and put a port handle on the right side // If property is an output, display a text label and put a port handle on the right side
else if (port.direction == XNode.NodePort.IO.Output) { else if (port.direction == XNode.NodePort.IO.Output)
{
// Display a label // Display a label
EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options); EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options);
Rect rect = GUILayoutUtility.GetLastRect(); Rect rect = GUILayoutUtility.GetLastRect();
rect.width += NodeEditorResources.styles.outputPort.padding.right; rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
position = rect.position + new Vector2(rect.width, 0); position = rect.position + new Vector2(rect.width, 0);
} }
PortField(position, port); PortField(position, port);
} }
/// <summary> Make a simple port field. </summary> /// <summary> Make a simple port field. </summary>
public static void PortField(Vector2 position, XNode.NodePort port) { public static void PortField(Vector2 position, XNode.NodePort port)
{
if (port == null) return; if (port == null) return;
Rect rect = new Rect(position, new Vector2(16, 16)); Rect rect = new Rect(position, new Vector2(16, 16));
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
DrawPortHandle(rect, backgroundColor, col); GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position // Register the handle position
Vector2 portPos = rect.center; Vector2 portPos = rect.center;
@ -232,19 +273,23 @@ namespace XNodeEditor {
} }
/// <summary> Add a port field to previous layout element. </summary> /// <summary> Add a port field to previous layout element. </summary>
public static void AddPortField(XNode.NodePort port) { public static void AddPortField(XNode.NodePort port)
{
if (port == null) return; if (port == null) return;
Rect rect = new Rect(); Rect rect = new Rect();
// If property is an input, display a regular property field and put a port handle on the left side // If property is an input, display a regular property field and put a port handle on the left side
if (port.direction == XNode.NodePort.IO.Input) { if (port.direction == XNode.NodePort.IO.Input)
{
rect = GUILayoutUtility.GetLastRect(); rect = GUILayoutUtility.GetLastRect();
float paddingLeft = NodeEditorResources.styles.inputPort.padding.left; float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left;
rect.position = rect.position - new Vector2(16 + paddingLeft, 0); rect.position = rect.position - new Vector2(16 + paddingLeft, 0);
// If property is an output, display a text label and put a port handle on the right side // If property is an output, display a text label and put a port handle on the right side
} else if (port.direction == XNode.NodePort.IO.Output) { }
else if (port.direction == XNode.NodePort.IO.Output)
{
rect = GUILayoutUtility.GetLastRect(); rect = GUILayoutUtility.GetLastRect();
rect.width += NodeEditorResources.styles.outputPort.padding.right; rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
rect.position = rect.position + new Vector2(rect.width, 0); rect.position = rect.position + new Vector2(rect.width, 0);
} }
@ -252,7 +297,9 @@ namespace XNodeEditor {
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
DrawPortHandle(rect, backgroundColor, col); GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position // Register the handle position
Vector2 portPos = rect.center; Vector2 portPos = rect.center;
@ -260,40 +307,55 @@ namespace XNodeEditor {
} }
/// <summary> Draws an input and an output port on the same line </summary> /// <summary> Draws an input and an output port on the same line </summary>
public static void PortPair(XNode.NodePort input, XNode.NodePort output) { public static void PortPair(XNode.NodePort input, XNode.NodePort output)
{
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
NodeEditorGUILayout.PortField(input, GUILayout.MinWidth(0)); NodeEditorGUILayout.PortField(input, GUILayout.MinWidth(0));
NodeEditorGUILayout.PortField(output, GUILayout.MinWidth(0)); NodeEditorGUILayout.PortField(output, GUILayout.MinWidth(0));
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) { /// <summary>
/// Draw the port
/// </summary>
/// <param name="rect">position and size</param>
/// <param name="backgroundColor">color for background texture of the port. Normaly used to Border</param>
/// <param name="typeColor"></param>
/// <param name="border">texture for border of the dot port</param>
/// <param name="dot">texture for the dot port</param>
public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor, Texture2D border, Texture2D dot)
{
Color col = GUI.color; Color col = GUI.color;
GUI.color = backgroundColor; GUI.color = backgroundColor;
GUI.DrawTexture(rect, NodeEditorResources.dotOuter); GUI.DrawTexture(rect, border);
GUI.color = typeColor; GUI.color = typeColor;
GUI.DrawTexture(rect, NodeEditorResources.dot); GUI.DrawTexture(rect, dot);
GUI.color = col; GUI.color = col;
} }
#region Obsolete #region Obsolete
[Obsolete("Use IsDynamicPortListPort instead")] [Obsolete("Use IsDynamicPortListPort instead")]
public static bool IsInstancePortListPort(XNode.NodePort port) { public static bool IsInstancePortListPort(XNode.NodePort port)
{
return IsDynamicPortListPort(port); return IsDynamicPortListPort(port);
} }
[Obsolete("Use DynamicPortList instead")] [Obsolete("Use DynamicPortList instead")]
public static void InstancePortList(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 InstancePortList(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)
{
DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation); DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation);
} }
#endregion #endregion
/// <summary> Is this port part of a DynamicPortList? </summary> /// <summary> Is this port part of a DynamicPortList? </summary>
public static bool IsDynamicPortListPort(XNode.NodePort port) { public static bool IsDynamicPortListPort(XNode.NodePort port)
{
string[] parts = port.fieldName.Split(' '); string[] parts = port.fieldName.Split(' ');
if (parts.Length != 2) return false; if (parts.Length != 2) return false;
Dictionary<string, ReorderableList> cache; Dictionary<string, ReorderableList> cache;
if (reorderableListCache.TryGetValue(port.node, out cache)) { if (reorderableListCache.TryGetValue(port.node, out cache))
{
ReorderableList list; ReorderableList list;
if (cache.TryGetValue(parts[0], out list)) return true; if (cache.TryGetValue(parts[0], out list)) return true;
} }
@ -306,14 +368,18 @@ namespace XNodeEditor {
/// <param name="serializedObject">The serializedObject of the node</param> /// <param name="serializedObject">The serializedObject of the node</param>
/// <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.Node node = serializedObject.targetObject as XNode.Node;
var indexedPorts = node.DynamicPorts.Select(x => { var indexedPorts = node.DynamicPorts.Select(x =>
{
string[] split = x.fieldName.Split(' '); string[] split = x.fieldName.Split(' ');
if (split != null && split.Length == 2 && split[0] == fieldName) { if (split != null && split.Length == 2 && split[0] == fieldName)
{
int i = -1; int i = -1;
if (int.TryParse(split[1], out i)) { if (int.TryParse(split[1], out i))
{
return new { index = i, port = x }; return new { index = i, port = x };
} }
} }
@ -325,11 +391,13 @@ namespace XNodeEditor {
ReorderableList list = null; ReorderableList list = null;
Dictionary<string, ReorderableList> rlc; Dictionary<string, ReorderableList> rlc;
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) { if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc))
{
if (!rlc.TryGetValue(fieldName, out list)) list = null; if (!rlc.TryGetValue(fieldName, out list)) list = null;
} }
// If a ReorderableList isn't cached for this array, do so. // If a ReorderableList isn't cached for this array, do so.
if (list == null) { if (list == null)
{
SerializedProperty arrayData = serializedObject.FindProperty(fieldName); SerializedProperty arrayData = serializedObject.FindProperty(fieldName);
list = CreateReorderableList(fieldName, dynamicPorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation); list = CreateReorderableList(fieldName, dynamicPorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation);
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list); if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list);
@ -340,53 +408,67 @@ namespace XNodeEditor {
} }
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.Node.ConnectionType connectionType, XNode.Node.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.Node node = serializedObject.targetObject as XNode.Node;
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);
list.drawElementCallback = list.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => { (Rect rect, int index, bool isActive, bool isFocused) =>
{
XNode.NodePort port = node.GetPort(fieldName + " " + index); XNode.NodePort port = node.GetPort(fieldName + " " + index);
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) { if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String)
if (arrayData.arraySize <= index) { {
if (arrayData.arraySize <= index)
{
EditorGUI.LabelField(rect, "Array[" + index + "] data out of range"); EditorGUI.LabelField(rect, "Array[" + index + "] data out of range");
return; return;
} }
SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index);
EditorGUI.PropertyField(rect, itemData, true); EditorGUI.PropertyField(rect, itemData, true);
} else EditorGUI.LabelField(rect, port != null ? port.fieldName : ""); }
if (port != null) { else EditorGUI.LabelField(rect, port != null ? port.fieldName : "");
if (port != null)
{
Vector2 pos = rect.position + (port.IsOutput ? new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); Vector2 pos = rect.position + (port.IsOutput ? new Vector2(rect.width + 6, 0) : new Vector2(-36, 0));
NodeEditorGUILayout.PortField(pos, port); NodeEditorGUILayout.PortField(pos, port);
} }
}; };
list.elementHeightCallback = list.elementHeightCallback =
(int index) => { (int index) =>
if (hasArrayData) { {
if (hasArrayData)
{
if (arrayData.arraySize <= index) return EditorGUIUtility.singleLineHeight; if (arrayData.arraySize <= index) return EditorGUIUtility.singleLineHeight;
SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index);
return EditorGUI.GetPropertyHeight(itemData); return EditorGUI.GetPropertyHeight(itemData);
} else return EditorGUIUtility.singleLineHeight; }
else return EditorGUIUtility.singleLineHeight;
}; };
list.drawHeaderCallback = list.drawHeaderCallback =
(Rect rect) => { (Rect rect) =>
{
EditorGUI.LabelField(rect, label); EditorGUI.LabelField(rect, label);
}; };
list.onSelectCallback = list.onSelectCallback =
(ReorderableList rl) => { (ReorderableList rl) =>
{
reorderableListIndex = rl.index; reorderableListIndex = rl.index;
}; };
list.onReorderCallback = list.onReorderCallback =
(ReorderableList rl) => { (ReorderableList rl) =>
{
bool hasRect = false; bool hasRect = false;
bool hasNewRect = false; bool hasNewRect = false;
Rect rect = Rect.zero; Rect rect = Rect.zero;
Rect newRect = Rect.zero; Rect newRect = Rect.zero;
// Move up // Move up
if (rl.index > reorderableListIndex) { if (rl.index > reorderableListIndex)
for (int i = reorderableListIndex; i < rl.index; ++i) { {
for (int i = reorderableListIndex; i < rl.index; ++i)
{
XNode.NodePort port = node.GetPort(fieldName + " " + i); XNode.NodePort port = node.GetPort(fieldName + " " + i);
XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i + 1)); XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i + 1));
port.SwapConnections(nextPort); port.SwapConnections(nextPort);
@ -399,8 +481,10 @@ namespace XNodeEditor {
} }
} }
// Move down // Move down
else { else
for (int i = reorderableListIndex; i > rl.index; --i) { {
for (int i = reorderableListIndex; i > rl.index; --i)
{
XNode.NodePort port = node.GetPort(fieldName + " " + i); XNode.NodePort port = node.GetPort(fieldName + " " + i);
XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i - 1)); XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i - 1));
port.SwapConnections(nextPort); port.SwapConnections(nextPort);
@ -417,7 +501,8 @@ namespace XNodeEditor {
serializedObject.Update(); serializedObject.Update();
// Move array data if there is any // Move array data if there is any
if (hasArrayData) { if (hasArrayData)
{
arrayData.MoveArrayElement(reorderableListIndex, rl.index); arrayData.MoveArrayElement(reorderableListIndex, rl.index);
} }
@ -428,7 +513,8 @@ namespace XNodeEditor {
EditorApplication.delayCall += NodeEditorWindow.current.Repaint; EditorApplication.delayCall += NodeEditorWindow.current.Repaint;
}; };
list.onAddCallback = list.onAddCallback =
(ReorderableList rl) => { (ReorderableList rl) =>
{
// Add dynamic port postfixed with an index number // Add dynamic port postfixed with an index number
string newName = fieldName + " 0"; string newName = fieldName + " 0";
int i = 0; int i = 0;
@ -438,19 +524,24 @@ namespace XNodeEditor {
else node.AddDynamicInput(type, connectionType, typeConstraint, newName); else node.AddDynamicInput(type, connectionType, typeConstraint, newName);
serializedObject.Update(); serializedObject.Update();
EditorUtility.SetDirty(node); EditorUtility.SetDirty(node);
if (hasArrayData) { if (hasArrayData)
{
arrayData.InsertArrayElementAtIndex(arrayData.arraySize); arrayData.InsertArrayElementAtIndex(arrayData.arraySize);
} }
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
}; };
list.onRemoveCallback = list.onRemoveCallback =
(ReorderableList rl) => { (ReorderableList rl) =>
{
var indexedPorts = node.DynamicPorts.Select(x => { var indexedPorts = node.DynamicPorts.Select(x =>
{
string[] split = x.fieldName.Split(' '); string[] split = x.fieldName.Split(' ');
if (split != null && split.Length == 2 && split[0] == fieldName) { if (split != null && split.Length == 2 && split[0] == fieldName)
{
int i = -1; int i = -1;
if (int.TryParse(split[1], out i)) { if (int.TryParse(split[1], out i))
{
return new { index = i, port = x }; return new { index = i, port = x };
} }
} }
@ -460,17 +551,24 @@ namespace XNodeEditor {
int index = rl.index; int index = rl.index;
if (dynamicPorts[index] == null) { if (dynamicPorts[index] == null)
{
Debug.LogWarning("No port found at index " + index + " - Skipped"); Debug.LogWarning("No port found at index " + index + " - Skipped");
} else if (dynamicPorts.Count <= index) { }
else if (dynamicPorts.Count <= index)
{
Debug.LogWarning("DynamicPorts[" + index + "] out of range. Length was " + dynamicPorts.Count + " - Skipped"); Debug.LogWarning("DynamicPorts[" + index + "] out of range. Length was " + dynamicPorts.Count + " - Skipped");
} else { }
else
{
// Clear the removed ports connections // Clear the removed ports connections
dynamicPorts[index].ClearConnections(); dynamicPorts[index].ClearConnections();
// Move following connections one step up to replace the missing connection // Move following connections one step up to replace the missing connection
for (int k = index + 1; k < dynamicPorts.Count(); k++) { for (int k = index + 1; k < dynamicPorts.Count(); k++)
for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++) { {
for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++)
{
XNode.NodePort other = dynamicPorts[k].GetConnection(j); XNode.NodePort other = dynamicPorts[k].GetConnection(j);
dynamicPorts[k].Disconnect(other); dynamicPorts[k].Disconnect(other);
dynamicPorts[k - 1].Connect(other); dynamicPorts[k - 1].Connect(other);
@ -482,16 +580,20 @@ namespace XNodeEditor {
EditorUtility.SetDirty(node); EditorUtility.SetDirty(node);
} }
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) { if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String)
if (arrayData.arraySize <= index) { {
if (arrayData.arraySize <= index)
{
Debug.LogWarning("Attempted to remove array index " + index + " where only " + arrayData.arraySize + " exist - Skipped"); Debug.LogWarning("Attempted to remove array index " + index + " where only " + arrayData.arraySize + " exist - Skipped");
Debug.Log(rl.list[0]); Debug.Log(rl.list[0]);
return; return;
} }
arrayData.DeleteArrayElementAtIndex(index); arrayData.DeleteArrayElementAtIndex(index);
// Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues // Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues
if (dynamicPorts.Count <= arrayData.arraySize) { if (dynamicPorts.Count <= arrayData.arraySize)
while (dynamicPorts.Count <= arrayData.arraySize) { {
while (dynamicPorts.Count <= arrayData.arraySize)
{
arrayData.DeleteArrayElementAtIndex(arrayData.arraySize - 1); arrayData.DeleteArrayElementAtIndex(arrayData.arraySize - 1);
} }
UnityEngine.Debug.LogWarning("Array size exceeded dynamic ports size. Excess items removed."); UnityEngine.Debug.LogWarning("Array size exceeded dynamic ports size. Excess items removed.");
@ -501,9 +603,11 @@ namespace XNodeEditor {
} }
}; };
if (hasArrayData) { if (hasArrayData)
{
int dynamicPortCount = dynamicPorts.Count; int dynamicPortCount = dynamicPorts.Count;
while (dynamicPortCount < arrayData.arraySize) { while (dynamicPortCount < arrayData.arraySize)
{
// Add dynamic port postfixed with an index number // Add dynamic port postfixed with an index number
string newName = arrayData.name + " 0"; string newName = arrayData.name + " 0";
int i = 0; int i = 0;
@ -513,7 +617,8 @@ namespace XNodeEditor {
EditorUtility.SetDirty(node); EditorUtility.SetDirty(node);
dynamicPortCount++; dynamicPortCount++;
} }
while (arrayData.arraySize < dynamicPortCount) { while (arrayData.arraySize < dynamicPortCount)
{
arrayData.InsertArrayElementAtIndex(arrayData.arraySize); arrayData.InsertArrayElementAtIndex(arrayData.arraySize);
} }
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();

View File

@ -27,10 +27,14 @@ namespace XNodeEditor {
inputPort = new GUIStyle(baseStyle); inputPort = new GUIStyle(baseStyle);
inputPort.alignment = TextAnchor.UpperLeft; inputPort.alignment = TextAnchor.UpperLeft;
inputPort.padding.left = 0; inputPort.padding.left = 0;
inputPort.active.background = dot;
inputPort.normal.background = dotOuter;
outputPort = new GUIStyle(baseStyle); outputPort = new GUIStyle(baseStyle);
outputPort.alignment = TextAnchor.UpperRight; outputPort.alignment = TextAnchor.UpperRight;
outputPort.padding.right = 0; outputPort.padding.right = 0;
outputPort.active.background = dot;
outputPort.normal.background = dotOuter;
nodeHeader = new GUIStyle(); nodeHeader = new GUIStyle();
nodeHeader.alignment = TextAnchor.MiddleCenter; nodeHeader.alignment = TextAnchor.MiddleCenter;

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>
@ -24,21 +26,25 @@ namespace XNodeEditor {
/// <summary> Called when NodeEditorWindow loses focus </summary> /// <summary> Called when NodeEditorWindow loses focus </summary>
public virtual void OnWindowFocusLost() { } public virtual void OnWindowFocusLost() { }
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
@ -48,7 +54,8 @@ namespace XNodeEditor {
} }
/// <summary> The order by which the menu items are displayed. </summary> /// <summary> The order by which the menu items are displayed. </summary>
public virtual int GetNodeMenuOrder(Type type) { public virtual int GetNodeMenuOrder(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
@ -71,14 +78,16 @@ namespace XNodeEditor {
// Check if user is allowed to add more of given node type // Check if user is allowed to add more of given node type
XNode.Node.DisallowMultipleNodesAttribute disallowAttrib; XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
bool disallowed = false; bool disallowed = false;
if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib)) { if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib))
{
int typeCount = target.nodes.Count(x => x.GetType() == type); int typeCount = target.nodes.Count(x => x.GetType() == type);
if (typeCount >= disallowAttrib.max) disallowed = true; if (typeCount >= disallowAttrib.max) disallowed = true;
} }
// Add node entry to context menu // Add node entry to context menu
if (disallowed) menu.AddItem(new GUIContent(path), false, null); if (disallowed) menu.AddItem(new GUIContent(path), false, null);
else menu.AddItem(new GUIContent(path), false, () => { else menu.AddItem(new GUIContent(path), false, () =>
{
XNode.Node node = CreateNode(type, pos); XNode.Node node = CreateNode(type, pos);
NodeEditorWindow.current.AutoConnect(node); NodeEditorWindow.current.AutoConnect(node);
}); });
@ -93,11 +102,13 @@ namespace XNodeEditor {
/// <summary> Returned gradient is used to color noodles </summary> /// <summary> Returned gradient is used to color noodles </summary>
/// <param name="output"> The output this noodle comes from. Never null. </param> /// <param name="output"> The output this noodle comes from. Never null. </param>
/// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param> /// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param>
public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input) { public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input)
{
Gradient grad = new Gradient(); Gradient grad = new Gradient();
// If dragging the noodle, draw solid, slightly transparent // If dragging the noodle, draw solid, slightly transparent
if (input == null) { if (input == null)
{
Color a = GetTypeColor(output.ValueType); Color a = GetTypeColor(output.ValueType);
grad.SetKeys( grad.SetKeys(
new GradientColorKey[] { new GradientColorKey(a, 0f) }, new GradientColorKey[] { new GradientColorKey(a, 0f) },
@ -105,11 +116,13 @@ namespace XNodeEditor {
); );
} }
// If normal, draw gradient fading from one input color to the other // If normal, draw gradient fading from one input color to the other
else { else
{
Color a = GetTypeColor(output.ValueType); Color a = GetTypeColor(output.ValueType);
Color b = GetTypeColor(input.ValueType); Color b = GetTypeColor(input.ValueType);
// If any port is hovered, tint white // If any port is hovered, tint white
if (window.hoveredPort == output || window.hoveredPort == input) { if (window.hoveredPort == output || window.hoveredPort == input)
{
a = Color.Lerp(a, Color.white, 0.8f); a = Color.Lerp(a, Color.white, 0.8f);
b = Color.Lerp(b, Color.white, 0.8f); b = Color.Lerp(b, Color.white, 0.8f);
} }
@ -124,40 +137,66 @@ namespace XNodeEditor {
/// <summary> Returned float is used for noodle thickness </summary> /// <summary> Returned float is used for noodle thickness </summary>
/// <param name="output"> The output this noodle comes from. Never null. </param> /// <param name="output"> The output this noodle comes from. Never null. </param>
/// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param> /// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param>
public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input) { public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input)
{
return NodeEditorPreferences.GetSettings().noodleThickness; return NodeEditorPreferences.GetSettings().noodleThickness;
} }
public virtual NoodlePath GetNoodlePath(XNode.NodePort output, XNode.NodePort input) { public virtual NoodlePath GetNoodlePath(XNode.NodePort output, XNode.NodePort input)
{
return NodeEditorPreferences.GetSettings().noodlePath; return NodeEditorPreferences.GetSettings().noodlePath;
} }
public virtual NoodleStroke GetNoodleStroke(XNode.NodePort output, XNode.NodePort input) { public virtual NoodleStroke GetNoodleStroke(XNode.NodePort output, XNode.NodePort input)
{
return NodeEditorPreferences.GetSettings().noodleStroke; return NodeEditorPreferences.GetSettings().noodleStroke;
} }
/// <summary> Returned color is used to color ports </summary> /// <summary> Returned color is used to color ports </summary>
public virtual Color GetPortColor(XNode.NodePort port) { public virtual Color GetPortColor(XNode.NodePort port)
{
return GetTypeColor(port.ValueType); return GetTypeColor(port.ValueType);
} }
/// <summary>
/// The returned Style is used to configure the paddings and icon texture of the ports.
/// Use these properties to customize your port style.
///
/// The properties used is:
/// <see cref="GUIStyle.padding"/>[Left and Right], <see cref="GUIStyle.normal"/> [Background] = border texture,
/// and <seealso cref="GUIStyle.active"/> [Background] = dot texture;
/// </summary>
/// <param name="port">the owner of the style</param>
/// <returns></returns>
public virtual GUIStyle GetPortStyle(XNode.NodePort port)
{
if (port.direction == XNode.NodePort.IO.Input)
return NodeEditorResources.styles.inputPort;
return NodeEditorResources.styles.outputPort;
}
/// <summary> The returned color is used to color the background of the door. /// <summary> The returned color is used to color the background of the door.
/// Usually used for outer edge effect </summary> /// Usually used for outer edge effect </summary>
public virtual Color GetPortBackgroundColor(XNode.NodePort port) { public virtual Color GetPortBackgroundColor(XNode.NodePort port)
{
return Color.gray; return Color.gray;
} }
/// <summary> Returns generated color for a type. This color is editable in preferences </summary> /// <summary> Returns generated color for a type. This color is editable in preferences </summary>
public virtual Color GetTypeColor(Type type) { public virtual Color GetTypeColor(Type type)
{
return NodeEditorPreferences.GetTypeColor(type); return NodeEditorPreferences.GetTypeColor(type);
} }
/// <summary> Override to display custom tooltips </summary> /// <summary> Override to display custom tooltips </summary>
public virtual string GetPortTooltip(XNode.NodePort port) { public virtual string GetPortTooltip(XNode.NodePort port)
{
Type portType = port.ValueType; Type portType = port.ValueType;
string tooltip = ""; string tooltip = "";
tooltip = portType.PrettyName(); tooltip = portType.PrettyName();
if (port.IsOutput) { if (port.IsOutput)
{
object obj = port.node.GetValue(port); object obj = port.node.GetValue(port);
tooltip += " = " + (obj != null ? obj.ToString() : "null"); tooltip += " = " + (obj != null ? obj.ToString() : "null");
} }
@ -165,12 +204,14 @@ namespace XNodeEditor {
} }
/// <summary> Deal with objects dropped into the graph through DragAndDrop </summary> /// <summary> Deal with objects dropped into the graph through DragAndDrop </summary>
public virtual void OnDropObjects(UnityEngine.Object[] objects) { public virtual void OnDropObjects(UnityEngine.Object[] objects)
{
if (GetType() != typeof(NodeGraphEditor)) Debug.Log("No OnDropObjects override defined for " + GetType()); if (GetType() != typeof(NodeGraphEditor)) Debug.Log("No OnDropObjects override defined for " + GetType());
} }
/// <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 XNode.Node CreateNode(Type type, Vector2 position) { public virtual XNode.Node CreateNode(Type type, Vector2 position)
{
Undo.RecordObject(target, "Create Node"); Undo.RecordObject(target, "Create Node");
XNode.Node node = target.AddNode(type); XNode.Node node = target.AddNode(type);
Undo.RegisterCreatedObjectUndo(node, "Create Node"); Undo.RegisterCreatedObjectUndo(node, "Create Node");
@ -183,7 +224,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 virtual XNode.Node CopyNode(XNode.Node original) { public virtual XNode.Node CopyNode(XNode.Node original)
{
Undo.RecordObject(target, "Duplicate Node"); Undo.RecordObject(target, "Duplicate Node");
XNode.Node node = target.CopyNode(original); XNode.Node node = target.CopyNode(original);
Undo.RegisterCreatedObjectUndo(node, "Duplicate Node"); Undo.RegisterCreatedObjectUndo(node, "Duplicate Node");
@ -194,13 +236,16 @@ namespace XNodeEditor {
} }
/// <summary> Return false for nodes that can't be removed </summary> /// <summary> Return false for nodes that can't be removed </summary>
public virtual bool CanRemove(XNode.Node node) { public virtual bool CanRemove(XNode.Node node)
{
// Check graph attributes to see if this node is required // Check graph attributes to see if this node is required
Type graphType = target.GetType(); Type graphType = target.GetType();
XNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll( XNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll(
graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute); graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute);
if (attribs.Any(x => x.Requires(node.GetType()))) { if (attribs.Any(x => x.Requires(node.GetType())))
if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) { {
if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1)
{
return false; return false;
} }
} }
@ -208,7 +253,8 @@ 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)
{
if (!CanRemove(node)) return; if (!CanRemove(node)) return;
// Remove the node // Remove the node
@ -224,18 +270,21 @@ namespace XNodeEditor {
[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;
} }
} }