NodeEditorReflection.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. using UnityEditor;
  7. using UnityEngine;
  8. namespace XNodeEditor {
  9. /// <summary> Contains reflection-related extensions built for xNode </summary>
  10. public static class NodeEditorReflection {
  11. [NonSerialized] private static Dictionary<Type, Color> nodeTint;
  12. [NonSerialized] private static Dictionary<Type, int> nodeWidth;
  13. /// <summary> All available node types </summary>
  14. public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } }
  15. [NonSerialized] private static Type[] _nodeTypes = null;
  16. /// <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>
  17. public static Func<bool> GetIsDockedDelegate(this EditorWindow window) {
  18. BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
  19. MethodInfo isDockedMethod = typeof(EditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true);
  20. return (Func<bool>) Delegate.CreateDelegate(typeof(Func<bool>), window, isDockedMethod);
  21. }
  22. public static Type[] GetNodeTypes() {
  23. //Get all classes deriving from Node via reflection
  24. return GetDerivedTypes(typeof(XNode.Node));
  25. }
  26. /// <summary> Custom node tint colors defined with [NodeColor(r, g, b)] </summary>
  27. public static bool TryGetAttributeTint(this Type nodeType, out Color tint) {
  28. if (nodeTint == null) {
  29. CacheAttributes<Color, XNode.Node.NodeTintAttribute>(ref nodeTint, x => x.color);
  30. }
  31. return nodeTint.TryGetValue(nodeType, out tint);
  32. }
  33. /// <summary> Get custom node widths defined with [NodeWidth(width)] </summary>
  34. public static bool TryGetAttributeWidth(this Type nodeType, out int width) {
  35. if (nodeWidth == null) {
  36. CacheAttributes<int, XNode.Node.NodeWidthAttribute>(ref nodeWidth, x => x.width);
  37. }
  38. return nodeWidth.TryGetValue(nodeType, out width);
  39. }
  40. private static void CacheAttributes<V, A>(ref Dictionary<Type, V> dict, Func<A, V> getter) where A : Attribute {
  41. dict = new Dictionary<Type, V>();
  42. for (int i = 0; i < nodeTypes.Length; i++) {
  43. object[] attribs = nodeTypes[i].GetCustomAttributes(typeof(A), true);
  44. if (attribs == null || attribs.Length == 0) continue;
  45. A attrib = attribs[0] as A;
  46. dict.Add(nodeTypes[i], getter(attrib));
  47. }
  48. }
  49. /// <summary> Get FieldInfo of a field, including those that are private and/or inherited </summary>
  50. public static FieldInfo GetFieldInfo(this Type type, string fieldName) {
  51. // If we can't find field in the first run, it's probably a private field in a base class.
  52. FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  53. // Search base classes for private fields only. Public fields are found above
  54. while (field == null && (type = type.BaseType) != typeof(XNode.Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
  55. return field;
  56. }
  57. /// <summary> Get all classes deriving from baseType via reflection </summary>
  58. public static Type[] GetDerivedTypes(this Type baseType) {
  59. List<System.Type> types = new List<System.Type>();
  60. System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
  61. foreach (Assembly assembly in assemblies) {
  62. try {
  63. types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
  64. } catch (ReflectionTypeLoadException) { }
  65. }
  66. return types.ToArray();
  67. }
  68. /// <summary> Find methods marked with the [ContextMenu] attribute and add them to the context menu </summary>
  69. public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj) {
  70. KeyValuePair<ContextMenu, MethodInfo>[] items = GetContextMenuMethods(obj);
  71. if (items.Length != 0) {
  72. contextMenu.AddSeparator("");
  73. List<string> invalidatedEntries = new List<string>();
  74. foreach (KeyValuePair<ContextMenu, MethodInfo> checkValidate in items) {
  75. if (checkValidate.Key.validate && !(bool) checkValidate.Value.Invoke(obj, null)) {
  76. invalidatedEntries.Add(checkValidate.Key.menuItem);
  77. }
  78. }
  79. for (int i = 0; i < items.Length; i++) {
  80. KeyValuePair<ContextMenu, MethodInfo> kvp = items[i];
  81. if (invalidatedEntries.Contains(kvp.Key.menuItem)) {
  82. contextMenu.AddDisabledItem(new GUIContent(kvp.Key.menuItem));
  83. } else {
  84. contextMenu.AddItem(new GUIContent(kvp.Key.menuItem), false, () => kvp.Value.Invoke(obj, null));
  85. }
  86. }
  87. }
  88. }
  89. /// <summary> Call OnValidate on target </summary>
  90. public static void TriggerOnValidate(this UnityEngine.Object target) {
  91. System.Reflection.MethodInfo onValidate = null;
  92. if (target != null) {
  93. onValidate = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  94. if (onValidate != null) onValidate.Invoke(target, null);
  95. }
  96. }
  97. public static KeyValuePair<ContextMenu, MethodInfo>[] GetContextMenuMethods(object obj) {
  98. Type type = obj.GetType();
  99. MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
  100. List<KeyValuePair<ContextMenu, MethodInfo>> kvp = new List<KeyValuePair<ContextMenu, MethodInfo>>();
  101. for (int i = 0; i < methods.Length; i++) {
  102. ContextMenu[] attribs = methods[i].GetCustomAttributes(typeof(ContextMenu), true).Select(x => x as ContextMenu).ToArray();
  103. if (attribs == null || attribs.Length == 0) continue;
  104. if (methods[i].GetParameters().Length != 0) {
  105. Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " has parameters and cannot be used for context menu commands.");
  106. continue;
  107. }
  108. if (methods[i].IsStatic) {
  109. Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " is static and cannot be used for context menu commands.");
  110. continue;
  111. }
  112. for (int k = 0; k < attribs.Length; k++) {
  113. kvp.Add(new KeyValuePair<ContextMenu, MethodInfo>(attribs[k], methods[i]));
  114. }
  115. }
  116. #if UNITY_5_5_OR_NEWER
  117. //Sort menu items
  118. kvp.Sort((x, y) => x.Key.priority.CompareTo(y.Key.priority));
  119. #endif
  120. return kvp.ToArray();
  121. }
  122. /// <summary> Very crude. Uses a lot of reflection. </summary>
  123. public static void OpenPreferences() {
  124. try {
  125. #if UNITY_2018_3_OR_NEWER
  126. SettingsService.OpenUserPreferences("Preferences/Node Editor");
  127. #else
  128. //Open preferences window
  129. Assembly assembly = Assembly.GetAssembly(typeof(UnityEditor.EditorWindow));
  130. Type type = assembly.GetType("UnityEditor.PreferencesWindow");
  131. type.GetMethod("ShowPreferencesWindow", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null);
  132. //Get the window
  133. EditorWindow window = EditorWindow.GetWindow(type);
  134. //Make sure custom sections are added (because waiting for it to happen automatically is too slow)
  135. FieldInfo refreshField = type.GetField("m_RefreshCustomPreferences", BindingFlags.NonPublic | BindingFlags.Instance);
  136. if ((bool) refreshField.GetValue(window)) {
  137. type.GetMethod("AddCustomSections", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(window, null);
  138. refreshField.SetValue(window, false);
  139. }
  140. //Get sections
  141. FieldInfo sectionsField = type.GetField("m_Sections", BindingFlags.Instance | BindingFlags.NonPublic);
  142. IList sections = sectionsField.GetValue(window) as IList;
  143. //Iterate through sections and check contents
  144. Type sectionType = sectionsField.FieldType.GetGenericArguments() [0];
  145. FieldInfo sectionContentField = sectionType.GetField("content", BindingFlags.Instance | BindingFlags.Public);
  146. for (int i = 0; i < sections.Count; i++) {
  147. GUIContent sectionContent = sectionContentField.GetValue(sections[i]) as GUIContent;
  148. if (sectionContent.text == "Node Editor") {
  149. //Found contents - Set index
  150. FieldInfo sectionIndexField = type.GetField("m_SelectedSectionIndex", BindingFlags.Instance | BindingFlags.NonPublic);
  151. sectionIndexField.SetValue(window, i);
  152. return;
  153. }
  154. }
  155. #endif
  156. } catch (Exception e) {
  157. Debug.LogError(e);
  158. Debug.LogWarning("Unity has changed around internally. Can't open properties through reflection. Please contact xNode developer and supply unity version number.");
  159. }
  160. }
  161. }
  162. }