mirror of
https://github.com/Siccity/xNode.git
synced 2025-12-20 09:16:01 +08:00
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.
278 lines
12 KiB
C#
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.");
|
|
}
|
|
}
|
|
}
|
|
} |