Last active
April 28, 2023 09:50
-
-
Save LotteMakesStuff/e354cf6e8a4a8194fced3323b15fc1ba to your computer and use it in GitHub Desktop.
A reimplementation of the logic in the Unity Editor that draws an inspector for a component. We can use this to learn how unity actually draws the inspector. for more info see https://www.patreon.com/posts/16724128
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEditor; | |
using System.Reflection; | |
public static class EditorExtensionMethods | |
{ | |
// Public reimplmentation of extention methods and helpers that are marked internal in UnityEditor.dll | |
public static bool IsArrayOrList(this Type listType) | |
{ | |
return listType.IsArray || (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>)); | |
} | |
public static Type GetArrayOrListElementType(this Type listType) | |
{ | |
if (listType.IsArray) | |
{ | |
return listType.GetElementType(); | |
} | |
if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>)) | |
{ | |
return listType.GetGenericArguments()[0]; | |
} | |
return null; | |
} | |
} | |
public class AssemblyHelper | |
{ | |
internal static Type[] GetTypesFromAssembly(Assembly assembly) | |
{ | |
if (assembly == null) | |
{ | |
return new Type[0]; | |
} | |
try | |
{ | |
return assembly.GetTypes(); | |
} | |
catch (ReflectionTypeLoadException) | |
{ | |
return new Type[0]; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEditor; | |
using UnityEngine; | |
// a custom inspector that forces unity to use our reimplementation of property drawing logic, rather than their own | |
// this attribute will make this match for anythig based on MononBahviour. | |
[CustomEditor(typeof(MonoBehaviour), true)] | |
public class GenericInspector : Editor | |
{ | |
public override void OnInspectorGUI() | |
{ | |
#if false // use unitys default inspector logic | |
DrawDefaultInspector(); | |
#else // use our reimplementation | |
ReflectedDrawDefaultInspector(); | |
#endif | |
} | |
// implemented in Editor.DoDrawDefaultInspector(...) | |
private void ReflectedDrawDefaultInspector() | |
{ | |
var obj = this.serializedObject; | |
EditorGUI.BeginChangeCheck(); // | |
obj.Update(); | |
SerializedProperty iterator = obj.GetIterator(); | |
bool enterChildren = true; // We *have* to enter children on the first loop. This returns all | |
// the properties on the component. After that, we do not want children, because the property | |
// drawer will handle drawing thoes | |
while (iterator.NextVisible(enterChildren)) // go over each property | |
{ | |
// inspectors start with a object link to the script file that implements the MonoBehaviour, | |
// this makes sure that link is drawn disabled | |
using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath)) | |
{ | |
// actually draw the property! | |
// this method is actually implemented in EditorGUILayout.PropertyField(...) | |
PropertyField(iterator, true); | |
} | |
enterChildren = false; | |
} | |
obj.ApplyModifiedProperties(); | |
EditorGUI.EndChangeCheck(); | |
// to make it clear that this is being drawn by our reimplementation, lets flag it with a lil watermark | |
using (new EditorGUI.DisabledScope(true)) | |
{ | |
GUILayout.BeginHorizontal(); | |
GUILayout.FlexibleSpace(); | |
GUILayout.Label("LotteMakesStuff"); | |
GUILayout.EndHorizontal(); | |
} | |
} | |
public static bool PropertyField(SerializedProperty property, bool includeChildren, params GUILayoutOption[] options) | |
{ | |
return PropertyField(property, null, includeChildren, options); | |
} | |
public static bool PropertyField(SerializedProperty property, GUIContent label, bool includeChildren, params GUILayoutOption[] options) | |
{ | |
// get the property handler and then ask it to draw. | |
return ScriptAttributeUtility.GetHandler(property).OnGUILayout(property, label, includeChildren, options); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using UnityEngine; | |
using UnityEditor; | |
using ReflectedExtentions; | |
public class PropertyHandler | |
{ | |
// handles drawing a single property to the inspector - keeps track of property and decorator drawers | |
private PropertyDrawer m_PropertyDrawer = null; | |
private List<DecoratorDrawer> m_DecoratorDrawers = null; | |
public string tooltip = null; | |
public List<ContextMenuItemAttribute> contextMenuItems = null; | |
public bool hasPropertyDrawer | |
{ | |
get | |
{ | |
return this.propertyDrawer != null; | |
} | |
} | |
private PropertyDrawer propertyDrawer | |
{ | |
get | |
{ | |
return (!this.isCurrentlyNested) ? this.m_PropertyDrawer : null; | |
} | |
} | |
private bool isCurrentlyNested | |
{ | |
get | |
{ | |
return this.m_PropertyDrawer != null && ScriptAttributeUtility.s_DrawerStack.Any() && this.m_PropertyDrawer == ScriptAttributeUtility.s_DrawerStack.Peek(); | |
} | |
} | |
public bool empty | |
{ | |
get | |
{ | |
return this.m_DecoratorDrawers == null && this.tooltip == null && this.propertyDrawer == null && this.contextMenuItems == null; | |
} | |
} | |
public void HandleAttribute(PropertyAttribute attribute, FieldInfo field, Type propertyType) | |
{ | |
if (attribute is TooltipAttribute) | |
{ | |
this.tooltip = (attribute as TooltipAttribute).tooltip; | |
} | |
else if (attribute is ContextMenuItemAttribute) | |
{ | |
if (!propertyType.IsArrayOrList()) | |
{ | |
if (this.contextMenuItems == null) | |
{ | |
this.contextMenuItems = new List<ContextMenuItemAttribute>(); | |
} | |
this.contextMenuItems.Add(attribute as ContextMenuItemAttribute); | |
} | |
} | |
else | |
{ | |
this.HandleDrawnType(attribute.GetType(), propertyType, field, attribute); | |
} | |
} | |
public void HandleDrawnType(Type drawnType, Type propertyType, FieldInfo field, PropertyAttribute attribute) | |
{ | |
Type drawerTypeForType = ScriptAttributeUtility.GetDrawerTypeForType(drawnType); | |
if (drawerTypeForType != null) | |
{ | |
if (typeof(PropertyDrawer).IsAssignableFrom(drawerTypeForType)) | |
{ | |
if (propertyType != null && propertyType.IsArrayOrList()) | |
{ | |
return; | |
} | |
this.m_PropertyDrawer = (PropertyDrawer)Activator.CreateInstance(drawerTypeForType); | |
this.m_PropertyDrawer.SetFieldInfo(field); | |
this.m_PropertyDrawer.SetAttribute(attribute); | |
} | |
else if (typeof(DecoratorDrawer).IsAssignableFrom(drawerTypeForType)) | |
{ | |
if (field != null && field.FieldType.IsArrayOrList() && !propertyType.IsArrayOrList()) | |
{ | |
return; | |
} | |
DecoratorDrawer decoratorDrawer = (DecoratorDrawer)Activator.CreateInstance(drawerTypeForType); | |
decoratorDrawer.SetAttribute(attribute); | |
if (this.m_DecoratorDrawers == null) | |
{ | |
this.m_DecoratorDrawers = new List<DecoratorDrawer>(); | |
} | |
this.m_DecoratorDrawers.Add(decoratorDrawer); | |
} | |
} | |
} | |
public bool OnGUI(Rect position, SerializedProperty property, GUIContent label, bool includeChildren) | |
{ | |
// save off the ammount of height were supposed to use | |
float heighRemaining = position.height; | |
position.height = 0f; | |
// Do we have any DecoratorDrawers? if so do em | |
if (this.m_DecoratorDrawers != null && !this.isCurrentlyNested) | |
{ | |
foreach (DecoratorDrawer decoratorDrawer in this.m_DecoratorDrawers) | |
{ | |
position.height = decoratorDrawer.GetHeight(); | |
// cache widths incase they change | |
float labelWidth = EditorGUIUtility.labelWidth; | |
float fieldWidth = EditorGUIUtility.fieldWidth; | |
// draw the decorator | |
decoratorDrawer.OnGUI(position); | |
// reset widths | |
EditorGUIUtility.labelWidth = labelWidth; | |
EditorGUIUtility.fieldWidth = fieldWidth; | |
// add update position with the space we just took up | |
position.y += position.height; | |
heighRemaining -= position.height; | |
} | |
} | |
// plug in the ammount of height left post drawing Decorators | |
position.height = heighRemaining; | |
// Do we have a property drawer? if so, use it, otherwise falldown to default drawers | |
if (this.propertyDrawer != null) | |
{ | |
float labelWidth = EditorGUIUtility.labelWidth; | |
float fieldWidth = EditorGUIUtility.fieldWidth; | |
this.propertyDrawer.OnGUISafe(position, property.Copy(), | |
label ?? Reflected.EditorGUIUtility.TempContent(property.displayName)); | |
EditorGUIUtility.labelWidth = labelWidth; | |
EditorGUIUtility.fieldWidth = fieldWidth; | |
return false; | |
} | |
if (!includeChildren) | |
{ | |
// if were not drawing nested children, fall down into the default drawer and return | |
return Reflected.EditorGUI.DefaultPropertyField(position, property, label); | |
} | |
// save off things that might change | |
Vector2 iconSize = EditorGUIUtility.GetIconSize(); | |
bool enabled = GUI.enabled; | |
int indentLevel = EditorGUI.indentLevel; | |
indentLevel = indentLevel - property.depth; | |
SerializedProperty serializedProperty = property.Copy(); // copy the itterator so we can advance it without screwing up logic elsewhere | |
SerializedProperty endProperty = serializedProperty.GetEndProperty(); | |
// draw the root property and remove its height from position, so we can draw the children in the right position | |
position.height = Reflected.EditorGUI.GetSinglePropertyHeight(serializedProperty, label); | |
EditorGUI.indentLevel = serializedProperty.depth + indentLevel; | |
bool enterChildren = Reflected.EditorGUI.DefaultPropertyField(position, serializedProperty, label) && Reflected.EditorGUI.HasVisibleChildFields(serializedProperty); | |
position.y += position.height + 2f; | |
// while we have children to jump down in to, get them to draw | |
while (serializedProperty.NextVisible(enterChildren) && !SerializedProperty.EqualContents(serializedProperty, endProperty)) | |
{ | |
EditorGUI.indentLevel = serializedProperty.depth + indentLevel; | |
position.height = EditorGUI.GetPropertyHeight(serializedProperty, null, false); | |
EditorGUI.BeginChangeCheck(); | |
enterChildren = (ScriptAttributeUtility.GetHandler(serializedProperty).OnGUI(position, serializedProperty, null, false) && Reflected.EditorGUI.HasVisibleChildFields(serializedProperty)); | |
if (EditorGUI.EndChangeCheck()) | |
{ | |
break; | |
} | |
position.y += position.height + 2f; | |
} | |
// reset things that might have changed whilst drawing nested children | |
GUI.enabled = enabled; | |
EditorGUIUtility.SetIconSize(iconSize); | |
EditorGUI.indentLevel = indentLevel; | |
return false; | |
} | |
public bool OnGUILayout(SerializedProperty property, GUIContent label, bool includeChildren, params GUILayoutOption[] options) | |
{ | |
return this.OnGUI(Reflected.EditorGUILayout.s_LastRect = ((property.propertyType != SerializedPropertyType.Boolean || this.propertyDrawer != null || (this.m_DecoratorDrawers != null && this.m_DecoratorDrawers.Count != 0)) | |
? EditorGUILayout.GetControlRect(Reflected.EditorGUI.LabelHasContent(label), this.GetHeight(property, label, includeChildren), options) | |
: Reflected.EditorGUILayout.GetToggleRect(true, options)), property, label, includeChildren); // reflected options param pass might not work? | |
} | |
// Measure this properties height | |
public float GetHeight(SerializedProperty property, GUIContent label, bool includeChildren) | |
{ | |
float height = 0f; | |
if (this.m_DecoratorDrawers != null && !this.isCurrentlyNested) | |
{ | |
// add up the heigh of all attached decorators | |
foreach (DecoratorDrawer decoratorDrawer in this.m_DecoratorDrawers) | |
{ | |
height += decoratorDrawer.GetHeight(); | |
} | |
} | |
if (this.propertyDrawer != null) | |
{ | |
// if this is drawn by a Property drawer, ask that for its height | |
height += this.propertyDrawer.GetPropertyHeightSafe(property.Copy(), label ?? Reflected.EditorGUIUtility.TempContent(property.displayName)); | |
} | |
else if (!includeChildren) | |
{ | |
// otherwise, it must use the default drawing logic. If it dosnt have children, lets ask it for its single height | |
height += Reflected.EditorGUI.GetSinglePropertyHeight(property, label); | |
} | |
else | |
{ | |
// otherwise we need to drop down and ask each child how big it is and add that to height | |
property = property.Copy(); // copy the itterator so we can advance it without screwing up logic elsewhere | |
SerializedProperty endProperty = property.GetEndProperty(); | |
height += Reflected.EditorGUI.GetSinglePropertyHeight(property, label); | |
bool enterChildren = property.isExpanded && Reflected.EditorGUI.HasVisibleChildFields(property); | |
while (property.NextVisible(enterChildren) && !SerializedProperty.EqualContents(property, endProperty)) | |
{ | |
height += ScriptAttributeUtility.GetHandler(property).GetHeight(property, Reflected.EditorGUIUtility.TempContent(property.displayName), true); | |
enterChildren = false; | |
height += 2f; | |
} | |
} | |
return height; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using UnityEditor; | |
using UnityEngine; | |
using ReflectedExtentions; | |
// clone of an internal Unity class that does stuff like keep track of PropertyHandlers and finding Attributes the inspector uses for drawing | |
internal class ScriptAttributeUtility | |
{ | |
private struct DrawerKeySet | |
{ | |
public Type drawer; | |
public Type type; | |
} | |
internal static Stack<PropertyDrawer> s_DrawerStack = new Stack<PropertyDrawer>(); | |
private static Dictionary<Type, DrawerKeySet> s_DrawerTypeForType = null; | |
private static Dictionary<string, List<PropertyAttribute>> s_BuiltinAttributes = null; | |
private static PropertyHandler s_SharedNullHandler = new PropertyHandler(); | |
private static PropertyHandler s_NextHandler = new PropertyHandler(); | |
private static PropertyHandlerCache s_GlobalCache = new PropertyHandlerCache(); | |
private static PropertyHandlerCache s_CurrentCache = null; | |
internal static PropertyHandlerCache propertyHandlerCache | |
{ | |
get | |
{ | |
return s_CurrentCache ?? s_GlobalCache; | |
} | |
set | |
{ | |
s_CurrentCache = value; | |
} | |
} | |
internal static void ClearGlobalCache() // TODO in Unity this is called by InspectorWindow.OnSelectionChange(), need to trigger this outselves from some selection change event | |
{ | |
s_GlobalCache.Clear(); | |
} | |
private static void PopulateBuiltinAttributes() | |
{ | |
s_BuiltinAttributes = new Dictionary<string, List<PropertyAttribute>>(); | |
AddBuiltinAttribute("GUIText", "m_Text", new MultilineAttribute()); | |
AddBuiltinAttribute("TextMesh", "m_Text", new MultilineAttribute()); | |
} | |
private static void AddBuiltinAttribute(string componentTypeName, string propertyPath, PropertyAttribute attr) | |
{ | |
string key = componentTypeName + "_" + propertyPath; | |
if (!s_BuiltinAttributes.ContainsKey(key)) | |
{ | |
s_BuiltinAttributes.Add(key, new List<PropertyAttribute>()); | |
} | |
s_BuiltinAttributes[key].Add(attr); | |
} | |
private static List<PropertyAttribute> GetBuiltinAttributes(SerializedProperty property) | |
{ | |
if (property.serializedObject.targetObject == null) | |
{ | |
return null; | |
} | |
Type type = property.serializedObject.targetObject.GetType(); | |
if (type == null) | |
{ | |
return null; | |
} | |
string key = type.Name + "_" + property.propertyPath; | |
List<PropertyAttribute> result = null; | |
s_BuiltinAttributes.TryGetValue(key, out result); | |
return result; | |
} | |
private static void BuildDrawerTypeForTypeDictionary() | |
{ | |
ScriptAttributeUtility.s_DrawerTypeForType = new Dictionary<Type, DrawerKeySet>(); | |
Type[] source = AppDomain.CurrentDomain.GetAssemblies().SelectMany((Assembly x) => AssemblyHelper.GetTypesFromAssembly(x)).ToArray(); | |
foreach (Type item in Reflected.EditorAssemblies.SubclassesOf(typeof(GUIDrawer))) | |
{ | |
object[] customAttributes = item.GetCustomAttributes(typeof(CustomPropertyDrawer), true); | |
object[] array = customAttributes; | |
for (int i = 0; i < array.Length; i++) | |
{ | |
CustomPropertyDrawer editor = (CustomPropertyDrawer)array[i]; | |
//ScriptAttributeUtility.s_DrawerTypeForType[editor.m_Type] = new DrawerKeySet | |
s_DrawerTypeForType[editor.GetHiddenType()] = new DrawerKeySet | |
{ | |
drawer = item, | |
type = editor.GetHiddenType() /*editor.m_Type*/ | |
}; | |
if (editor.GetUseForChildren() /*editor.m_UseForChildren*/) | |
{ | |
IEnumerable<Type> enumerable = from x in source | |
where x.IsSubclassOf(editor.GetHiddenType() /*editor.m_Type*/) | |
select x; | |
foreach (Type item2 in enumerable) | |
{ | |
if (ScriptAttributeUtility.s_DrawerTypeForType.ContainsKey(item2)) | |
{ | |
Type type = editor.GetHiddenType(); //editor.m_Type; | |
DrawerKeySet drawerKeySet = ScriptAttributeUtility.s_DrawerTypeForType[item2]; | |
if (!type.IsAssignableFrom(drawerKeySet.type)) | |
{ | |
goto IL_0158; | |
} | |
continue; | |
} | |
goto IL_0158; | |
IL_0158: | |
ScriptAttributeUtility.s_DrawerTypeForType[item2] = new DrawerKeySet | |
{ | |
drawer = item, | |
type = editor.GetHiddenType() //editor.m_Type; | |
}; | |
} | |
} | |
} | |
} | |
} | |
internal static Type GetDrawerTypeForType(Type type) | |
{ | |
if (ScriptAttributeUtility.s_DrawerTypeForType == null) | |
{ | |
ScriptAttributeUtility.BuildDrawerTypeForTypeDictionary(); | |
} | |
DrawerKeySet drawerKeySet = default(DrawerKeySet); | |
ScriptAttributeUtility.s_DrawerTypeForType.TryGetValue(type, out drawerKeySet); | |
if (drawerKeySet.drawer != null) | |
{ | |
return drawerKeySet.drawer; | |
} | |
if (type.IsGenericType) | |
{ | |
ScriptAttributeUtility.s_DrawerTypeForType.TryGetValue(type.GetGenericTypeDefinition(), out drawerKeySet); | |
} | |
return drawerKeySet.drawer; | |
} | |
private static List<PropertyAttribute> GetFieldAttributes(FieldInfo field) | |
{ | |
if (field == null) | |
{ | |
return null; | |
} | |
object[] customAttributes = field.GetCustomAttributes(typeof(PropertyAttribute), true); | |
if (customAttributes != null && customAttributes.Length > 0) | |
{ | |
return new List<PropertyAttribute>(from e in customAttributes | |
select e as PropertyAttribute into e | |
orderby -e.order | |
select e); | |
} | |
return null; | |
} | |
private static FieldInfo GetFieldInfoFromProperty(SerializedProperty property, out Type type) | |
{ | |
Type scriptTypeFromProperty = ScriptAttributeUtility.GetScriptTypeFromProperty(property); | |
if (scriptTypeFromProperty == null) | |
{ | |
type = null; | |
return null; | |
} | |
return ScriptAttributeUtility.GetFieldInfoFromPropertyPath(scriptTypeFromProperty, property.propertyPath, out type); | |
} | |
private static Type GetScriptTypeFromProperty(SerializedProperty property) | |
{ | |
SerializedProperty serializedProperty = property.serializedObject.FindProperty("m_Script"); | |
if (serializedProperty == null) | |
{ | |
return null; | |
} | |
MonoScript monoScript = serializedProperty.objectReferenceValue as MonoScript; | |
if ((UnityEngine.Object)monoScript == (UnityEngine.Object)null) | |
{ | |
return null; | |
} | |
return monoScript.GetClass(); | |
} | |
private static FieldInfo GetFieldInfoFromPropertyPath(Type host, string path, out Type type) | |
{ | |
FieldInfo fieldInfo = null; | |
type = host; | |
string[] array = path.Split('.'); | |
for (int i = 0; i < array.Length; i++) | |
{ | |
string text = array[i]; | |
if (i < array.Length - 1 && text == "Array" && array[i + 1].StartsWith("data[")) | |
{ | |
if (type.IsArrayOrList()) | |
{ | |
type = type.GetArrayOrListElementType(); | |
} | |
i++; | |
} | |
else | |
{ | |
FieldInfo fieldInfo2 = null; | |
Type type2 = type; | |
while (fieldInfo2 == null && type2 != null) | |
{ | |
fieldInfo2 = type2.GetField(text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); | |
type2 = type2.BaseType; | |
} | |
if (fieldInfo2 == null) | |
{ | |
type = null; | |
return null; | |
} | |
fieldInfo = fieldInfo2; | |
type = fieldInfo.FieldType; | |
} | |
} | |
return fieldInfo; | |
} | |
internal static PropertyHandler GetHandler(SerializedProperty property) | |
{ | |
if (property == null) | |
{ | |
return ScriptAttributeUtility.s_SharedNullHandler; | |
} | |
if (property.serializedObject.GetInspectorMode() != 0) | |
{ | |
return ScriptAttributeUtility.s_SharedNullHandler; | |
} | |
PropertyHandler handler = ScriptAttributeUtility.propertyHandlerCache.GetHandler(property); | |
if (handler != null) | |
{ | |
return handler; | |
} | |
Type type = null; | |
List<PropertyAttribute> list = null; | |
FieldInfo field = null; | |
UnityEngine.Object targetObject = property.serializedObject.targetObject; | |
if (targetObject is MonoBehaviour || targetObject is ScriptableObject) | |
{ | |
field = ScriptAttributeUtility.GetFieldInfoFromProperty(property, out type); | |
list = ScriptAttributeUtility.GetFieldAttributes(field); | |
} | |
else | |
{ | |
if (ScriptAttributeUtility.s_BuiltinAttributes == null) | |
{ | |
ScriptAttributeUtility.PopulateBuiltinAttributes(); | |
} | |
if (list == null) | |
{ | |
list = ScriptAttributeUtility.GetBuiltinAttributes(property); | |
} | |
} | |
handler = ScriptAttributeUtility.s_NextHandler; | |
if (list != null) | |
{ | |
for (int num = list.Count - 1; num >= 0; num--) | |
{ | |
handler.HandleAttribute(list[num], field, type); | |
} | |
} | |
if (!handler.hasPropertyDrawer && type != null) | |
{ | |
handler.HandleDrawnType(type, type, field, null); | |
} | |
if (handler.empty) | |
{ | |
ScriptAttributeUtility.propertyHandlerCache.SetHandler(property, ScriptAttributeUtility.s_SharedNullHandler); | |
handler = ScriptAttributeUtility.s_SharedNullHandler; | |
} | |
else | |
{ | |
ScriptAttributeUtility.propertyHandlerCache.SetHandler(property, handler); | |
ScriptAttributeUtility.s_NextHandler = new PropertyHandler(); | |
} | |
return handler; | |
} | |
} | |
// stores PropertyHandlers for drawing properties in a dictionary against property hashes | |
internal class PropertyHandlerCache | |
{ | |
protected Dictionary<int, PropertyHandler> m_PropertyHandlers = new Dictionary<int, PropertyHandler>(); | |
internal PropertyHandler GetHandler(SerializedProperty property) | |
{ | |
int propertyHash = GetPropertyHash(property); | |
PropertyHandler result = null; | |
if (this.m_PropertyHandlers.TryGetValue(propertyHash, out result)) | |
{ | |
return result; | |
} | |
return null; | |
} | |
internal void SetHandler(SerializedProperty property, PropertyHandler handler) | |
{ | |
int propertyHash = GetPropertyHash(property); | |
m_PropertyHandlers[propertyHash] = handler; | |
} | |
private static int GetPropertyHash(SerializedProperty property) | |
{ | |
if (property.serializedObject.targetObject == null) | |
{ | |
return 0; | |
} | |
int num = property.serializedObject.targetObject.GetInstanceID() ^ property.GetHashCodeForPropertyPathWithoutArrayIndex(); /*property.hashCodeForPropertyPathWithoutArrayIndex;*/ | |
if (property.propertyType == SerializedPropertyType.ObjectReference) | |
{ | |
num ^= property.objectReferenceInstanceIDValue; | |
} | |
return num; | |
} | |
public void Clear() | |
{ | |
this.m_PropertyHandlers.Clear(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using UnityEngine; | |
using UnityEditor; | |
using Reflected; | |
namespace Reflected | |
{ | |
// provides easy access to a bunch of APIs that are marked as internal in the UnityEditor. we wimply copy the method signiture | |
// and use reflection to get access to stuff were not supposed to | |
public static class EditorGUIUtility | |
{ | |
internal static GUIContent TempContent(string t) | |
{ | |
var bindflags = BindingFlags.NonPublic | BindingFlags.Static; | |
var method = typeof(UnityEditor.EditorGUIUtility).GetMethod("TempContent", bindflags, null, new[] { typeof(string) }, null ); | |
return (GUIContent)method.Invoke(null, new [] { t }); | |
} | |
} | |
public static class EditorGUILayout | |
{ | |
public static Rect s_LastRect | |
{ | |
get | |
{ | |
return ReflectionHelper.GetField<Rect>("s_LastRect", typeof(UnityEditor.EditorGUILayout)); | |
} | |
set | |
{ | |
ReflectionHelper.SetField<Rect>("s_LastRect", typeof(UnityEditor.EditorGUILayout), value); | |
} | |
} | |
internal static Rect GetToggleRect(bool hasLabel, params GUILayoutOption[] options) | |
{ | |
return (Rect)ReflectionHelper.GetReflectedMethod("GetToggleRect", typeof(UnityEditor.EditorGUILayout)).Invoke(null, new object[] { hasLabel, options }); | |
} | |
} | |
public static class EditorGUI | |
{ | |
public static bool LabelHasContent(GUIContent label) | |
{ | |
if (label == null) | |
{ | |
return true; | |
} | |
return label.text != string.Empty || label.image != null; | |
} | |
public static float GetSinglePropertyHeight(SerializedProperty property, GUIContent label) | |
{ | |
return (float)ReflectionHelper.GetReflectedMethod("GetSinglePropertyHeight", typeof(UnityEditor.EditorGUI)).Invoke(null, new object[] { property, label }); | |
} | |
internal static bool HasVisibleChildFields(SerializedProperty property) | |
{ | |
return (bool)ReflectionHelper.GetReflectedMethod("HasVisibleChildFields", typeof(UnityEditor.EditorGUI)).Invoke(null, new object[] { property }); | |
} | |
internal static bool DefaultPropertyField(Rect position, SerializedProperty property, GUIContent label) | |
{ | |
return (bool)ReflectionHelper.GetReflectedMethod("DefaultPropertyField", typeof(UnityEditor.EditorGUI)).Invoke(null, new object[] { position, property, label }); | |
} | |
} | |
public class EditorAssemblies | |
{ | |
internal static IEnumerable<Type> SubclassesOf(Type parent) | |
{ | |
Type hiddenType = ReflectionHelper.GetPrivateType("UnityEditor.EditorAssemblies", typeof(CustomEditor)); | |
return (IEnumerable<Type>)ReflectionHelper.GetReflectedMethod("SubclassesOf", hiddenType).Invoke(null, new object[] { parent }); | |
} | |
} | |
public static class ReflectionHelper | |
{ | |
public static Type GetPrivateType(string name, Type source) | |
{ | |
var assembly = source.Assembly; | |
return assembly.GetType(name); | |
} | |
public static Type GetPrivateType(string fqName) | |
{ | |
return Type.GetType(fqName); | |
} | |
public static T GetField<T>(string name, Type type, bool isStatic = true, object instance = null) | |
{ | |
var bindflags = isStatic ? (BindingFlags.NonPublic | BindingFlags.Static) : (BindingFlags.NonPublic | BindingFlags.Instance); | |
var field = type.GetField(name, bindflags); | |
return (T)field.GetValue(instance); | |
} | |
public static void SetField<T>(string name, Type type, T value, bool isStatic = true, object instantce = null) | |
{ | |
var bindflags = isStatic ? (BindingFlags.NonPublic | BindingFlags.Static) : (BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); | |
var field = type.GetField(name, bindflags); | |
if (instantce != null) | |
{ | |
field = instantce.GetType().GetField(name, bindflags); | |
} | |
field.SetValue(instantce, value); | |
} | |
public static T GetProperty<T>(string name, Type type, object instance) | |
{ | |
var bindflags = BindingFlags.NonPublic | BindingFlags.Instance; | |
var propInfo = type.GetProperty(name, bindflags); | |
MethodInfo getAccessor = propInfo.GetGetMethod(true); | |
return (T)getAccessor.Invoke(instance, null); | |
} | |
public static MethodInfo GetReflectedMethod(string name, Type type, bool isStatic = true, object instantce = null) | |
{ | |
var bindflags = isStatic ? (BindingFlags.NonPublic | BindingFlags.Static) : (BindingFlags.NonPublic | BindingFlags.Instance); | |
var method = type.GetMethod(name, bindflags); | |
return method; | |
} | |
} | |
} | |
namespace ReflectedExtentions | |
{ | |
public static class PropertyDrawerExtention | |
{ | |
public static float GetPropertyHeightSafe(this PropertyDrawer drawer, SerializedProperty property, GUIContent label) | |
{ | |
return (float)ReflectionHelper.GetReflectedMethod("GetPropertyHeightSafe", typeof(UnityEditor.PropertyDrawer), false, drawer).Invoke(drawer, new object[] { property, label }); | |
} | |
public static void OnGUISafe(this PropertyDrawer drawer, Rect position, SerializedProperty property, GUIContent label) | |
{ | |
ReflectionHelper.GetReflectedMethod("OnGUISafe", typeof(UnityEditor.PropertyDrawer), false, drawer).Invoke(drawer, new object[] { position, property, label }); | |
} | |
public static void SetFieldInfo(this PropertyDrawer drawer, FieldInfo info) | |
{ | |
ReflectionHelper.SetField("m_FieldInfo", typeof(PropertyDrawer), info, false, drawer); | |
} | |
public static void SetAttribute(this PropertyDrawer drawer, PropertyAttribute attrib) | |
{ | |
ReflectionHelper.SetField("m_Attribute", typeof(PropertyDrawer), attrib, false, drawer); | |
} | |
} | |
public static class DecoratorDrawerExtention | |
{ | |
public static void SetAttribute(this DecoratorDrawer drawer, PropertyAttribute attrib) | |
{ | |
ReflectionHelper.SetField("m_Attribute", typeof(DecoratorDrawer), attrib, false, drawer); | |
} | |
} | |
public static class CustomPropertyDrawerExtentions | |
{ | |
public static Type GetHiddenType(this CustomPropertyDrawer prop) | |
{ | |
return ReflectionHelper.GetField<Type>("m_Type", typeof(CustomPropertyDrawer), false, prop); | |
} | |
public static bool GetUseForChildren(this CustomPropertyDrawer prop) | |
{ | |
return ReflectionHelper.GetField<bool>("m_UseForChildren", typeof(CustomPropertyDrawer), false, prop); | |
} | |
} | |
public static class SerializedPropertyExtentions | |
{ | |
public static int GetHashCodeForPropertyPathWithoutArrayIndex(this SerializedProperty prop) | |
{ | |
return ReflectionHelper.GetProperty<int>("hashCodeForPropertyPathWithoutArrayIndex", typeof(SerializedProperty), prop); | |
} | |
} | |
public static class SerializedObjectExtentions | |
{ | |
public static int GetInspectorMode(this SerializedObject prop) | |
{ | |
//return ReflectionHelper.GetField<int>("inspectorMode", typeof(SerializedObject), false, prop); | |
return ReflectionHelper.GetProperty<int>("inspectorMode", typeof(SerializedObject), prop); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment