1
0
mirror of https://github.com/Siccity/xNode.git synced 2025-12-21 01:36:03 +08:00
xNode/Scripts/Editor/NodeEditorReflection.cs
Emre Dogan 7ec429ba77 Changed node rendering. Header and Body (and optionally ports) sections are separate images. Allows more customizability.
Updated Node Editor Preferences window. Better separators. Added additional appearance options.
Removed Rounding in GridToWindowPositionNoClipped() (and DrawGrid()) which was originally meant to fix UI Sharpness. But it did not have impact in my case and without it made panning feel much smoother.
2023-10-08 07:05:11 +01:00

278 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using XNode;
using Object = UnityEngine.Object;
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
#endif
namespace XNodeEditor
{
/// <summary> Contains reflection-related extensions built for xNode </summary>
public static class NodeEditorReflection
{
[NonSerialized] private static Dictionary<Type, Color> nodeTint;
[NonSerialized] private static Dictionary<Type, Color> nodeHeaderColor;
[NonSerialized] private static Dictionary<Type, Color> nodeBodyColor;
[NonSerialized] private static Dictionary<Type, int> nodeWidth;
/// <summary> All available node types </summary>
public static Type[] nodeTypes => _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes();
[NonSerialized] private static Type[] _nodeTypes;
/// <summary> Return a delegate used to determine whether window is docked or not. It is faster to cache this delegate than run the reflection required each time. </summary>
public static Func<bool> GetIsDockedDelegate(this EditorWindow window)
{
BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance |
BindingFlags.Static;
MethodInfo isDockedMethod = typeof(EditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true);
return (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), window, isDockedMethod);
}
public static Type[] GetNodeTypes()
{
//Get all classes deriving from Node via reflection
return GetDerivedTypes(typeof(Node));
}
/// <summary> Custom node tint colors defined with [NodeTint(r, g, b)] </summary>
public static bool TryGetAttributeTint(this Type nodeType, out Color tint)
{
if (nodeTint == null)
{
CacheAttributes<Color, Node.NodeTintAttribute>(ref nodeTint, x => x.color);
}
return nodeTint.TryGetValue(nodeType, out tint);
}
/// <summary> Custom node header colors defined with [NodeColorHeader(r, g, b)] </summary>
public static bool TryGetAttributeHeader(this Type nodeType, out Color headerColor)
{
if (nodeHeaderColor == null)
{
CacheAttributes<Color, Node.NodeColorHeaderAttribute>(ref nodeHeaderColor, x => x.color);
}
return nodeHeaderColor.TryGetValue(nodeType, out headerColor);
}
/// <summary> Custom node body colors defined with [NodeColorBody(r, g, b)] </summary>
public static bool TryGetAttributeBody(this Type nodeType, out Color bodyColor)
{
if (nodeBodyColor == null)
{
CacheAttributes<Color, Node.NodeColorBodyAttribute>(ref nodeBodyColor, x => x.color);
}
return nodeBodyColor.TryGetValue(nodeType, out bodyColor);
}
/// <summary> Get custom node widths defined with [NodeWidth(width)] </summary>
public static bool TryGetAttributeWidth(this Type nodeType, out int width)
{
if (nodeWidth == null)
{
CacheAttributes<int, Node.NodeWidthAttribute>(ref nodeWidth, x => x.width);
}
return nodeWidth.TryGetValue(nodeType, out width);
}
private static void CacheAttributes<V, A>(ref Dictionary<Type, V> dict, Func<A, V> getter) where A : Attribute
{
dict = new Dictionary<Type, V>();
for (int i = 0; i < nodeTypes.Length; i++)
{
object[] attribs = nodeTypes[i].GetCustomAttributes(typeof(A), true);
if (attribs == null || attribs.Length == 0)
{
continue;
}
A attrib = attribs[0] as A;
dict.Add(nodeTypes[i], getter(attrib));
}
}
/// <summary> Get FieldInfo of a field, including those that are private and/or inherited </summary>
public static FieldInfo GetFieldInfo(this Type type, string fieldName)
{
// If we can't find field in the first run, it's probably a private field in a base class.
FieldInfo field = type.GetField(fieldName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
// Search base classes for private fields only. Public fields are found above
while (field == null && (type = type.BaseType) != typeof(Node))
{
field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
}
return field;
}
/// <summary> Get all classes deriving from baseType via reflection </summary>
public static Type[] GetDerivedTypes(this Type baseType)
{
var types = new List<Type>();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
try
{
types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t))
.ToArray());
}
catch (ReflectionTypeLoadException) {}
}
return types.ToArray();
}
/// <summary> Find methods marked with the [ContextMenu] attribute and add them to the context menu </summary>
public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj)
{
var items = GetContextMenuMethods(obj);
if (items.Length != 0)
{
contextMenu.AddSeparator("");
var invalidatedEntries = new List<string>();
foreach (var checkValidate in items)
{
if (checkValidate.Key.validate && !(bool)checkValidate.Value.Invoke(obj, null))
{
invalidatedEntries.Add(checkValidate.Key.menuItem);
}
}
for (int i = 0; i < items.Length; i++)
{
var kvp = items[i];
if (invalidatedEntries.Contains(kvp.Key.menuItem))
{
contextMenu.AddDisabledItem(new GUIContent(kvp.Key.menuItem));
}
else
{
contextMenu.AddItem(new GUIContent(kvp.Key.menuItem), false, () => kvp.Value.Invoke(obj, null));
}
}
}
}
/// <summary> Call OnValidate on target </summary>
public static void TriggerOnValidate(this Object target)
{
MethodInfo onValidate = null;
if (target != null)
{
onValidate = target.GetType().GetMethod("OnValidate",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (onValidate != null)
{
onValidate.Invoke(target, null);
}
}
}
public static KeyValuePair<ContextMenu, MethodInfo>[] GetContextMenuMethods(object obj)
{
Type type = obj.GetType();
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
BindingFlags.NonPublic);
var kvp = new List<KeyValuePair<ContextMenu, MethodInfo>>();
for (int i = 0; i < methods.Length; i++)
{
var attribs = methods[i].GetCustomAttributes(typeof(ContextMenu), true).Select(x => x as ContextMenu)
.ToArray();
if (attribs == null || attribs.Length == 0)
{
continue;
}
if (methods[i].GetParameters().Length != 0)
{
Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name +
" has parameters and cannot be used for context menu commands.");
continue;
}
if (methods[i].IsStatic)
{
Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name +
" is static and cannot be used for context menu commands.");
continue;
}
for (int k = 0; k < attribs.Length; k++)
{
kvp.Add(new KeyValuePair<ContextMenu, MethodInfo>(attribs[k], methods[i]));
}
}
#if UNITY_5_5_OR_NEWER
//Sort menu items
kvp.Sort((x, y) => x.Key.priority.CompareTo(y.Key.priority));
#endif
return kvp.ToArray();
}
/// <summary> Very crude. Uses a lot of reflection. </summary>
public static void OpenPreferences()
{
try
{
#if UNITY_2018_3_OR_NEWER
SettingsService.OpenUserPreferences("Preferences/Node Editor");
#else
//Open preferences window
Assembly assembly = Assembly.GetAssembly(typeof(UnityEditor.EditorWindow));
Type type = assembly.GetType("UnityEditor.PreferencesWindow");
type.GetMethod("ShowPreferencesWindow", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null);
//Get the window
EditorWindow window = EditorWindow.GetWindow(type);
//Make sure custom sections are added (because waiting for it to happen automatically is too slow)
FieldInfo refreshField =
type.GetField("m_RefreshCustomPreferences", BindingFlags.NonPublic | BindingFlags.Instance);
if ((bool)refreshField.GetValue(window))
{
type.GetMethod("AddCustomSections", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(window, null);
refreshField.SetValue(window, false);
}
//Get sections
FieldInfo sectionsField = type.GetField("m_Sections", BindingFlags.Instance | BindingFlags.NonPublic);
IList sections = sectionsField.GetValue(window) as IList;
//Iterate through sections and check contents
Type sectionType = sectionsField.FieldType.GetGenericArguments()[0];
FieldInfo sectionContentField =
sectionType.GetField("content", BindingFlags.Instance | BindingFlags.Public);
for (int i = 0; i < sections.Count; i++)
{
GUIContent sectionContent = sectionContentField.GetValue(sections[i]) as GUIContent;
if (sectionContent.text == "Node Editor")
{
//Found contents - Set index
FieldInfo sectionIndexField =
type.GetField("m_SelectedSectionIndex", BindingFlags.Instance | BindingFlags.NonPublic);
sectionIndexField.SetValue(window, i);
return;
}
}
#endif
}
catch (Exception e)
{
Debug.LogError(e);
Debug.LogWarning(
"Unity has changed around internally. Can't open properties through reflection. Please contact xNode developer and supply unity version number.");
}
}
}
}