0.2.0: Moved into inspector drawer from module.toolbox to module.inspector

This commit is contained in:
Anders Ejlersen 2021-09-18 15:48:14 +02:00
parent 5671c2c754
commit ffec2abdf4
227 changed files with 5306 additions and 29 deletions

View file

@ -0,0 +1,233 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Module.Inspector.Editor.Utilities
{
internal static class EditorMethodUtility
{
private static Dictionary<Type, ResultPrimary[]> CACHED_TYPE_TO_PRIMARIES;
private static Dictionary<MethodInfo, Result> CACHED_RESULTS;
private static Dictionary<Type, Type> CACHED_ATT_TO_DRAWER;
private static void InitializeCache()
{
if (CACHED_ATT_TO_DRAWER != null)
return;
CACHED_ATT_TO_DRAWER = new Dictionary<Type, Type>();
Type baseDrawerType = typeof(AbstractMethodDrawer);
var assembly = Assembly.GetAssembly(baseDrawerType);
Type[] types = assembly.GetTypes();
for (var i = 0; i < types.Length; i++)
{
Type type = types[i];
if (type.IsAbstract || !baseDrawerType.IsAssignableFrom(type))
continue;
IList<CustomAttributeData> attributes = type.GetCustomAttributesData();
for (var j = 0; j < attributes.Count; j++)
{
CustomAttributeData attData = attributes[j];
IList<CustomAttributeTypedArgument> arguments = attData.ConstructorArguments;
for (var k = 0; k < arguments.Count; k++)
{
if (arguments[k].Value is Type argType)
CACHED_ATT_TO_DRAWER.Add(argType, type);
}
}
}
}
public static ResultPrimary[] QueryPrimary(Object target)
{
InitializeCache();
if (CACHED_TYPE_TO_PRIMARIES == null)
CACHED_TYPE_TO_PRIMARIES = new Dictionary<Type, ResultPrimary[]>();
Type type = target.GetType();
if (CACHED_TYPE_TO_PRIMARIES.TryGetValue(type, out ResultPrimary[] primaries))
return primaries;
primaries = InternalFetchPrimary(type);
CACHED_TYPE_TO_PRIMARIES.Add(type, primaries);
return primaries;
}
public static Result Query(MethodInfo methodInfo)
{
InitializeCache();
if (CACHED_RESULTS == null)
CACHED_RESULTS = new Dictionary<MethodInfo, Result>();
if (CACHED_RESULTS.TryGetValue(methodInfo, out Result result))
return result;
result = InternalFetchProperties(methodInfo);
if (result != null)
CACHED_RESULTS.Add(methodInfo, result);
return result;
}
private static ResultPrimary[] InternalFetchPrimary(Type type)
{
const BindingFlags FLAGS = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
MethodInfo[] methods = type.GetMethods(FLAGS)
.Where(m => m.GetParameters().Length == 0)
.ToArray();
var list = new List<ResultPrimary>(methods.Length);
for (var i = 0; i < methods.Length; i++)
{
Result result = Query(methods[i]);
if (result != null)
list.Add(new ResultPrimary(methods[i], result));
}
return list.ToArray();
}
private static Result InternalFetchProperties(MethodInfo methodInfo)
{
ResultValue<DrawerMethodAttribute, DrawerMethodDrawer> drawer = null;
ResultValue<DecoratorMethodAttribute, DecoratorMethodDrawer> decorator = null;
var accessModifiers = new List<ResultValue<AccessModifierMethodAttribute, AccessModifierMethodDrawer>>(2);
object[] attributes = methodInfo.GetCustomAttributes(false);
for (var i = 0; i < attributes.Length; i++)
{
object att = attributes[i];
if (att is DrawerMethodAttribute attDrawer)
{
if (drawer != null)
continue;
var prop = InternalCreateInstanceOf<DrawerMethodDrawer>(attDrawer);
if (prop != null)
drawer = new ResultValue<DrawerMethodAttribute, DrawerMethodDrawer>(attDrawer, prop);
}
else if (att is AccessModifierMethodAttribute accessModifier)
{
var prop = InternalCreateInstanceOf<AccessModifierMethodDrawer>(accessModifier);
if (prop != null)
accessModifiers.Add(new ResultValue<AccessModifierMethodAttribute, AccessModifierMethodDrawer>(accessModifier, prop));
}
else if (att is DecoratorMethodAttribute attDecorator)
{
if (decorator != null)
continue;
var prop = InternalCreateInstanceOf<DecoratorMethodDrawer>(attDecorator);
if (prop != null)
decorator = new ResultValue<DecoratorMethodAttribute, DecoratorMethodDrawer>(attDecorator, prop);
}
}
if (drawer == null && decorator == null && accessModifiers.Count == 0)
return null;
return new Result(drawer, decorator, accessModifiers);
}
private static T InternalCreateInstanceOf<T>(AbstractMethodAttribute att) where T : AbstractMethodDrawer
{
if (CACHED_ATT_TO_DRAWER.TryGetValue(att.GetType(), out Type drawerType))
return Activator.CreateInstance(drawerType) as T;
return null;
}
/// <summary>
/// Class: Result from method query
/// </summary>
public sealed class Result
{
public readonly ResultValue<DrawerMethodAttribute, DrawerMethodDrawer> draw;
public readonly ResultValue<DecoratorMethodAttribute, DecoratorMethodDrawer> decorator;
public readonly List<ResultValue<AccessModifierMethodAttribute, AccessModifierMethodDrawer>> accessModifiers;
public Result(ResultValue<DrawerMethodAttribute, DrawerMethodDrawer> draw,
ResultValue<DecoratorMethodAttribute, DecoratorMethodDrawer> decorator,
List<ResultValue<AccessModifierMethodAttribute, AccessModifierMethodDrawer>> accessModifiers)
{
this.draw = draw;
this.decorator = decorator;
this.accessModifiers = accessModifiers;
}
public T GetAccessModifier<T>() where T : AccessModifierMethodAttribute
{
for (var i = 0; i < accessModifiers.Count; i++)
{
if (accessModifiers[i].attribute is T att)
return att;
}
return null;
}
}
/// <summary>
/// Class: Contains attribute and drawer values
/// </summary>
/// <typeparam name="T0"></typeparam>
/// <typeparam name="T1"></typeparam>
public sealed class ResultValue<T0, T1> where T0 : AbstractMethodAttribute
where T1 : AbstractMethodDrawer
{
public readonly T0 attribute;
public readonly T1 drawer;
public ResultValue(T0 attribute, T1 drawer)
{
this.attribute = attribute;
this.drawer = drawer;
}
}
/// <summary>
/// Class: Contains primary method info and drawer for a specific method
/// </summary>
public sealed class ResultPrimary
{
public readonly MethodInfo methodInfo;
public readonly AbstractMethodDrawer drawer;
public ResultPrimary(MethodInfo methodInfo, AbstractMethodDrawer drawer)
{
this.methodInfo = methodInfo;
this.drawer = drawer;
}
public ResultPrimary(MethodInfo methodInfo, Result result)
{
this.methodInfo = methodInfo;
if (result.draw != null)
drawer = result.draw.drawer;
else if (result.decorator != null)
drawer = result.decorator.drawer;
else if (result.accessModifiers != null && result.accessModifiers.Count > 0)
drawer = result.accessModifiers[0].drawer;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8084cb44127a2464ab72b4d104b7b622
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,76 @@
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
namespace Module.Inspector.Editor.Utilities
{
public static class EditorNamingUtility
{
public static string ConvertTo(Naming.EPatternType type, string str)
{
switch (type)
{
case Naming.EPatternType.CamelCasing:
return ToCamel(str);
case Naming.EPatternType.PascalCasing:
return ToPascal(str);
case Naming.EPatternType.SnakeCasing:
return ToSnake(str);
case Naming.EPatternType.SnakeCasingAllCaps:
return ToSnake(str).ToUpper();
case Naming.EPatternType.KebabCasing:
return ToKebab(str);
case Naming.EPatternType.KebabCasingAllCaps:
return ToKebab(str).ToUpper();
default:
return str;
}
}
public static string ToPascal(string str)
{
var pattern = new Regex(@"[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+");
MatchCollection matches = pattern.Matches(str);
if (matches.Count != 0)
str = matches.Cast<Match>().Select(c => c.Value).Aggregate(( a, b ) => a + " " + b);
var culture = new CultureInfo("en-US", false);
str = culture.TextInfo.ToTitleCase(str);
str = str.Replace(@" ", "");
return str;
}
public static string ToCamel(string str)
{
str = ToPascal(str);
if (str.Length > 0)
str = char.ToLower(str[0]) + str.Substring(1);
return str;
}
public static string ToSnake(string str)
{
var pattern = new Regex(@"[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+");
MatchCollection matches = pattern.Matches(str);
if (matches.Count != 0)
str = matches.Cast<Match>().Select(c => c.Value).Aggregate(( a, b ) => a + "_" + b);
return str;
}
public static string ToKebab(string str)
{
var pattern = new Regex(@"[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+");
MatchCollection matches = pattern.Matches(str);
if (matches.Count != 0)
str = matches.Cast<Match>().Select(c => c.Value).Aggregate(( a, b ) => a + "-" + b);
return str;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cbf1a9393f7c4425b846686ccc5445e0
timeCreated: 1625650972

View file

@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace Module.Inspector.Editor.Utilities
{
public static class EditorPropertyUtility
{
private static Dictionary<FieldInfo, Result> CACHED_RESULTS;
private static Dictionary<Type, Type> CACHED_ATT_TO_DRAWER;
private static void InitializeCache()
{
if (CACHED_ATT_TO_DRAWER != null)
return;
CACHED_ATT_TO_DRAWER = new Dictionary<Type, Type>();
Type baseDrawerType = typeof(AbstractPropertyDrawer);
var assembly = Assembly.GetAssembly(baseDrawerType);
Type[] types = assembly.GetTypes();
for (var i = 0; i < types.Length; i++)
{
Type type = types[i];
if (type.IsAbstract || !baseDrawerType.IsAssignableFrom(type))
continue;
IList<CustomAttributeData> attributes = type.GetCustomAttributesData();
for (var j = 0; j < attributes.Count; j++)
{
CustomAttributeData attData = attributes[j];
IList<CustomAttributeTypedArgument> arguments = attData.ConstructorArguments;
for (var k = 0; k < arguments.Count; k++)
{
if (arguments[k].Value is Type argType)
CACHED_ATT_TO_DRAWER.Add(argType, type);
}
}
}
}
public static Result Query(FieldInfo fieldInfo)
{
InitializeCache();
if (CACHED_RESULTS == null)
CACHED_RESULTS = new Dictionary<FieldInfo, Result>();
if (CACHED_RESULTS.TryGetValue(fieldInfo, out Result result))
return result;
result = InternalFetchProperties(fieldInfo);
CACHED_RESULTS.Add(fieldInfo, result);
return result;
}
private static Result InternalFetchProperties(FieldInfo fieldInfo)
{
ResultValue<DrawerPropertyAttribute, DrawerPropertyDrawer> drawer = null;
var typeDrawer = InternalCreateInstanceOf<DrawerPropertyDrawer>(fieldInfo);
if (typeDrawer != null)
drawer = new ResultValue<DrawerPropertyAttribute, DrawerPropertyDrawer>(null, typeDrawer);
var valueModifiers = new List<ResultValue<ValueModifierPropertyAttribute, ValueModifierPropertyDrawer>>(2);
var accessModifiers = new List<ResultValue<AccessModifierPropertyAttribute, AccessModifierPropertyDrawer>>(2);
object[] attributes = fieldInfo.GetCustomAttributes(false);
string tooltip = null;
var isObsolete = false;
string obsoleteText = null;
for (var i = 0; i < attributes.Length; i++)
{
object att = attributes[i];
if (att is DrawerPropertyAttribute attDrawer)
{
if (drawer != null)
continue;
var prop = InternalCreateInstanceOf<DrawerPropertyDrawer>(attDrawer);
if (prop != null)
drawer = new ResultValue<DrawerPropertyAttribute, DrawerPropertyDrawer>(attDrawer, prop);
}
else if (att is ValueModifierPropertyAttribute valueModifier)
{
var prop = InternalCreateInstanceOf<ValueModifierPropertyDrawer>(valueModifier);
if (prop != null)
valueModifiers.Add(new ResultValue<ValueModifierPropertyAttribute, ValueModifierPropertyDrawer>(valueModifier, prop));
}
else if (att is AccessModifierPropertyAttribute accessModifier)
{
var prop = InternalCreateInstanceOf<AccessModifierPropertyDrawer>(accessModifier);
if (prop != null)
accessModifiers.Add(new ResultValue<AccessModifierPropertyAttribute, AccessModifierPropertyDrawer>(accessModifier, prop));
}
else if (att is TooltipAttribute attTooltip)
{
tooltip = attTooltip.tooltip;
}
else if (att is ObsoleteAttribute attObsolete)
{
isObsolete = true;
obsoleteText = attObsolete.Message;
}
}
if (!string.IsNullOrEmpty(obsoleteText))
{
if (tooltip != null)
tooltip += $"\n[Obsolete: {obsoleteText}";
else
tooltip = $"Obsolete: {obsoleteText}";
}
return new Result(drawer, valueModifiers, accessModifiers, tooltip, isObsolete);
}
private static T InternalCreateInstanceOf<T>(AbstractPropertyAttribute att) where T : AbstractPropertyDrawer
{
if (CACHED_ATT_TO_DRAWER.TryGetValue(att.GetType(), out Type drawerType))
return Activator.CreateInstance(drawerType) as T;
return null;
}
private static T InternalCreateInstanceOf<T>(FieldInfo fieldInfo) where T : AbstractPropertyDrawer
{
if (CACHED_ATT_TO_DRAWER.TryGetValue(fieldInfo.FieldType, out Type drawerType))
return Activator.CreateInstance(drawerType) as T;
return null;
}
/// <summary>
/// Class: Result from property query
/// </summary>
public sealed class Result
{
public readonly ResultValue<DrawerPropertyAttribute, DrawerPropertyDrawer> draw;
public readonly List<ResultValue<ValueModifierPropertyAttribute, ValueModifierPropertyDrawer>> valueModifiers;
public readonly List<ResultValue<AccessModifierPropertyAttribute, AccessModifierPropertyDrawer>> accessModifiers;
public readonly string tooltip;
public readonly bool isObsolete;
public Result(ResultValue<DrawerPropertyAttribute, DrawerPropertyDrawer> draw,
List<ResultValue<ValueModifierPropertyAttribute, ValueModifierPropertyDrawer>> valueModifiers,
List<ResultValue<AccessModifierPropertyAttribute, AccessModifierPropertyDrawer>> accessModifiers,
string tooltip,
bool isObsolete)
{
this.draw = draw;
this.valueModifiers = valueModifiers;
this.accessModifiers = accessModifiers;
this.tooltip = tooltip;
this.isObsolete = isObsolete;
}
public T GetValueModifier<T>() where T : ValueModifierPropertyAttribute
{
for (var i = 0; i < valueModifiers.Count; i++)
{
if (valueModifiers[i].attribute is T att)
return att;
}
return null;
}
public T GetAccessModifier<T>() where T : AccessModifierPropertyAttribute
{
for (var i = 0; i < accessModifiers.Count; i++)
{
if (accessModifiers[i].attribute is T att)
return att;
}
return null;
}
}
/// <summary>
/// Class: Contains attribute and drawer values
/// </summary>
/// <typeparam name="T0"></typeparam>
/// <typeparam name="T1"></typeparam>
public sealed class ResultValue<T0, T1> where T0 : AbstractPropertyAttribute
where T1 : AbstractPropertyDrawer
{
public readonly T0 attribute;
public readonly T1 drawer;
public ResultValue(T0 attribute, T1 drawer)
{
this.attribute = attribute;
this.drawer = drawer;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dd20b0ba8f5857e4d89bd475e38b7c13
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,56 @@
using System;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Module.Inspector.Editor.Utilities
{
internal static class EditorSerializedPropertyUtility
{
public static bool IsValue(Object obj, string fieldName, object value, bool useFieldValue)
{
if (obj == null || string.IsNullOrEmpty(fieldName))
return false;
var so = new SerializedObject(obj);
SerializedProperty sp = so.FindProperty(fieldName);
return IsValue(sp, value, useFieldValue);
}
public static bool IsValue(SerializedProperty sp, object value, bool useFieldValue)
{
if (sp == null)
return false;
if (useFieldValue)
{
switch (sp.propertyType)
{
case SerializedPropertyType.Generic:
return value == (object)sp.objectReferenceValue;
case SerializedPropertyType.Integer:
return value is int i && sp.intValue == i;
case SerializedPropertyType.Boolean:
return value is bool b && sp.boolValue == b;
case SerializedPropertyType.Float:
return value is float f && Mathf.Approximately(sp.floatValue, f);
case SerializedPropertyType.String:
return value is string s && sp.stringValue == s;
case SerializedPropertyType.LayerMask:
return value is LayerMask l && sp.intValue == l;
case SerializedPropertyType.Enum:
return value is Enum && sp.intValue == Convert.ToInt32(value);
}
}
else
{
if (sp.propertyType == SerializedPropertyType.Boolean)
return sp.boolValue;
if (sp.propertyType == SerializedPropertyType.ObjectReference)
return sp.objectReferenceValue != null;
}
return true;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d9c65105e5014222be899656c09dceef
timeCreated: 1610022228

View file

@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Module.Inspector.Editor
{
internal static class EditorTypeUtility
{
private const BindingFlags FIELD_FLAGS = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly Dictionary<Type, Type[]> DICT_AS_TYPES = new Dictionary<Type, Type[]>();
private static readonly Dictionary<Type, string[]> DICT_AS_STRS = new Dictionary<Type, string[]>();
private static readonly Dictionary<Type, string[]> DICT_AS_DESCS = new Dictionary<Type, string[]>();
private static readonly Dictionary<Type, FieldInfo[]> DICT_AS_FIELDS = new Dictionary<Type, FieldInfo[]>();
private static readonly Dictionary<Type, string[]> DICT_FIELDS_AS_STRS = new Dictionary<Type, string[]>();
private static readonly Dictionary<Type, string[]> DICT_FIELDS_AS_DESCS = new Dictionary<Type, string[]>();
public static Type GetType(string fullname)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (var i = 0; i < assemblies.Length; i++)
{
Assembly assembly = assemblies[i];
Type type = assembly.GetType(fullname);
if (type != null)
return type;
}
return null;
}
public static FieldInfo GetField(object target, string fieldName)
{
return GetFieldByName(target.GetType(), fieldName);
}
public static bool SetFieldValue(object target, string fieldName, object value, bool allowDuplicate)
{
if (InternalTrySetUnityObject(target, fieldName, value, allowDuplicate))
return true;
FieldInfo field = GetField(target, fieldName);
if (field == null)
return false;
try
{
field.SetValue(target, value);
if (target is Object o)
EditorUtility.SetDirty(o);
}
catch (Exception e)
{
Debug.LogException(e);
return false;
}
return true;
}
private static bool InternalTrySetUnityObject(object target, string fieldName, object value, bool allowDuplicate)
{
if (!(target is Object o))
return false;
var so = new SerializedObject(o);
SerializedProperty sp = so.FindProperty(fieldName);
bool success = sp?.TrySetValueTo(value, allowDuplicate) ?? false;
if (success)
so.ApplyModifiedProperties();
return success;
}
internal static Type[] GetAssignableFrom(Type type)
{
if (!DICT_AS_TYPES.ContainsKey(type))
InternalFetch(type);
return DICT_AS_TYPES[type];
}
internal static string[] GetAssignableFromAsStrings(Type type)
{
if (!DICT_AS_STRS.ContainsKey(type))
InternalFetch(type);
return DICT_AS_STRS[type];
}
internal static string[] GetAssignableFromAsDescriptions(Type type)
{
if (!DICT_AS_DESCS.ContainsKey(type))
InternalFetch(type);
return DICT_AS_DESCS[type];
}
internal static FieldInfo[] GetFields(Type type)
{
if (!DICT_AS_FIELDS.ContainsKey(type))
InternalFetchFields(type);
return DICT_AS_FIELDS[type];
}
internal static FieldInfo GetFieldByName(Type type, string fieldName)
{
FieldInfo[] fields = GetFields(type);
for (var i = 0; i < fields.Length; i++)
{
if (fields[i].Name.Equals(fieldName))
return fields[i];
}
return null;
}
internal static string[] GetFieldsAsStrings(Type type)
{
if (!DICT_FIELDS_AS_STRS.ContainsKey(type))
InternalFetchFields(type);
return DICT_FIELDS_AS_STRS[type];
}
internal static string[] GetFieldsAsDescriptions(Type type)
{
if (!DICT_FIELDS_AS_DESCS.ContainsKey(type))
InternalFetchFields(type);
return DICT_FIELDS_AS_DESCS[type];
}
private static void InternalFetch(Type assignableFrom)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
var listTypes = new List<Type>(32);
for (var i = 0; i < assemblies.Length; i++)
{
Assembly assembly = assemblies[i];
Type[] types = assembly.GetTypes();
for (var j = 0; j < types.Length; j++)
{
Type type = types[j];
if (!type.IsAbstract && assignableFrom.IsAssignableFrom(type))
listTypes.Add(type);
}
}
listTypes.Sort((t0, t1) => string.Compare(t0.Name, t1.Name, StringComparison.Ordinal));
var fullnames = new string[listTypes.Count];
var descs = new string[listTypes.Count];
for (var i = 0; i < fullnames.Length; i++)
{
fullnames[i] = listTypes[i].FullName;
descs[i] = $"{listTypes[i].Name} (<i>{fullnames[i]}</i>)";
}
DICT_AS_TYPES.Add(assignableFrom, listTypes.ToArray());
DICT_AS_STRS.Add(assignableFrom, fullnames);
DICT_AS_DESCS.Add(assignableFrom, descs);
}
private static void InternalFetchFields(Type type)
{
FieldInfo[] fields = type.GetFields(FIELD_FLAGS);
Array.Sort(fields, (f0, f1) => string.Compare(f0.Name, f1.Name, StringComparison.Ordinal));
var names = new string[fields.Length];
var descs = new string[fields.Length];
for (var i = 0; i < names.Length; i++)
{
names[i] = fields[i].Name;
descs[i] = $"{fields[i].Name} (<i>{fields[i].FieldType.Name}</i>)";
}
DICT_AS_FIELDS.Add(type, fields);
DICT_FIELDS_AS_STRS.Add(type, names);
DICT_FIELDS_AS_DESCS.Add(type, descs);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 762ec4eaaaf08a745929731d2379bed8
timeCreated: 1590404966