using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using UnityEditor; using UnityEditor.ProjectWindowCallback; using UnityEngine; using XNode; using Object = UnityEngine.Object; namespace XNodeEditor { /// A set of editor-only utilities and extensions for xNode public static class NodeEditorUtilities { /// C#'s Script Icon [The one MonoBhevaiour Scripts have]. private static readonly Texture2D scriptIcon = EditorGUIUtility.IconContent("cs Script Icon").image as Texture2D; /// Saves Attribute from Type+Field for faster lookup. Resets on recompiles. private static readonly Dictionary>> typeAttributes = new Dictionary>>(); /// Saves ordered PropertyAttribute from Type+Field for faster lookup. Resets on recompiles. private static readonly Dictionary>> typeOrderedPropertyAttributes = new Dictionary>>(); public static bool GetAttrib(Type classType, out T attribOut) where T : Attribute { object[] attribs = classType.GetCustomAttributes(typeof(T), false); return GetAttrib(attribs, out attribOut); } public static bool GetAttrib(object[] attribs, out T attribOut) where T : Attribute { for (int i = 0; i < attribs.Length; i++) { if (attribs[i] is T) { attribOut = attribs[i] as T; return true; } } attribOut = null; return false; } public static bool GetAttrib(Type classType, string fieldName, out T attribOut) where T : Attribute { // If we can't find field in the first run, it's probably a private field in a base class. FieldInfo field = classType.GetFieldInfo(fieldName); // This shouldn't happen. Ever. if (field == null) { Debug.LogWarning("Field " + fieldName + " couldnt be found"); attribOut = null; return false; } object[] attribs = field.GetCustomAttributes(typeof(T), true); return GetAttrib(attribs, out attribOut); } public static bool HasAttrib(object[] attribs) where T : Attribute { for (int i = 0; i < attribs.Length; i++) { if (attribs[i].GetType() == typeof(T)) { return true; } } return false; } public static bool GetCachedAttrib(Type classType, string fieldName, out T attribOut) where T : Attribute { Dictionary> typeFields; if (!typeAttributes.TryGetValue(classType, out typeFields)) { typeFields = new Dictionary>(); typeAttributes.Add(classType, typeFields); } Dictionary typeTypes; if (!typeFields.TryGetValue(fieldName, out typeTypes)) { typeTypes = new Dictionary(); typeFields.Add(fieldName, typeTypes); } Attribute attr; if (!typeTypes.TryGetValue(typeof(T), out attr)) { if (GetAttrib(classType, fieldName, out attribOut)) { typeTypes.Add(typeof(T), attribOut); return true; } typeTypes.Add(typeof(T), null); } if (attr == null) { attribOut = null; return false; } attribOut = attr as T; return true; } public static List GetCachedPropertyAttribs(Type classType, string fieldName) { Dictionary> typeFields; if (!typeOrderedPropertyAttributes.TryGetValue(classType, out typeFields)) { typeFields = new Dictionary>(); typeOrderedPropertyAttributes.Add(classType, typeFields); } List typeAttributes; if (!typeFields.TryGetValue(fieldName, out typeAttributes)) { FieldInfo field = classType.GetFieldInfo(fieldName); object[] attribs = field.GetCustomAttributes(typeof(PropertyAttribute), true); typeAttributes = attribs.Cast().Reverse().ToList(); //Unity draws them in reverse typeFields.Add(fieldName, typeAttributes); } return typeAttributes; } public static bool IsMac() { #if UNITY_2017_1_OR_NEWER return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX; #else return SystemInfo.operatingSystem.StartsWith("Mac"); #endif } /// Returns true if this can be casted to public static bool IsCastableTo(this Type from, Type to) { if (to.IsAssignableFrom(from)) { return true; } var methods = from.GetMethods(BindingFlags.Public | BindingFlags.Static) .Where( m => m.ReturnType == to && (m.Name == "op_Implicit" || m.Name == "op_Explicit") ); return methods.Count() > 0; } /// /// Looking for ports with value Type compatible with a given type. /// /// Node to search /// Type to find compatiblities /// /// True if NodeType has some port with value type compatible public static bool HasCompatiblePortType(Type nodeType, Type compatibleType, NodePort.IO direction = NodePort.IO.Input) { Type findType = typeof(Node.InputAttribute); if (direction == NodePort.IO.Output) { findType = typeof(Node.OutputAttribute); } //Get All fields from node type and we go filter only field with portAttribute. //This way is possible to know the values of the all ports and if have some with compatible value tue foreach (FieldInfo f in NodeDataCache.GetNodeFields(nodeType)) { object portAttribute = f.GetCustomAttributes(findType, false).FirstOrDefault(); if (portAttribute != null) { if (IsCastableTo(f.FieldType, compatibleType)) { return true; } } } return false; } /// /// Filter only node types that contains some port value type compatible with an given type /// /// List with all nodes type to filter /// Compatible Type to Filter /// Return Only Node Types with ports compatible, or an empty list public static List GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, NodePort.IO direction = NodePort.IO.Input) { //Result List var filteredTypes = new List(); //Return empty list if (nodeTypes == null) { return filteredTypes; } if (compatibleType == null) { return filteredTypes; } //Find compatiblity foreach (Type findType in nodeTypes) { if (HasCompatiblePortType(findType, compatibleType, direction)) { filteredTypes.Add(findType); } } return filteredTypes; } /// Return a prettiefied type name. public static string PrettyName(this Type type) { if (type == null) { return "null"; } if (type == typeof(object)) { return "object"; } if (type == typeof(float)) { return "float"; } if (type == typeof(int)) { return "int"; } if (type == typeof(long)) { return "long"; } if (type == typeof(double)) { return "double"; } if (type == typeof(string)) { return "string"; } if (type == typeof(bool)) { return "bool"; } if (type.IsGenericType) { string s = ""; Type genericType = type.GetGenericTypeDefinition(); if (genericType == typeof(List<>)) { s = "List"; } else { s = type.GetGenericTypeDefinition().ToString(); } var types = type.GetGenericArguments(); string[] stypes = new string[types.Length]; for (int i = 0; i < types.Length; i++) { stypes[i] = types[i].PrettyName(); } return s + "<" + string.Join(", ", stypes) + ">"; } if (type.IsArray) { string rank = ""; for (int i = 1; i < type.GetArrayRank(); i++) { rank += ","; } Type elementType = type.GetElementType(); if (!elementType.IsArray) { return elementType.PrettyName() + "[" + rank + "]"; } { string s = elementType.PrettyName(); int i = s.IndexOf('['); return s.Substring(0, i) + "[" + rank + "]" + s.Substring(i); } } return type.ToString(); } /// Returns the default name for the node type. public static string NodeDefaultName(Type type) { string typeName = type.Name; // Automatically remove redundant 'Node' postfix if (typeName.EndsWith("Node")) { typeName = typeName.Substring(0, typeName.LastIndexOf("Node")); } typeName = ObjectNames.NicifyVariableName(typeName); return typeName; } /// Returns the default creation path for the node type. public static string NodeDefaultPath(Type type) { string typePath = type.ToString().Replace('.', '/'); // Automatically remove redundant 'Node' postfix if (typePath.EndsWith("Node")) { typePath = typePath.Substring(0, typePath.LastIndexOf("Node")); } typePath = ObjectNames.NicifyVariableName(typePath); return typePath; } /// Creates a new C# Class. [MenuItem("Assets/Create/xNode/Node C# Script", false, 89)] private static void CreateNode() { string[] guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs"); if (guids.Length == 0) { Debug.LogWarning("xNode_NodeTemplate.cs.txt not found in asset database"); return; } string path = AssetDatabase.GUIDToAssetPath(guids[0]); CreateFromTemplate( "NewNode.cs", path ); } /// Creates a new C# Class. [MenuItem("Assets/Create/xNode/NodeGraph C# Script", false, 89)] private static void CreateGraph() { string[] guids = AssetDatabase.FindAssets("xNode_NodeGraphTemplate.cs"); if (guids.Length == 0) { Debug.LogWarning("xNode_NodeGraphTemplate.cs.txt not found in asset database"); return; } string path = AssetDatabase.GUIDToAssetPath(guids[0]); CreateFromTemplate( "NewNodeGraph.cs", path ); } public static void CreateFromTemplate(string initialName, string templatePath) { ProjectWindowUtil.StartNameEditingIfProjectWindowExists( 0, ScriptableObject.CreateInstance(), initialName, scriptIcon, templatePath ); } /// Inherits from EndNameAction, must override EndNameAction.Action public class DoCreateCodeFile : EndNameEditAction { public override void Action(int instanceId, string pathName, string resourceFile) { Object o = CreateScript(pathName, resourceFile); ProjectWindowUtil.ShowCreatedAsset(o); } } /// Creates Script from Template's path. internal static Object CreateScript(string pathName, string templatePath) { string className = Path.GetFileNameWithoutExtension(pathName).Replace(" ", string.Empty); string templateText = string.Empty; UTF8Encoding encoding = new UTF8Encoding(true, false); if (File.Exists(templatePath)) { /// Read procedures. StreamReader reader = new StreamReader(templatePath); templateText = reader.ReadToEnd(); reader.Close(); templateText = templateText.Replace("#SCRIPTNAME#", className); templateText = templateText.Replace("#NOTRIM#", string.Empty); /// You can replace as many tags you make on your templates, just repeat Replace function /// e.g.: /// templateText = templateText.Replace("#NEWTAG#", "MyText"); /// Write procedures. StreamWriter writer = new StreamWriter(Path.GetFullPath(pathName), false, encoding); writer.Write(templateText); writer.Close(); AssetDatabase.ImportAsset(pathName); return AssetDatabase.LoadAssetAtPath(pathName, typeof(Object)); } Debug.LogError(string.Format("The template file was not found: {0}", templatePath)); return null; } } }