mirror of
https://github.com/Siccity/xNode.git
synced 2025-12-20 09:16:01 +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:
parent
e3127a9135
commit
fdd9b24eca
@ -5,8 +5,10 @@ using UnityEditor;
|
||||
using UnityEngine;
|
||||
using XNodeEditor.Internal;
|
||||
|
||||
namespace XNodeEditor {
|
||||
public partial class NodeEditorWindow {
|
||||
namespace XNodeEditor
|
||||
{
|
||||
public partial class NodeEditorWindow
|
||||
{
|
||||
public enum NodeActivity { Idle, HoldNode, DragNode, HoldGrid, DragGrid }
|
||||
public static NodeActivity currentActivity = NodeActivity.Idle;
|
||||
public static bool isPanning { get; private set; }
|
||||
@ -43,14 +45,17 @@ namespace XNodeEditor {
|
||||
private Vector2 lastMousePosition;
|
||||
private float dragThreshold = 1f;
|
||||
|
||||
public void Controls() {
|
||||
public void Controls()
|
||||
{
|
||||
wantsMouseMove = true;
|
||||
Event e = Event.current;
|
||||
switch (e.type) {
|
||||
switch (e.type)
|
||||
{
|
||||
case EventType.DragUpdated:
|
||||
case EventType.DragPerform:
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
|
||||
if (e.type == EventType.DragPerform) {
|
||||
if (e.type == EventType.DragPerform)
|
||||
{
|
||||
DragAndDrop.AcceptDrag();
|
||||
graphEditor.OnDropObjects(DragAndDrop.objectReferences);
|
||||
}
|
||||
@ -66,52 +71,68 @@ namespace XNodeEditor {
|
||||
if (NodeEditorPreferences.GetSettings().zoomToMouse) panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset);
|
||||
break;
|
||||
case EventType.MouseDrag:
|
||||
if (e.button == 0) {
|
||||
if (IsDraggingPort) {
|
||||
if (e.button == 0)
|
||||
{
|
||||
if (IsDraggingPort)
|
||||
{
|
||||
// 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;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
draggedOutputTarget = null;
|
||||
}
|
||||
Repaint();
|
||||
} else if (currentActivity == NodeActivity.HoldNode) {
|
||||
}
|
||||
else if (currentActivity == NodeActivity.HoldNode)
|
||||
{
|
||||
RecalculateDragOffsets(e);
|
||||
currentActivity = NodeActivity.DragNode;
|
||||
Repaint();
|
||||
}
|
||||
if (currentActivity == NodeActivity.DragNode) {
|
||||
if (currentActivity == NodeActivity.DragNode)
|
||||
{
|
||||
// Holding ctrl inverts grid snap
|
||||
bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap;
|
||||
if (e.control) gridSnap = !gridSnap;
|
||||
|
||||
Vector2 mousePos = WindowToGridPosition(e.mousePosition);
|
||||
// Move selected nodes with offset
|
||||
for (int i = 0; i < Selection.objects.Length; i++) {
|
||||
if (Selection.objects[i] is XNode.Node) {
|
||||
for (int i = 0; i < Selection.objects.Length; i++)
|
||||
{
|
||||
if (Selection.objects[i] is XNode.Node)
|
||||
{
|
||||
XNode.Node node = Selection.objects[i] as XNode.Node;
|
||||
Undo.RecordObject(node, "Moved Node");
|
||||
Vector2 initial = node.position;
|
||||
node.position = mousePos + dragOffset[i];
|
||||
if (gridSnap) {
|
||||
if (gridSnap)
|
||||
{
|
||||
node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8;
|
||||
node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8;
|
||||
}
|
||||
|
||||
// Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame.
|
||||
Vector2 offset = node.position - initial;
|
||||
if (offset.sqrMagnitude > 0) {
|
||||
foreach (XNode.NodePort output in node.Outputs) {
|
||||
if (offset.sqrMagnitude > 0)
|
||||
{
|
||||
foreach (XNode.NodePort output in node.Outputs)
|
||||
{
|
||||
Rect rect;
|
||||
if (portConnectionPoints.TryGetValue(output, out rect)) {
|
||||
if (portConnectionPoints.TryGetValue(output, out rect))
|
||||
{
|
||||
rect.position += offset;
|
||||
portConnectionPoints[output] = rect;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XNode.NodePort input in node.Inputs) {
|
||||
foreach (XNode.NodePort input in node.Inputs)
|
||||
{
|
||||
Rect rect;
|
||||
if (portConnectionPoints.TryGetValue(input, out rect)) {
|
||||
if (portConnectionPoints.TryGetValue(input, out rect))
|
||||
{
|
||||
rect.position += offset;
|
||||
portConnectionPoints[input] = rect;
|
||||
}
|
||||
@ -120,22 +141,28 @@ namespace XNodeEditor {
|
||||
}
|
||||
}
|
||||
// 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];
|
||||
if (gridSnap) {
|
||||
if (gridSnap)
|
||||
{
|
||||
pos.x = (Mathf.Round(pos.x / 16) * 16);
|
||||
pos.y = (Mathf.Round(pos.y / 16) * 16);
|
||||
}
|
||||
selectedReroutes[i].SetPoint(pos);
|
||||
}
|
||||
Repaint();
|
||||
} else if (currentActivity == NodeActivity.HoldGrid) {
|
||||
}
|
||||
else if (currentActivity == NodeActivity.HoldGrid)
|
||||
{
|
||||
currentActivity = NodeActivity.DragGrid;
|
||||
preBoxSelection = Selection.objects;
|
||||
preBoxSelectionReroute = selectedReroutes.ToArray();
|
||||
dragBoxStart = WindowToGridPosition(e.mousePosition);
|
||||
Repaint();
|
||||
} else if (currentActivity == NodeActivity.DragGrid) {
|
||||
}
|
||||
else if (currentActivity == NodeActivity.DragGrid)
|
||||
{
|
||||
Vector2 boxStartPos = GridToWindowPosition(dragBoxStart);
|
||||
Vector2 boxSize = e.mousePosition - boxStartPos;
|
||||
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);
|
||||
Repaint();
|
||||
}
|
||||
} else if (e.button == 1 || e.button == 2) {
|
||||
}
|
||||
else if (e.button == 1 || e.button == 2)
|
||||
{
|
||||
//check drag threshold for larger screens
|
||||
if (e.delta.magnitude > dragThreshold) {
|
||||
if (e.delta.magnitude > dragThreshold)
|
||||
{
|
||||
panOffset += e.delta * zoom;
|
||||
isPanning = true;
|
||||
}
|
||||
@ -153,17 +183,23 @@ namespace XNodeEditor {
|
||||
break;
|
||||
case EventType.MouseDown:
|
||||
Repaint();
|
||||
if (e.button == 0) {
|
||||
if (e.button == 0)
|
||||
{
|
||||
draggedOutputReroutes.Clear();
|
||||
|
||||
if (IsHoveringPort) {
|
||||
if (hoveredPort.IsOutput) {
|
||||
if (IsHoveringPort)
|
||||
{
|
||||
if (hoveredPort.IsOutput)
|
||||
{
|
||||
draggedOutput = hoveredPort;
|
||||
autoConnectOutput = hoveredPort;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
hoveredPort.VerifyConnections();
|
||||
autoConnectOutput = null;
|
||||
if (hoveredPort.IsConnected) {
|
||||
if (hoveredPort.IsConnected)
|
||||
{
|
||||
XNode.Node node = hoveredPort.node;
|
||||
XNode.NodePort output = hoveredPort.Connection;
|
||||
int outputConnectionIndex = output.GetConnectionIndex(hoveredPort);
|
||||
@ -174,25 +210,33 @@ namespace XNodeEditor {
|
||||
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 (!Selection.Contains(hoveredNode)) {
|
||||
if (!Selection.Contains(hoveredNode))
|
||||
{
|
||||
SelectNode(hoveredNode, e.control || e.shift);
|
||||
if (!e.control && !e.shift) selectedReroutes.Clear();
|
||||
} else if (e.control || e.shift) DeselectNode(hoveredNode);
|
||||
}
|
||||
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.
|
||||
isDoubleClick = (e.clickCount == 2);
|
||||
|
||||
e.Use();
|
||||
currentActivity = NodeActivity.HoldNode;
|
||||
} else if (IsHoveringReroute) {
|
||||
}
|
||||
else if (IsHoveringReroute)
|
||||
{
|
||||
// If reroute isn't selected
|
||||
if (!selectedReroutes.Contains(hoveredReroute)) {
|
||||
if (!selectedReroutes.Contains(hoveredReroute))
|
||||
{
|
||||
// Add it
|
||||
if (e.control || e.shift) selectedReroutes.Add(hoveredReroute);
|
||||
// Select it
|
||||
else {
|
||||
else
|
||||
{
|
||||
selectedReroutes = new List<RerouteReference>() { hoveredReroute };
|
||||
Selection.activeObject = null;
|
||||
}
|
||||
@ -204,9 +248,11 @@ namespace XNodeEditor {
|
||||
currentActivity = NodeActivity.HoldNode;
|
||||
}
|
||||
// If mousedown on grid background, deselect all
|
||||
else if (!IsHoveringNode) {
|
||||
else if (!IsHoveringNode)
|
||||
{
|
||||
currentActivity = NodeActivity.HoldGrid;
|
||||
if (!e.control && !e.shift) {
|
||||
if (!e.control && !e.shift)
|
||||
{
|
||||
selectedReroutes.Clear();
|
||||
Selection.activeObject = null;
|
||||
}
|
||||
@ -214,24 +260,29 @@ namespace XNodeEditor {
|
||||
}
|
||||
break;
|
||||
case EventType.MouseUp:
|
||||
if (e.button == 0) {
|
||||
if (e.button == 0)
|
||||
{
|
||||
//Port drag release
|
||||
if (IsDraggingPort) {
|
||||
if (IsDraggingPort)
|
||||
{
|
||||
// If connection is valid, save it
|
||||
if (draggedOutputTarget != null && draggedOutput.CanConnectTo(draggedOutputTarget)) {
|
||||
if (draggedOutputTarget != null && draggedOutput.CanConnectTo(draggedOutputTarget))
|
||||
{
|
||||
XNode.Node node = draggedOutputTarget.node;
|
||||
if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget);
|
||||
|
||||
// ConnectionIndex can be -1 if the connection is removed instantly after creation
|
||||
int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget);
|
||||
if (connectionIndex != -1) {
|
||||
if (connectionIndex != -1)
|
||||
{
|
||||
draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes);
|
||||
if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
|
||||
EditorUtility.SetDirty(graph);
|
||||
}
|
||||
}
|
||||
// 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();
|
||||
graphEditor.AddContextMenuItems(menu);
|
||||
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
|
||||
@ -241,13 +292,18 @@ namespace XNodeEditor {
|
||||
draggedOutputTarget = null;
|
||||
EditorUtility.SetDirty(graph);
|
||||
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);
|
||||
foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node);
|
||||
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
|
||||
} else if (!IsHoveringNode) {
|
||||
}
|
||||
else if (!IsHoveringNode)
|
||||
{
|
||||
// If click outside node, release field focus
|
||||
if (!isPanning) {
|
||||
if (!isPanning)
|
||||
{
|
||||
EditorGUI.FocusTextInControl(null);
|
||||
EditorGUIUtility.editingTextField = false;
|
||||
}
|
||||
@ -255,44 +311,61 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
// If click node header, select it.
|
||||
if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift)) {
|
||||
if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift))
|
||||
{
|
||||
selectedReroutes.Clear();
|
||||
SelectNode(hoveredNode, false);
|
||||
|
||||
// Double click to center node
|
||||
if (isDoubleClick) {
|
||||
if (isDoubleClick)
|
||||
{
|
||||
Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero;
|
||||
panOffset = -hoveredNode.position - nodeDimension;
|
||||
}
|
||||
}
|
||||
|
||||
// If click reroute, select it.
|
||||
if (IsHoveringReroute && !(e.control || e.shift)) {
|
||||
if (IsHoveringReroute && !(e.control || e.shift))
|
||||
{
|
||||
selectedReroutes = new List<RerouteReference>() { hoveredReroute };
|
||||
Selection.activeObject = null;
|
||||
}
|
||||
|
||||
Repaint();
|
||||
currentActivity = NodeActivity.Idle;
|
||||
} else if (e.button == 1 || e.button == 2) {
|
||||
if (!isPanning) {
|
||||
if (IsDraggingPort) {
|
||||
}
|
||||
else if (e.button == 1 || e.button == 2)
|
||||
{
|
||||
if (!isPanning)
|
||||
{
|
||||
if (IsDraggingPort)
|
||||
{
|
||||
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] = new RerouteReference(selectedReroutes[0].port, selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1);
|
||||
} else if (IsHoveringReroute) {
|
||||
}
|
||||
else if (IsHoveringReroute)
|
||||
{
|
||||
ShowRerouteContextMenu(hoveredReroute);
|
||||
} else if (IsHoveringPort) {
|
||||
}
|
||||
else if (IsHoveringPort)
|
||||
{
|
||||
ShowPortContextMenu(hoveredPort);
|
||||
} else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
|
||||
}
|
||||
else if (IsHoveringNode && IsHoveringTitle(hoveredNode))
|
||||
{
|
||||
if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false);
|
||||
autoConnectOutput = null;
|
||||
GenericMenu menu = new GenericMenu();
|
||||
NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu);
|
||||
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.
|
||||
} else if (!IsHoveringNode) {
|
||||
}
|
||||
else if (!IsHoveringNode)
|
||||
{
|
||||
autoConnectOutput = null;
|
||||
GenericMenu menu = new GenericMenu();
|
||||
graphEditor.AddContextMenuItems(menu);
|
||||
@ -307,18 +380,27 @@ namespace XNodeEditor {
|
||||
case EventType.KeyDown:
|
||||
if (EditorGUIUtility.editingTextField) break;
|
||||
else if (e.keyCode == KeyCode.F) Home();
|
||||
if (NodeEditorUtilities.IsMac()) {
|
||||
if (NodeEditorUtilities.IsMac())
|
||||
{
|
||||
if (e.keyCode == KeyCode.Return) RenameSelectedNode();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (e.keyCode == KeyCode.F2) RenameSelectedNode();
|
||||
}
|
||||
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 (e.keyCode == KeyCode.A)
|
||||
{
|
||||
if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node)))
|
||||
{
|
||||
foreach (XNode.Node node in graph.nodes)
|
||||
{
|
||||
DeselectNode(node);
|
||||
}
|
||||
} else {
|
||||
foreach (XNode.Node node in graph.nodes) {
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (XNode.Node node in graph.nodes)
|
||||
{
|
||||
SelectNode(node, true);
|
||||
}
|
||||
}
|
||||
@ -327,19 +409,28 @@ namespace XNodeEditor {
|
||||
break;
|
||||
case EventType.ValidateCommand:
|
||||
case EventType.ExecuteCommand:
|
||||
if (e.commandName == "SoftDelete") {
|
||||
if (e.commandName == "SoftDelete")
|
||||
{
|
||||
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
|
||||
e.Use();
|
||||
} else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete") {
|
||||
}
|
||||
else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete")
|
||||
{
|
||||
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
|
||||
e.Use();
|
||||
} else if (e.commandName == "Duplicate") {
|
||||
}
|
||||
else if (e.commandName == "Duplicate")
|
||||
{
|
||||
if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes();
|
||||
e.Use();
|
||||
} else if (e.commandName == "Copy") {
|
||||
}
|
||||
else if (e.commandName == "Copy")
|
||||
{
|
||||
if (e.type == EventType.ExecuteCommand) CopySelectedNodes();
|
||||
e.Use();
|
||||
} else if (e.commandName == "Paste") {
|
||||
}
|
||||
else if (e.commandName == "Paste")
|
||||
{
|
||||
if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition));
|
||||
e.Use();
|
||||
}
|
||||
@ -347,7 +438,8 @@ namespace XNodeEditor {
|
||||
break;
|
||||
case EventType.Ignore:
|
||||
// If release mouse outside window
|
||||
if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid) {
|
||||
if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid)
|
||||
{
|
||||
Repaint();
|
||||
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];
|
||||
// Selected nodes
|
||||
for (int i = 0; i < Selection.objects.Length; i++) {
|
||||
if (Selection.objects[i] is XNode.Node) {
|
||||
for (int i = 0; i < Selection.objects.Length; i++)
|
||||
{
|
||||
if (Selection.objects[i] is XNode.Node)
|
||||
{
|
||||
XNode.Node node = Selection.objects[i] as XNode.Node;
|
||||
dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition);
|
||||
}
|
||||
}
|
||||
|
||||
// Selected reroutes
|
||||
for (int i = 0; i < selectedReroutes.Count; i++) {
|
||||
for (int i = 0; i < selectedReroutes.Count; i++)
|
||||
{
|
||||
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>
|
||||
public void Home() {
|
||||
public void Home()
|
||||
{
|
||||
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 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);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
zoom = 2;
|
||||
panOffset = Vector2.zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
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.Clear();
|
||||
foreach (UnityEngine.Object item in Selection.objects) {
|
||||
if (item is XNode.Node) {
|
||||
foreach (UnityEngine.Object item in Selection.objects)
|
||||
{
|
||||
if (item is XNode.Node)
|
||||
{
|
||||
XNode.Node node = item as XNode.Node;
|
||||
graphEditor.RemoveNode(node);
|
||||
}
|
||||
@ -401,29 +505,37 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
/// <summary> Initiate a rename on the currently selected node </summary>
|
||||
public void RenameSelectedNode() {
|
||||
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
|
||||
public void RenameSelectedNode()
|
||||
{
|
||||
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node)
|
||||
{
|
||||
XNode.Node node = Selection.activeObject as XNode.Node;
|
||||
Vector2 size;
|
||||
if (nodeSizes.TryGetValue(node, out size)) {
|
||||
if (nodeSizes.TryGetValue(node, out size))
|
||||
{
|
||||
RenamePopup.Show(Selection.activeObject, size.x);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
RenamePopup.Show(Selection.activeObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
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 + 1] = node;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Duplicate selected nodes and select the duplicates </summary>
|
||||
public void DuplicateSelectedNodes() {
|
||||
public void DuplicateSelectedNodes()
|
||||
{
|
||||
// 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();
|
||||
if (selectedNodes == null || selectedNodes.Length == 0) return;
|
||||
@ -432,15 +544,18 @@ namespace XNodeEditor {
|
||||
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();
|
||||
}
|
||||
|
||||
public void PasteNodes(Vector2 pos) {
|
||||
public void PasteNodes(Vector2 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;
|
||||
|
||||
// Get top-left node
|
||||
@ -449,14 +564,16 @@ namespace XNodeEditor {
|
||||
|
||||
UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.Length];
|
||||
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];
|
||||
if (srcNode == null) continue;
|
||||
|
||||
// Check if user is allowed to add more of given node type
|
||||
XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
|
||||
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);
|
||||
if (typeCount >= disallowAttrib.max) continue;
|
||||
}
|
||||
@ -468,16 +585,20 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
// 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];
|
||||
if (srcNode == null) continue;
|
||||
foreach (XNode.NodePort port in srcNode.Ports) {
|
||||
for (int c = 0; c < port.ConnectionCount; c++) {
|
||||
foreach (XNode.NodePort port in srcNode.Ports)
|
||||
{
|
||||
for (int c = 0; c < port.ConnectionCount; 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.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();
|
||||
newNodeOut.UpdatePorts();
|
||||
inputPort = newNodeIn.GetInputPort(inputPort.fieldName);
|
||||
@ -493,8 +614,10 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
/// <summary> Draw a connection as we are dragging it </summary>
|
||||
public void DrawDraggedConnection() {
|
||||
if (IsDraggingPort) {
|
||||
public void DrawDraggedConnection()
|
||||
{
|
||||
if (IsDraggingPort)
|
||||
{
|
||||
Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null);
|
||||
float thickness = graphEditor.GetNoodleThickness(draggedOutput, null);
|
||||
NoodlePath path = graphEditor.GetNoodlePath(draggedOutput, null);
|
||||
@ -504,7 +627,8 @@ namespace XNodeEditor {
|
||||
if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return;
|
||||
List<Vector2> gridPoints = new List<Vector2>();
|
||||
gridPoints.Add(fromRect.center);
|
||||
for (int i = 0; i < draggedOutputReroutes.Count; i++) {
|
||||
for (int i = 0; i < draggedOutputReroutes.Count; i++)
|
||||
{
|
||||
gridPoints.Add(draggedOutputReroutes[i]);
|
||||
}
|
||||
if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center);
|
||||
@ -512,24 +636,27 @@ namespace XNodeEditor {
|
||||
|
||||
DrawNoodle(gradient, path, stroke, thickness, gridPoints);
|
||||
|
||||
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(draggedOutput);
|
||||
Color bgcol = Color.black;
|
||||
Color frcol = gradient.colorKeys[0].color;
|
||||
bgcol.a = 0.6f;
|
||||
frcol.a = 0.6f;
|
||||
|
||||
// Loop through reroute points again and draw the points
|
||||
for (int i = 0; i < draggedOutputReroutes.Count; i++) {
|
||||
for (int i = 0; i < draggedOutputReroutes.Count; i++)
|
||||
{
|
||||
// Draw reroute point at position
|
||||
Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16));
|
||||
rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8);
|
||||
rect = GridToWindowRect(rect);
|
||||
|
||||
NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol);
|
||||
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;
|
||||
//Get node position
|
||||
Vector2 nodePos = GridToWindowPosition(node.position);
|
||||
@ -542,7 +669,8 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
// Find input port of same type
|
||||
|
||||
@ -334,6 +334,8 @@ namespace XNodeEditor {
|
||||
if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue;
|
||||
|
||||
Color portColor = graphEditor.GetPortColor(output);
|
||||
GUIStyle portStyle = graphEditor.GetPortStyle(output);
|
||||
|
||||
for (int k = 0; k < output.ConnectionCount; k++) {
|
||||
XNode.NodePort input = output.GetConnection(k);
|
||||
|
||||
@ -367,11 +369,11 @@ namespace XNodeEditor {
|
||||
// Draw selected reroute points with an outline
|
||||
if (selectedReroutes.Contains(rerouteRef)) {
|
||||
GUI.color = NodeEditorPreferences.GetSettings().highlightColor;
|
||||
GUI.DrawTexture(rect, NodeEditorResources.dotOuter);
|
||||
GUI.DrawTexture(rect, portStyle.normal.background);
|
||||
}
|
||||
|
||||
GUI.color = portColor;
|
||||
GUI.DrawTexture(rect, NodeEditorResources.dot);
|
||||
GUI.DrawTexture(rect, portStyle.active.background);
|
||||
if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef);
|
||||
if (rect.Contains(mousePos)) hoveredReroute = rerouteRef;
|
||||
|
||||
|
||||
@ -7,20 +7,24 @@ using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XNodeEditor {
|
||||
namespace XNodeEditor
|
||||
{
|
||||
/// <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 int reorderableListIndex = -1;
|
||||
|
||||
/// <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) {
|
||||
PropertyField(property, (GUIContent) null, includeChildren, options);
|
||||
public static void PropertyField(SerializedProperty property, bool includeChildren = true, params GUILayoutOption[] options)
|
||||
{
|
||||
PropertyField(property, (GUIContent)null, includeChildren, options);
|
||||
}
|
||||
|
||||
/// <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();
|
||||
XNode.Node node = property.serializedObject.targetObject as XNode.Node;
|
||||
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>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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 is not a port, display a regular property field
|
||||
if (port == null) EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
|
||||
else {
|
||||
else
|
||||
{
|
||||
Rect rect = new Rect();
|
||||
|
||||
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 (port.direction == XNode.NodePort.IO.Input) {
|
||||
if (port.direction == XNode.NodePort.IO.Input)
|
||||
{
|
||||
// Get data from [Input] attribute
|
||||
XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected;
|
||||
XNode.Node.InputAttribute inputAttribute;
|
||||
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;
|
||||
showBacking = inputAttribute.backingValue;
|
||||
}
|
||||
@ -60,30 +69,40 @@ namespace XNodeEditor {
|
||||
|
||||
float spacePadding = 0;
|
||||
string tooltip = null;
|
||||
foreach (var attr in propertyAttributes) {
|
||||
if (attr is SpaceAttribute) {
|
||||
foreach (var attr in propertyAttributes)
|
||||
{
|
||||
if (attr is SpaceAttribute)
|
||||
{
|
||||
if (usePropertyAttributes) GUILayout.Space((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
|
||||
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 = EditorGUI.IndentedRect(position);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamicPortList) {
|
||||
if (dynamicPortList)
|
||||
{
|
||||
Type type = GetType(property);
|
||||
XNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple;
|
||||
DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType);
|
||||
return;
|
||||
}
|
||||
switch (showBacking) {
|
||||
switch (showBacking)
|
||||
{
|
||||
case XNode.Node.ShowBackingValue.Unconnected:
|
||||
// Display a label if port is connected
|
||||
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip));
|
||||
@ -101,15 +120,18 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
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);
|
||||
// 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
|
||||
XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected;
|
||||
XNode.Node.OutputAttribute outputAttribute;
|
||||
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;
|
||||
showBacking = outputAttribute.backingValue;
|
||||
}
|
||||
@ -120,30 +142,40 @@ namespace XNodeEditor {
|
||||
|
||||
float spacePadding = 0;
|
||||
string tooltip = null;
|
||||
foreach (var attr in propertyAttributes) {
|
||||
if (attr is SpaceAttribute) {
|
||||
foreach (var attr in propertyAttributes)
|
||||
{
|
||||
if (attr is SpaceAttribute)
|
||||
{
|
||||
if (usePropertyAttributes) GUILayout.Space((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
|
||||
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 = EditorGUI.IndentedRect(position);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamicPortList) {
|
||||
if (dynamicPortList)
|
||||
{
|
||||
Type type = GetType(property);
|
||||
XNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple;
|
||||
DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType);
|
||||
return;
|
||||
}
|
||||
switch (showBacking) {
|
||||
switch (showBacking)
|
||||
{
|
||||
case XNode.Node.ShowBackingValue.Unconnected:
|
||||
// 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));
|
||||
@ -161,7 +193,7 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -169,7 +201,8 @@ namespace XNodeEditor {
|
||||
|
||||
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(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
|
||||
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.Reflection.FieldInfo fi = parentType.GetFieldInfo(property.name);
|
||||
return fi.FieldType;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <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 (options == null) options = new GUILayoutOption[] { GUILayout.MinWidth(30) };
|
||||
Vector2 position = Vector3.zero;
|
||||
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 (port.direction == XNode.NodePort.IO.Input) {
|
||||
if (port.direction == XNode.NodePort.IO.Input)
|
||||
{
|
||||
// Display a label
|
||||
EditorGUILayout.LabelField(content, options);
|
||||
|
||||
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);
|
||||
}
|
||||
// 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
|
||||
EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options);
|
||||
|
||||
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);
|
||||
}
|
||||
PortField(position, port);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
Rect rect = new Rect(position, new Vector2(16, 16));
|
||||
|
||||
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(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
|
||||
Vector2 portPos = rect.center;
|
||||
@ -232,19 +273,23 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
/// <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;
|
||||
Rect rect = new Rect();
|
||||
|
||||
// If property is an input, display a regular property field and put a port handle on the left side
|
||||
if (port.direction == XNode.NodePort.IO.Input) {
|
||||
if (port.direction == XNode.NodePort.IO.Input)
|
||||
{
|
||||
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);
|
||||
// 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.width += NodeEditorResources.styles.outputPort.padding.right;
|
||||
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
|
||||
rect.position = rect.position + new Vector2(rect.width, 0);
|
||||
}
|
||||
|
||||
@ -252,7 +297,9 @@ namespace XNodeEditor {
|
||||
|
||||
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(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
|
||||
Vector2 portPos = rect.center;
|
||||
@ -260,40 +307,55 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
/// <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();
|
||||
NodeEditorGUILayout.PortField(input, GUILayout.MinWidth(0));
|
||||
NodeEditorGUILayout.PortField(output, GUILayout.MinWidth(0));
|
||||
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;
|
||||
GUI.color = backgroundColor;
|
||||
GUI.DrawTexture(rect, NodeEditorResources.dotOuter);
|
||||
GUI.DrawTexture(rect, border);
|
||||
GUI.color = typeColor;
|
||||
GUI.DrawTexture(rect, NodeEditorResources.dot);
|
||||
GUI.DrawTexture(rect, dot);
|
||||
GUI.color = col;
|
||||
}
|
||||
|
||||
#region Obsolete
|
||||
|
||||
#region Obsolete
|
||||
[Obsolete("Use IsDynamicPortListPort instead")]
|
||||
public static bool IsInstancePortListPort(XNode.NodePort port) {
|
||||
public static bool IsInstancePortListPort(XNode.NodePort port)
|
||||
{
|
||||
return IsDynamicPortListPort(port);
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
/// <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(' ');
|
||||
if (parts.Length != 2) return false;
|
||||
Dictionary<string, ReorderableList> cache;
|
||||
if (reorderableListCache.TryGetValue(port.node, out cache)) {
|
||||
if (reorderableListCache.TryGetValue(port.node, out cache))
|
||||
{
|
||||
ReorderableList list;
|
||||
if (cache.TryGetValue(parts[0], out list)) return true;
|
||||
}
|
||||
@ -306,18 +368,22 @@ namespace XNodeEditor {
|
||||
/// <param name="serializedObject">The serializedObject of the node</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>
|
||||
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;
|
||||
|
||||
var indexedPorts = node.DynamicPorts.Select(x => {
|
||||
var indexedPorts = node.DynamicPorts.Select(x =>
|
||||
{
|
||||
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;
|
||||
if (int.TryParse(split[1], out i)) {
|
||||
if (int.TryParse(split[1], out i))
|
||||
{
|
||||
return new { index = i, port = x };
|
||||
}
|
||||
}
|
||||
return new { index = -1, port = (XNode.NodePort) null };
|
||||
return new { index = -1, port = (XNode.NodePort)null };
|
||||
}).Where(x => x.port != null);
|
||||
List<XNode.NodePort> dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
|
||||
|
||||
@ -325,11 +391,13 @@ namespace XNodeEditor {
|
||||
|
||||
ReorderableList list = null;
|
||||
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 a ReorderableList isn't cached for this array, do so.
|
||||
if (list == null) {
|
||||
if (list == null)
|
||||
{
|
||||
SerializedProperty arrayData = serializedObject.FindProperty(fieldName);
|
||||
list = CreateReorderableList(fieldName, dynamicPorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation);
|
||||
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;
|
||||
XNode.Node node = serializedObject.targetObject as XNode.Node;
|
||||
ReorderableList list = new ReorderableList(dynamicPorts, null, true, true, true, true);
|
||||
string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName);
|
||||
|
||||
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);
|
||||
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) {
|
||||
if (arrayData.arraySize <= index) {
|
||||
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String)
|
||||
{
|
||||
if (arrayData.arraySize <= index)
|
||||
{
|
||||
EditorGUI.LabelField(rect, "Array[" + index + "] data out of range");
|
||||
return;
|
||||
}
|
||||
SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index);
|
||||
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));
|
||||
NodeEditorGUILayout.PortField(pos, port);
|
||||
}
|
||||
};
|
||||
list.elementHeightCallback =
|
||||
(int index) => {
|
||||
if (hasArrayData) {
|
||||
(int index) =>
|
||||
{
|
||||
if (hasArrayData)
|
||||
{
|
||||
if (arrayData.arraySize <= index) return EditorGUIUtility.singleLineHeight;
|
||||
SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index);
|
||||
return EditorGUI.GetPropertyHeight(itemData);
|
||||
} else return EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
else return EditorGUIUtility.singleLineHeight;
|
||||
};
|
||||
list.drawHeaderCallback =
|
||||
(Rect rect) => {
|
||||
(Rect rect) =>
|
||||
{
|
||||
EditorGUI.LabelField(rect, label);
|
||||
};
|
||||
list.onSelectCallback =
|
||||
(ReorderableList rl) => {
|
||||
(ReorderableList rl) =>
|
||||
{
|
||||
reorderableListIndex = rl.index;
|
||||
};
|
||||
list.onReorderCallback =
|
||||
(ReorderableList rl) => {
|
||||
(ReorderableList rl) =>
|
||||
{
|
||||
bool hasRect = false;
|
||||
bool hasNewRect = false;
|
||||
Rect rect = Rect.zero;
|
||||
Rect newRect = Rect.zero;
|
||||
// Move up
|
||||
if (rl.index > reorderableListIndex) {
|
||||
for (int i = reorderableListIndex; i < rl.index; ++i) {
|
||||
if (rl.index > reorderableListIndex)
|
||||
{
|
||||
for (int i = reorderableListIndex; i < rl.index; ++i)
|
||||
{
|
||||
XNode.NodePort port = node.GetPort(fieldName + " " + i);
|
||||
XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i + 1));
|
||||
port.SwapConnections(nextPort);
|
||||
@ -399,8 +481,10 @@ namespace XNodeEditor {
|
||||
}
|
||||
}
|
||||
// Move down
|
||||
else {
|
||||
for (int i = reorderableListIndex; i > rl.index; --i) {
|
||||
else
|
||||
{
|
||||
for (int i = reorderableListIndex; i > rl.index; --i)
|
||||
{
|
||||
XNode.NodePort port = node.GetPort(fieldName + " " + i);
|
||||
XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i - 1));
|
||||
port.SwapConnections(nextPort);
|
||||
@ -417,7 +501,8 @@ namespace XNodeEditor {
|
||||
serializedObject.Update();
|
||||
|
||||
// Move array data if there is any
|
||||
if (hasArrayData) {
|
||||
if (hasArrayData)
|
||||
{
|
||||
arrayData.MoveArrayElement(reorderableListIndex, rl.index);
|
||||
}
|
||||
|
||||
@ -428,7 +513,8 @@ namespace XNodeEditor {
|
||||
EditorApplication.delayCall += NodeEditorWindow.current.Repaint;
|
||||
};
|
||||
list.onAddCallback =
|
||||
(ReorderableList rl) => {
|
||||
(ReorderableList rl) =>
|
||||
{
|
||||
// Add dynamic port postfixed with an index number
|
||||
string newName = fieldName + " 0";
|
||||
int i = 0;
|
||||
@ -438,39 +524,51 @@ namespace XNodeEditor {
|
||||
else node.AddDynamicInput(type, connectionType, typeConstraint, newName);
|
||||
serializedObject.Update();
|
||||
EditorUtility.SetDirty(node);
|
||||
if (hasArrayData) {
|
||||
if (hasArrayData)
|
||||
{
|
||||
arrayData.InsertArrayElementAtIndex(arrayData.arraySize);
|
||||
}
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
};
|
||||
list.onRemoveCallback =
|
||||
(ReorderableList rl) => {
|
||||
(ReorderableList rl) =>
|
||||
{
|
||||
|
||||
var indexedPorts = node.DynamicPorts.Select(x => {
|
||||
var indexedPorts = node.DynamicPorts.Select(x =>
|
||||
{
|
||||
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;
|
||||
if (int.TryParse(split[1], out i)) {
|
||||
if (int.TryParse(split[1], out i))
|
||||
{
|
||||
return new { index = i, port = x };
|
||||
}
|
||||
}
|
||||
return new { index = -1, port = (XNode.NodePort) null };
|
||||
return new { index = -1, port = (XNode.NodePort)null };
|
||||
}).Where(x => x.port != null);
|
||||
dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
|
||||
|
||||
int index = rl.index;
|
||||
|
||||
if (dynamicPorts[index] == null) {
|
||||
if (dynamicPorts[index] == null)
|
||||
{
|
||||
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");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Clear the removed ports connections
|
||||
dynamicPorts[index].ClearConnections();
|
||||
// Move following connections one step up to replace the missing connection
|
||||
for (int k = index + 1; k < dynamicPorts.Count(); k++) {
|
||||
for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++) {
|
||||
for (int k = index + 1; k < dynamicPorts.Count(); k++)
|
||||
{
|
||||
for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++)
|
||||
{
|
||||
XNode.NodePort other = dynamicPorts[k].GetConnection(j);
|
||||
dynamicPorts[k].Disconnect(other);
|
||||
dynamicPorts[k - 1].Connect(other);
|
||||
@ -482,16 +580,20 @@ namespace XNodeEditor {
|
||||
EditorUtility.SetDirty(node);
|
||||
}
|
||||
|
||||
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) {
|
||||
if (arrayData.arraySize <= index) {
|
||||
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String)
|
||||
{
|
||||
if (arrayData.arraySize <= index)
|
||||
{
|
||||
Debug.LogWarning("Attempted to remove array index " + index + " where only " + arrayData.arraySize + " exist - Skipped");
|
||||
Debug.Log(rl.list[0]);
|
||||
return;
|
||||
}
|
||||
arrayData.DeleteArrayElementAtIndex(index);
|
||||
// Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues
|
||||
if (dynamicPorts.Count <= arrayData.arraySize) {
|
||||
while (dynamicPorts.Count <= arrayData.arraySize) {
|
||||
if (dynamicPorts.Count <= arrayData.arraySize)
|
||||
{
|
||||
while (dynamicPorts.Count <= arrayData.arraySize)
|
||||
{
|
||||
arrayData.DeleteArrayElementAtIndex(arrayData.arraySize - 1);
|
||||
}
|
||||
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;
|
||||
while (dynamicPortCount < arrayData.arraySize) {
|
||||
while (dynamicPortCount < arrayData.arraySize)
|
||||
{
|
||||
// Add dynamic port postfixed with an index number
|
||||
string newName = arrayData.name + " 0";
|
||||
int i = 0;
|
||||
@ -513,7 +617,8 @@ namespace XNodeEditor {
|
||||
EditorUtility.SetDirty(node);
|
||||
dynamicPortCount++;
|
||||
}
|
||||
while (arrayData.arraySize < dynamicPortCount) {
|
||||
while (arrayData.arraySize < dynamicPortCount)
|
||||
{
|
||||
arrayData.InsertArrayElementAtIndex(arrayData.arraySize);
|
||||
}
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
@ -27,10 +27,14 @@ namespace XNodeEditor {
|
||||
inputPort = new GUIStyle(baseStyle);
|
||||
inputPort.alignment = TextAnchor.UpperLeft;
|
||||
inputPort.padding.left = 0;
|
||||
inputPort.active.background = dot;
|
||||
inputPort.normal.background = dotOuter;
|
||||
|
||||
outputPort = new GUIStyle(baseStyle);
|
||||
outputPort.alignment = TextAnchor.UpperRight;
|
||||
outputPort.padding.right = 0;
|
||||
outputPort.active.background = dot;
|
||||
outputPort.normal.background = dotOuter;
|
||||
|
||||
nodeHeader = new GUIStyle();
|
||||
nodeHeader.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
@ -4,10 +4,12 @@ using System.Linq;
|
||||
using UnityEditor;
|
||||
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>
|
||||
[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")]
|
||||
public Rect position { get { return window.position; } set { window.position = value; } }
|
||||
/// <summary> Are we currently renaming a node? </summary>
|
||||
@ -24,21 +26,25 @@ namespace XNodeEditor {
|
||||
/// <summary> Called when NodeEditorWindow loses focus </summary>
|
||||
public virtual void OnWindowFocusLost() { }
|
||||
|
||||
public virtual Texture2D GetGridTexture() {
|
||||
public virtual Texture2D GetGridTexture()
|
||||
{
|
||||
return NodeEditorPreferences.GetSettings().gridTexture;
|
||||
}
|
||||
|
||||
public virtual Texture2D GetSecondaryGridTexture() {
|
||||
public virtual Texture2D GetSecondaryGridTexture()
|
||||
{
|
||||
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>
|
||||
public virtual NodeEditorPreferences.Settings GetDefaultPreferences() {
|
||||
public virtual NodeEditorPreferences.Settings GetDefaultPreferences()
|
||||
{
|
||||
return new NodeEditorPreferences.Settings();
|
||||
}
|
||||
|
||||
/// <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
|
||||
XNode.Node.CreateNodeMenuAttribute attrib;
|
||||
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>
|
||||
public virtual int GetNodeMenuOrder(Type type) {
|
||||
public virtual int GetNodeMenuOrder(Type type)
|
||||
{
|
||||
//Check if type has the CreateNodeMenuAttribute
|
||||
XNode.Node.CreateNodeMenuAttribute attrib;
|
||||
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
|
||||
XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
|
||||
bool disallowed = false;
|
||||
if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib)) {
|
||||
if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib))
|
||||
{
|
||||
int typeCount = target.nodes.Count(x => x.GetType() == type);
|
||||
if (typeCount >= disallowAttrib.max) disallowed = true;
|
||||
}
|
||||
|
||||
// Add node entry to context menu
|
||||
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);
|
||||
NodeEditorWindow.current.AutoConnect(node);
|
||||
});
|
||||
@ -93,11 +102,13 @@ namespace XNodeEditor {
|
||||
/// <summary> Returned gradient is used to color noodles </summary>
|
||||
/// <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>
|
||||
public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input) {
|
||||
public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input)
|
||||
{
|
||||
Gradient grad = new Gradient();
|
||||
|
||||
// If dragging the noodle, draw solid, slightly transparent
|
||||
if (input == null) {
|
||||
if (input == null)
|
||||
{
|
||||
Color a = GetTypeColor(output.ValueType);
|
||||
grad.SetKeys(
|
||||
new GradientColorKey[] { new GradientColorKey(a, 0f) },
|
||||
@ -105,11 +116,13 @@ namespace XNodeEditor {
|
||||
);
|
||||
}
|
||||
// If normal, draw gradient fading from one input color to the other
|
||||
else {
|
||||
else
|
||||
{
|
||||
Color a = GetTypeColor(output.ValueType);
|
||||
Color b = GetTypeColor(input.ValueType);
|
||||
// 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);
|
||||
b = Color.Lerp(b, Color.white, 0.8f);
|
||||
}
|
||||
@ -124,40 +137,66 @@ namespace XNodeEditor {
|
||||
/// <summary> Returned float is used for noodle thickness </summary>
|
||||
/// <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>
|
||||
public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input) {
|
||||
public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public virtual NoodleStroke GetNoodleStroke(XNode.NodePort output, XNode.NodePort input) {
|
||||
public virtual NoodleStroke GetNoodleStroke(XNode.NodePort output, XNode.NodePort input)
|
||||
{
|
||||
return NodeEditorPreferences.GetSettings().noodleStroke;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// Usually used for outer edge effect </summary>
|
||||
public virtual Color GetPortBackgroundColor(XNode.NodePort port) {
|
||||
public virtual Color GetPortBackgroundColor(XNode.NodePort port)
|
||||
{
|
||||
return Color.gray;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
string tooltip = "";
|
||||
tooltip = portType.PrettyName();
|
||||
if (port.IsOutput) {
|
||||
if (port.IsOutput)
|
||||
{
|
||||
object obj = port.node.GetValue(port);
|
||||
tooltip += " = " + (obj != null ? obj.ToString() : "null");
|
||||
}
|
||||
@ -165,12 +204,14 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
/// <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());
|
||||
}
|
||||
|
||||
/// <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");
|
||||
XNode.Node node = target.AddNode(type);
|
||||
Undo.RegisterCreatedObjectUndo(node, "Create Node");
|
||||
@ -183,7 +224,8 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
/// <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");
|
||||
XNode.Node node = target.CopyNode(original);
|
||||
Undo.RegisterCreatedObjectUndo(node, "Duplicate Node");
|
||||
@ -194,13 +236,16 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
/// <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
|
||||
Type graphType = target.GetType();
|
||||
XNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll(
|
||||
graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute);
|
||||
if (attribs.Any(x => x.Requires(node.GetType()))) {
|
||||
if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) {
|
||||
if (attribs.Any(x => x.Requires(node.GetType())))
|
||||
{
|
||||
if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -208,7 +253,8 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
// Remove the node
|
||||
@ -224,18 +270,21 @@ namespace XNodeEditor {
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
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;
|
||||
public string editorPrefsKey;
|
||||
/// <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="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.editorPrefsKey = editorPrefsKey;
|
||||
}
|
||||
|
||||
public Type GetInspectedType() {
|
||||
public Type GetInspectedType()
|
||||
{
|
||||
return inspectedType;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user