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;
}
}
}