NodeEditorUtilities.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Text;
  7. using UnityEditor;
  8. using UnityEngine;
  9. using Object = UnityEngine.Object;
  10. namespace XNodeEditor {
  11. /// <summary> A set of editor-only utilities and extensions for xNode </summary>
  12. public static class NodeEditorUtilities {
  13. /// <summary>C#'s Script Icon [The one MonoBhevaiour Scripts have].</summary>
  14. private static Texture2D scriptIcon = (EditorGUIUtility.IconContent("cs Script Icon").image as Texture2D);
  15. /// Saves Attribute from Type+Field for faster lookup. Resets on recompiles.
  16. private static Dictionary<Type, Dictionary<string, Dictionary<Type, Attribute>>> typeAttributes = new Dictionary<Type, Dictionary<string, Dictionary<Type, Attribute>>>();
  17. /// Saves ordered PropertyAttribute from Type+Field for faster lookup. Resets on recompiles.
  18. private static Dictionary<Type, Dictionary<string, List<PropertyAttribute>>> typeOrderedPropertyAttributes = new Dictionary<Type, Dictionary<string, List<PropertyAttribute>>>();
  19. public static bool GetAttrib<T>(Type classType, out T attribOut) where T : Attribute {
  20. object[] attribs = classType.GetCustomAttributes(typeof(T), false);
  21. return GetAttrib(attribs, out attribOut);
  22. }
  23. public static bool GetAttrib<T>(object[] attribs, out T attribOut) where T : Attribute {
  24. for (int i = 0; i < attribs.Length; i++) {
  25. if (attribs[i] is T) {
  26. attribOut = attribs[i] as T;
  27. return true;
  28. }
  29. }
  30. attribOut = null;
  31. return false;
  32. }
  33. public static bool GetAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute {
  34. // If we can't find field in the first run, it's probably a private field in a base class.
  35. FieldInfo field = classType.GetFieldInfo(fieldName);
  36. // This shouldn't happen. Ever.
  37. if (field == null) {
  38. Debug.LogWarning("Field " + fieldName + " couldnt be found");
  39. attribOut = null;
  40. return false;
  41. }
  42. object[] attribs = field.GetCustomAttributes(typeof(T), true);
  43. return GetAttrib(attribs, out attribOut);
  44. }
  45. public static bool HasAttrib<T>(object[] attribs) where T : Attribute {
  46. for (int i = 0; i < attribs.Length; i++) {
  47. if (attribs[i].GetType() == typeof(T)) {
  48. return true;
  49. }
  50. }
  51. return false;
  52. }
  53. public static bool GetCachedAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute {
  54. Dictionary<string, Dictionary<Type, Attribute>> typeFields;
  55. if (!typeAttributes.TryGetValue(classType, out typeFields)) {
  56. typeFields = new Dictionary<string, Dictionary<Type, Attribute>>();
  57. typeAttributes.Add(classType, typeFields);
  58. }
  59. Dictionary<Type, Attribute> typeTypes;
  60. if (!typeFields.TryGetValue(fieldName, out typeTypes)) {
  61. typeTypes = new Dictionary<Type, Attribute>();
  62. typeFields.Add(fieldName, typeTypes);
  63. }
  64. Attribute attr;
  65. if (!typeTypes.TryGetValue(typeof(T), out attr)) {
  66. if (GetAttrib<T>(classType, fieldName, out attribOut)) {
  67. typeTypes.Add(typeof(T), attribOut);
  68. return true;
  69. } else typeTypes.Add(typeof(T), null);
  70. }
  71. if (attr == null) {
  72. attribOut = null;
  73. return false;
  74. }
  75. attribOut = attr as T;
  76. return true;
  77. }
  78. public static List<PropertyAttribute> GetCachedPropertyAttribs(Type classType, string fieldName) {
  79. Dictionary<string, List<PropertyAttribute>> typeFields;
  80. if (!typeOrderedPropertyAttributes.TryGetValue(classType, out typeFields)) {
  81. typeFields = new Dictionary<string, List<PropertyAttribute>>();
  82. typeOrderedPropertyAttributes.Add(classType, typeFields);
  83. }
  84. List<PropertyAttribute> typeAttributes;
  85. if (!typeFields.TryGetValue(fieldName, out typeAttributes)) {
  86. FieldInfo field = classType.GetFieldInfo(fieldName);
  87. object[] attribs = field.GetCustomAttributes(typeof(PropertyAttribute), true);
  88. typeAttributes = attribs.Cast<PropertyAttribute>().Reverse().ToList(); //Unity draws them in reverse
  89. typeFields.Add(fieldName, typeAttributes);
  90. }
  91. return typeAttributes;
  92. }
  93. public static bool IsMac() {
  94. #if UNITY_2017_1_OR_NEWER
  95. return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX;
  96. #else
  97. return SystemInfo.operatingSystem.StartsWith("Mac");
  98. #endif
  99. }
  100. /// <summary> Returns true if this can be casted to <see cref="Type"/></summary>
  101. public static bool IsCastableTo(this Type from, Type to) {
  102. if (to.IsAssignableFrom(from)) return true;
  103. var methods = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
  104. .Where(
  105. m => m.ReturnType == to &&
  106. (m.Name == "op_Implicit" ||
  107. m.Name == "op_Explicit")
  108. );
  109. return methods.Count() > 0;
  110. }
  111. /// <summary> Return a prettiefied type name. </summary>
  112. public static string PrettyName(this Type type) {
  113. if (type == null) return "null";
  114. if (type == typeof(System.Object)) return "object";
  115. if (type == typeof(float)) return "float";
  116. else if (type == typeof(int)) return "int";
  117. else if (type == typeof(long)) return "long";
  118. else if (type == typeof(double)) return "double";
  119. else if (type == typeof(string)) return "string";
  120. else if (type == typeof(bool)) return "bool";
  121. else if (type.IsGenericType) {
  122. string s = "";
  123. Type genericType = type.GetGenericTypeDefinition();
  124. if (genericType == typeof(List<>)) s = "List";
  125. else s = type.GetGenericTypeDefinition().ToString();
  126. Type[] types = type.GetGenericArguments();
  127. string[] stypes = new string[types.Length];
  128. for (int i = 0; i < types.Length; i++) {
  129. stypes[i] = types[i].PrettyName();
  130. }
  131. return s + "<" + string.Join(", ", stypes) + ">";
  132. } else if (type.IsArray) {
  133. string rank = "";
  134. for (int i = 1; i < type.GetArrayRank(); i++) {
  135. rank += ",";
  136. }
  137. Type elementType = type.GetElementType();
  138. if (!elementType.IsArray) return elementType.PrettyName() + "[" + rank + "]";
  139. else {
  140. string s = elementType.PrettyName();
  141. int i = s.IndexOf('[');
  142. return s.Substring(0, i) + "[" + rank + "]" + s.Substring(i);
  143. }
  144. } else return type.ToString();
  145. }
  146. /// <summary> Returns the default name for the node type. </summary>
  147. public static string NodeDefaultName(Type type) {
  148. string typeName = type.Name;
  149. // Automatically remove redundant 'Node' postfix
  150. if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node"));
  151. typeName = UnityEditor.ObjectNames.NicifyVariableName(typeName);
  152. return typeName;
  153. }
  154. /// <summary> Returns the default creation path for the node type. </summary>
  155. public static string NodeDefaultPath(Type type) {
  156. string typePath = type.ToString().Replace('.', '/');
  157. // Automatically remove redundant 'Node' postfix
  158. if (typePath.EndsWith("Node")) typePath = typePath.Substring(0, typePath.LastIndexOf("Node"));
  159. typePath = UnityEditor.ObjectNames.NicifyVariableName(typePath);
  160. return typePath;
  161. }
  162. /// <summary>Creates a new C# Class.</summary>
  163. [MenuItem("Assets/Create/xNode/Node C# Script", false, 89)]
  164. private static void CreateNode() {
  165. string[] guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs");
  166. if (guids.Length == 0) {
  167. Debug.LogWarning("xNode_NodeTemplate.cs.txt not found in asset database");
  168. return;
  169. }
  170. string path = AssetDatabase.GUIDToAssetPath(guids[0]);
  171. CreateFromTemplate(
  172. "NewNode.cs",
  173. path
  174. );
  175. }
  176. /// <summary>Creates a new C# Class.</summary>
  177. [MenuItem("Assets/Create/xNode/NodeGraph C# Script", false, 89)]
  178. private static void CreateGraph() {
  179. string[] guids = AssetDatabase.FindAssets("xNode_NodeGraphTemplate.cs");
  180. if (guids.Length == 0) {
  181. Debug.LogWarning("xNode_NodeGraphTemplate.cs.txt not found in asset database");
  182. return;
  183. }
  184. string path = AssetDatabase.GUIDToAssetPath(guids[0]);
  185. CreateFromTemplate(
  186. "NewNodeGraph.cs",
  187. path
  188. );
  189. }
  190. public static void CreateFromTemplate(string initialName, string templatePath) {
  191. ProjectWindowUtil.StartNameEditingIfProjectWindowExists(
  192. 0,
  193. ScriptableObject.CreateInstance<DoCreateCodeFile>(),
  194. initialName,
  195. scriptIcon,
  196. templatePath
  197. );
  198. }
  199. /// Inherits from EndNameAction, must override EndNameAction.Action
  200. public class DoCreateCodeFile : UnityEditor.ProjectWindowCallback.EndNameEditAction {
  201. public override void Action(int instanceId, string pathName, string resourceFile) {
  202. Object o = CreateScript(pathName, resourceFile);
  203. ProjectWindowUtil.ShowCreatedAsset(o);
  204. }
  205. }
  206. /// <summary>Creates Script from Template's path.</summary>
  207. internal static UnityEngine.Object CreateScript(string pathName, string templatePath) {
  208. string className = Path.GetFileNameWithoutExtension(pathName).Replace(" ", string.Empty);
  209. string templateText = string.Empty;
  210. UTF8Encoding encoding = new UTF8Encoding(true, false);
  211. if (File.Exists(templatePath)) {
  212. /// Read procedures.
  213. StreamReader reader = new StreamReader(templatePath);
  214. templateText = reader.ReadToEnd();
  215. reader.Close();
  216. templateText = templateText.Replace("#SCRIPTNAME#", className);
  217. templateText = templateText.Replace("#NOTRIM#", string.Empty);
  218. /// You can replace as many tags you make on your templates, just repeat Replace function
  219. /// e.g.:
  220. /// templateText = templateText.Replace("#NEWTAG#", "MyText");
  221. /// Write procedures.
  222. StreamWriter writer = new StreamWriter(Path.GetFullPath(pathName), false, encoding);
  223. writer.Write(templateText);
  224. writer.Close();
  225. AssetDatabase.ImportAsset(pathName);
  226. return AssetDatabase.LoadAssetAtPath(pathName, typeof(Object));
  227. } else {
  228. Debug.LogError(string.Format("The template file was not found: {0}", templatePath));
  229. return null;
  230. }
  231. }
  232. }
  233. }