1
0
mirror of https://github.com/Siccity/xNode.git synced 2025-12-20 09:16:01 +08:00
xNode/Scripts/Editor/NodeEditor.cs
2023-10-08 23:22:25 +01:00

296 lines
9.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using XNode;
using XNodeEditor.Internal;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
#endif
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
#endif
namespace XNodeEditor
{
/// <summary> Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. </summary>
[CustomNodeEditor(typeof(Node))]
public class NodeEditor : NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, Node>
{
/// <summary> Fires every whenever a node was modified through the editor </summary>
public static Action<Node> onUpdateNode;
public static readonly Dictionary<NodePort, Vector2> portPositions = new Dictionary<NodePort, Vector2>();
#if ODIN_INSPECTOR
protected internal static bool inNodeEditor = false;
#endif
public virtual void OnHeaderGUI()
{
GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeaderLabel);
}
/// <summary> Draws standard field editors for all public fields </summary>
public virtual void OnBodyGUI()
{
#if ODIN_INSPECTOR
inNodeEditor = true;
#endif
// Unity specifically requires this to save/update any serial object.
// serializedObject.Update(); must go at the start of an inspector gui, and
// serializedObject.ApplyModifiedProperties(); goes at the end.
serializedObject.Update();
string[] excludes = { "m_Script", "graph", "position", "ports" };
#if ODIN_INSPECTOR
try
{
#if ODIN_INSPECTOR_3
objectTree.BeginDraw(true);
#else
InspectorUtilities.BeginDrawPropertyTree(objectTree, true);
#endif
}
catch (ArgumentNullException)
{
#if ODIN_INSPECTOR_3
objectTree.EndDraw();
#else
InspectorUtilities.EndDrawPropertyTree(objectTree);
#endif
NodeEditor.DestroyEditor(this.target);
return;
}
GUIHelper.PushLabelWidth(84);
objectTree.Draw(true);
#if ODIN_INSPECTOR_3
objectTree.EndDraw();
#else
InspectorUtilities.EndDrawPropertyTree(objectTree);
#endif
GUIHelper.PopLabelWidth();
#else
// Iterate through serialized properties and draw them like the Inspector (But with ports)
SerializedProperty iterator = serializedObject.GetIterator();
bool enterChildren = true;
while (iterator.NextVisible(enterChildren))
{
enterChildren = false;
if (excludes.Contains(iterator.name))
{
continue;
}
NodeEditorGUILayout.PropertyField(iterator);
}
#endif
// Iterate through dynamic ports and draw them in the order in which they are serialized
foreach (NodePort dynamicPort in target.DynamicPorts)
{
if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort))
{
continue;
}
NodeEditorGUILayout.PortField(dynamicPort);
}
serializedObject.ApplyModifiedProperties();
#if ODIN_INSPECTOR
// Call repaint so that the graph window elements respond properly to layout changes coming from Odin
if (GUIHelper.RepaintRequested)
{
GUIHelper.ClearRepaintRequest();
window.Repaint();
}
#endif
#if ODIN_INSPECTOR
inNodeEditor = false;
#endif
}
/// <summary>
/// Called after rendering. Used for additional controls specific to a node.
/// </summary>
public virtual void OnControlsGUI() {}
public virtual int GetWidth()
{
Type type = target.GetType();
int width;
if (type.TryGetAttributeWidth(out width))
{
return width;
}
return 208;
}
/// <summary> Returns tint for target node </summary>
public virtual Color GetTint()
{
// Try get color from [NodeTint] attribute
Type type = target.GetType();
Color color;
if (type.TryGetAttributeTint(out color))
{
return color;
}
// Return default color (grey)
return NodeEditorPreferences.GetSettings().tintColor;
}
/// <summary> Returns header color for target node </summary>
public virtual Color GetHeaderColor()
{
// Try get color from [NodeColorHeader] attribute
Type type = target.GetType();
Color color;
if (type.TryGetAttributeHeader(out color))
{
return color;
}
// Return default color (grey)
return NodeEditorPreferences.GetSettings().bgHeaderColor;
}
/// <summary> Returns body color for target node </summary>
public virtual Color GetBodyColor()
{
// Try get color from [NodeColorBody] attribute
Type type = target.GetType();
Color color;
if (type.TryGetAttributeBody(out color))
{
return color;
}
// Return default color (grey)
return NodeEditorPreferences.GetSettings().bgBodyColor;
}
public virtual GUIStyle GetHeaderStyle()
{
return NodeEditorResources.styles.nodeHeader;
}
public virtual GUIStyle GetPortsStyle()
{
return NodeEditorResources.styles.nodePorts;
}
public virtual GUIStyle GetBodyStyle()
{
return NodeEditorResources.styles.nodeBody;
}
public virtual RectOffset GetBodyPadding()
{
return NodeEditorResources.styles.nodeBodyPadding.padding;
}
public virtual GUIStyle GetHeaderLabelStyle()
{
return NodeEditorResources.styles.nodeHeaderLabel;
}
public virtual GUIStyle GetBodyHighlightStyle()
{
return NodeEditorResources.styles.nodeHighlight;
}
/// <summary> Override to display custom node header tooltips </summary>
public virtual string GetHeaderTooltip()
{
return null;
}
/// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary>
public virtual void AddContextMenuItems(GenericMenu menu)
{
bool canRemove = true;
// Actions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is Node)
{
Node node = Selection.activeObject as Node;
menu.AddItem(new GUIContent("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node));
menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode);
canRemove = NodeGraphEditor.GetEditor(node.graph, NodeEditorWindow.current).CanRemove(node);
}
// Add actions to any number of selected nodes
menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes);
menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes);
if (canRemove)
{
menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes);
}
else
{
menu.AddItem(new GUIContent("Remove"), false, null);
}
// Custom sctions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is Node)
{
Node node = Selection.activeObject as Node;
menu.AddCustomContextMenuItems(node);
}
}
/// <summary> Rename the node asset. This will trigger a reimport of the node. </summary>
public void Rename(string newName)
{
if (newName == null || newName.Trim() == "")
{
newName = NodeEditorUtilities.NodeDefaultName(target.GetType());
}
target.name = newName;
OnRename();
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
}
/// <summary> Called when entering into rename mode for this node. </summary>
public virtual void OnRenameActive() {}
/// <summary> Called after this node's name has changed. </summary>
public virtual void OnRename() {}
/// <summary> Called when exiting rename mode for this node. </summary>
public virtual void OnRenameDeactive() {}
[AttributeUsage(AttributeTargets.Class)]
public class CustomNodeEditorAttribute : Attribute,
INodeEditorAttrib
{
private readonly Type inspectedType;
/// <summary> Tells a NodeEditor which Node type it is an editor for </summary>
/// <param name="inspectedType">Type that this editor can edit</param>
public CustomNodeEditorAttribute(Type inspectedType)
{
this.inspectedType = inspectedType;
}
public Type GetInspectedType()
{
return inspectedType;
}
}
}
}