using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine; using UnityObject = UnityEngine.Object; namespace Unity.VisualScripting { [InitializeAfterPlugins] public static class Icons { static Icons() { EditorGUIUtility_GetScriptObjectFromClass = typeof(EditorGUIUtility).GetMethod("GetScript", BindingFlags.Static | BindingFlags.NonPublic); EditorGUIUtility_GetIconForObject = typeof(EditorGUIUtility).GetMethod("GetIconForObject", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); Load(); } private static readonly MethodInfo EditorGUIUtility_GetScriptObjectFromClass; // UnityObject EditorGUIUtility.GetScript(string scriptClass); private static readonly MethodInfo EditorGUIUtility_GetIconForObject; // Texture2D EditorGUIUtility.GetIconForObject(UnityObject obj); private static readonly Dictionary typeIcons = new Dictionary(); private static readonly Dictionary namespaceIcons = new Dictionary(); private static readonly Dictionary enumIcons = new Dictionary(); private static readonly Dictionary resourcesTypeIcons = new Dictionary(); public static void Load() { using (ProfilingUtility.SampleBlock("Load Icons")) { Language.Load(); } } internal static void Clear() { typeIcons.Clear(); namespaceIcons.Clear(); enumIcons.Clear(); resourcesTypeIcons.Clear(); } public static EditorTexture Icon(this Type type) { return Type(type); } public static EditorTexture Icon(this MemberInfo member, ActionDirection direction = ActionDirection.Any) { return Member(member, direction); } public static EditorTexture Icon(this Enum @enum) { return Enum(@enum); } public static EditorTexture Icon(this Namespace @namespace) { return Namespace(@namespace); } public static EditorTexture Type(Type type) { if (type == null) { return BoltCore.Icons.@null; } if (!typeIcons.ContainsKey(type)) { var icon = GetCustomTypeIcon(type); if (icon == null) { if (type.IsClass) { icon = GetTypeVisibilityIcon(type, Language.@class); } else if (type.IsInterface) { icon = GetTypeVisibilityIcon(type, Language.@interface); } else if (type.IsPrimitive) { icon = GetTypeVisibilityIcon(type, Language.primitive); } else if (type.IsEnum) { icon = GetTypeVisibilityIcon(type, Language.@enum); } else if (type.IsValueType) { icon = GetTypeVisibilityIcon(type, Language.@struct); } } typeIcons.Add(type, icon); } return typeIcons[type]; } private static EditorTexture GetCustomTypeIcon(Type type, bool inherit = true) { var attribute = type.GetAttribute(); if (attribute != null) { type = attribute.type; } var resourcesIcon = GetResourcesTypeIcon(type.CSharpFileName(true, true)) ?? GetResourcesTypeIcon(type.CSharpFileName(true, false)) ?? GetResourcesTypeIcon(type.CSharpFileName(false, true)) ?? GetResourcesTypeIcon(type.CSharpFileName(false, false)); if (resourcesIcon != null) { return resourcesIcon; } if (typeof(UnityObject).IsAssignableFrom(type)) { var unityIcon = GetBuiltInUnityTypeIcon(type); if (unityIcon != null) { return unityIcon; } } if (type.IsEnum || type == typeof(Enum)) { return GetResourcesTypeIcon("enum"); } if (type.IsGenericType) { var definition = type.GetGenericTypeDefinition(); var isList = definition == typeof(IList<>); var isEnumerable = definition == typeof(IEnumerable<>); if (isList || isEnumerable) { var argument = type.GetGenericArguments()[0]; if (argument.IsEnum || argument == typeof(Enum)) { if (isList) { return GetResourcesTypeIcon("System.Collections.Generic.IList_enum"); } return GetResourcesTypeIcon("System.Collections.Generic.IEnumerable_enum"); } } } if (typeof(MonoBehaviour).IsAssignableFrom(type)) { var scriptIcon = GetScriptTypeIcon(type.Name); if (scriptIcon != null) { return scriptIcon; } } if (inherit) { foreach (var inherited in type.BaseTypeAndInterfaces(false).OrderBy(InterfaceImplementationPriority)) { if (inherited == typeof(object)) { continue; } var inheritedIcon = GetCustomTypeIcon(inherited, false); if (inheritedIcon != null) { return inheritedIcon; } } if (type.BaseType != null && type.BaseType != typeof(object)) { var baseTypeIcon = GetCustomTypeIcon(type.BaseType, true); if (baseTypeIcon != null) { return baseTypeIcon; } } } if (typeof(UnityObject).IsAssignableFrom(type)) { return GetBuiltInUnityTypeIcon(typeof(UnityObject)); } return null; } private static int InterfaceImplementationPriority(Type type) { // Quick and dirty method to determine interface implementation priority. /* Desired order is: * * IList<> * IList * ICollection<> * ICollection * IEnumerable<> * IEnumerable */ var priority = type.GetAttribute()?.priority; if (priority != null) { return priority.Value; } if (type.IsGenericType) { var definition = type.GetGenericTypeDefinition(); if (definition == typeof(IList<>)) { return 0; } if (definition == typeof(ICollection<>)) { return 2; } if (definition == typeof(IEnumerable<>)) { return 4; } return int.MaxValue - 1; } if (type == typeof(IList)) { return 1; } if (type == typeof(ICollection)) { return 3; } if (type == typeof(IEnumerable)) { return 5; } return int.MaxValue; } public static EditorTexture Icon(this UnityObject obj) { var icon = (Texture2D)EditorGUIUtility.ObjectContent(obj, obj?.GetType()).image; if (icon != null) { return EditorTexture.Single(icon); } return null; } private static EditorTexture GetResourcesTypeIcon(string fileName) { if (!resourcesTypeIcons.TryGetValue(fileName, out var icon)) { icon = PluginResources.LoadSharedIcon($"{fileName}.png", false); resourcesTypeIcons.Add(fileName, icon); } return icon; } private static EditorTexture GetBuiltInUnityTypeIcon(Type type) { var icon = AssetPreview.GetMiniTypeThumbnail(type); // Change the blank file icon to a Unity-logo file icon if (icon == EditorGUIUtility.FindTexture("DefaultAsset Icon")) { icon = EditorGUIUtility.FindTexture("ScriptableObject Icon"); } if (icon != null) { return EditorTexture.Single(icon); } return null; } private static EditorTexture GetScriptTypeIcon(string scriptName) { var scriptObject = (UnityObject)EditorGUIUtility_GetScriptObjectFromClass.Invoke(null, new object[] { scriptName }); if (scriptObject != null) { var scriptIcon = (Texture2D)EditorGUIUtility_GetIconForObject.Invoke(null, new object[] { scriptObject }); if (scriptIcon != null) { return EditorTexture.Single(scriptIcon); } } var scriptPath = AssetDatabase.GetAssetPath(scriptObject); if (scriptPath != null) { switch (Path.GetExtension(scriptPath)) { case ".js": return EditorTexture.Single((Texture2D)EditorGUIUtility.IconContent("js Script Icon").image); case ".cs": return EditorTexture.Single((Texture2D)EditorGUIUtility.IconContent("cs Script Icon").image); case ".boo": return EditorTexture.Single((Texture2D)EditorGUIUtility.IconContent("boo Script Icon").image); } } return null; } private static EditorTexture GetTypeVisibilityIcon(Type type, LanguageIconSet languageIcon) { if (languageIcon == null) { return null; } if (type.IsNested) { if (type.IsNestedPrivate) { return languageIcon.@private; } if (type.IsNestedFamily) { return languageIcon.@protected; } if (type.IsNestedAssembly) { return languageIcon.@internal; } if (type.IsNestedPublic) { return languageIcon.@public; } } else { if (type.IsPublic) { return languageIcon.@public; } return languageIcon.@internal; } return null; } public static EditorTexture Member(MemberInfo member, ActionDirection direction) { Ensure.That(nameof(member)).IsNotNull(member); var method = member as MethodInfo; var field = member as FieldInfo; var property = member as PropertyInfo; var constructor = member as ConstructorInfo; if (method != null) { if (method.IsExtension()) { return Language.extensionMethod.@public; } if (method.IsPrivate) { return Language.method.@private; } if (method.IsFamily) { return Language.method.@protected; } if (method.IsAssembly) { return Language.method.@internal; } if (method.IsPublic) { return Language.method.@public; } } else if (constructor != null) { if (constructor.IsPrivate) { return Language.constructor.@private; } if (constructor.IsFamily) { return Language.constructor.@protected; } if (constructor.IsAssembly) { return Language.constructor.@internal; } if (constructor.IsPublic) { return Language.constructor.@public; } } else if (field != null) { if (field.IsLiteral) { if (field.IsPrivate) { return Language.method.@private; } if (field.IsFamily) { return Language.method.@protected; } if (field.IsAssembly) { return Language.method.@internal; } if (field.IsPublic) { return Language.method.@public; } } else { if (field.IsPrivate) { return Language.method.@private; } if (field.IsFamily) { return Language.method.@protected; } if (field.IsAssembly) { return Language.method.@internal; } if (field.IsPublic) { return Language.method.@public; } } } else if (property != null) { var accessors = property.GetAccessors(true); var getter = accessors.FirstOrDefault(accessor => accessor.ReturnType != typeof(void)); var setter = accessors.FirstOrDefault(accessor => accessor.ReturnType == typeof(void)); bool isPrivate, isProtected, isInternal, isPublic; if (direction == ActionDirection.Any) { isPrivate = getter == null || getter.IsPrivate || setter == null || setter.IsPrivate; if (isPrivate) { isProtected = false; isInternal = false; isPublic = false; } else { isProtected = getter.IsFamily || setter.IsFamily; isInternal = getter.IsAssembly || setter.IsAssembly; isPublic = getter.IsPublic && setter.IsPublic; } } else if (direction == ActionDirection.Get && getter != null) { isPrivate = getter.IsPrivate; isProtected = getter.IsFamily; isInternal = getter.IsAssembly; isPublic = getter.IsPublic; } else if (direction == ActionDirection.Set && setter != null) { isPrivate = setter.IsPrivate; isProtected = setter.IsFamily; isInternal = setter.IsAssembly; isPublic = setter.IsPublic; } else { return null; } if (isPrivate) { return Language.property.@private; } if (isProtected) { return Language.property.@protected; } if (isInternal) { return Language.property.@internal; } if (isPublic) { return Language.property.@public; } } return null; } public static EditorTexture Enum(Enum @enum) { Ensure.That(nameof(@enum)).IsNotNull(@enum); if (!enumIcons.ContainsKey(@enum)) { var enumType = @enum.GetType(); if (!enumType.IsEnum) { throw new ArgumentException(nameof(@enum)); } var namespaced = PluginResources.LoadSharedIcon($"{enumType.CSharpFileName(true)}/{@enum}.png", false); var nonNamespaced = PluginResources.LoadSharedIcon($"{enumType.CSharpFileName(false)}/{@enum}.png", false); enumIcons.Add(@enum, namespaced ?? nonNamespaced); } return enumIcons[@enum]; } public static EditorTexture Namespace(Namespace @namespace) { Ensure.That(nameof(@namespace)).IsNotNull(@namespace); if (@namespace.IsGlobal) { return Language.@namespace.@public; } if (!namespaceIcons.ContainsKey(@namespace)) { var path = $"{@namespace}.png"; namespaceIcons.Add(@namespace, PluginResources.LoadSharedIcon(path, false) ?? Language.@namespace.@public); } return namespaceIcons[@namespace]; } public static class Language { public static LanguageIconSet method; public static LanguageIconSet @namespace { get; private set; } public static LanguageIconSet @class { get; private set; } public static LanguageIconSet @interface { get; private set; } public static LanguageIconSet @struct { get; private set; } public static LanguageIconSet @enum { get; private set; } public static LanguageIconSet primitive { get; private set; } public static LanguageIconSet field { get; private set; } public static LanguageIconSet property { get; private set; } public static LanguageIconSet extensionMethod { get; private set; } public static LanguageIconSet constructor { get; private set; } public static LanguageIconSet @const { get; private set; } public static LanguageIconSet favorite { get; private set; } internal static void Load() { @namespace = LanguageIconSet.Load("Namespace"); @interface = LanguageIconSet.Load("Interface"); @class = LanguageIconSet.Load("Class"); @struct = LanguageIconSet.Load("Struct"); @enum = LanguageIconSet.Load("Enum"); primitive = LanguageIconSet.Load("Primitive"); field = LanguageIconSet.Load("Field"); property = LanguageIconSet.Load("Property"); method = LanguageIconSet.Load("Method"); extensionMethod = LanguageIconSet.Load("ExtensionMethod"); constructor = LanguageIconSet.Load("Constructor"); @const = LanguageIconSet.Load("Const"); favorite = LanguageIconSet.Load("Favorite"); } } } }