forked from Shardstone/trail-into-darkness
changed directory structure
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using Jovian.Utilities;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
#endif
|
||||
|
||||
namespace Jovian.InspectorTools {
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class ShowInInspectorAttribute : PropertyAttribute {
|
||||
public readonly PlayModeVisibility Visibility;
|
||||
public readonly bool isEdittingSupported;
|
||||
|
||||
public ShowInInspectorAttribute() {
|
||||
Visibility = PlayModeVisibility.PlayMode;
|
||||
isEdittingSupported = false;
|
||||
}
|
||||
|
||||
public ShowInInspectorAttribute(PlayModeVisibility playModeVisibility, bool isEdittingSupported = false) {
|
||||
Visibility = playModeVisibility;
|
||||
this.isEdittingSupported = isEdittingSupported;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace Jovian.InspectorTools.Internal {
|
||||
public class ShowInInspectorAttributeHandler {
|
||||
private const int MAX_DEPTH = 10;
|
||||
|
||||
private bool _initialized;
|
||||
private readonly UnityObject target;
|
||||
private readonly SerializedObject serializedObject;
|
||||
private List<Member> showMembers;
|
||||
|
||||
private GUIStyle labelStyle;
|
||||
private GUIStyle tooltipStyle;
|
||||
|
||||
private class Member {
|
||||
public MemberInfo member;
|
||||
public readonly PlayModeVisibility visibility;
|
||||
public readonly bool isEditingSupported;
|
||||
private readonly object parent;
|
||||
public bool supportsExpansion;
|
||||
public bool expanded;
|
||||
public Member parentMember;
|
||||
public int depth;
|
||||
|
||||
public string niceName;
|
||||
public string name;
|
||||
|
||||
public bool IsChild => parentMember != null;
|
||||
public bool IsParentExpanded => parentMember.expanded;
|
||||
|
||||
public Member(MemberInfo member,
|
||||
PlayModeVisibility visibility,
|
||||
bool isEditingSupported,
|
||||
object parent,
|
||||
int depth,
|
||||
Member parentMember) {
|
||||
this.member = member;
|
||||
this.visibility = visibility;
|
||||
this.isEditingSupported = isEditingSupported;
|
||||
this.parent = parent;
|
||||
this.parentMember = parentMember;
|
||||
this.depth = depth;
|
||||
name = member.Name;
|
||||
niceName = ObjectNames.NicifyVariableName(name);
|
||||
}
|
||||
|
||||
public bool TryGetValueAndData(out object value) {
|
||||
switch (member) {
|
||||
case FieldInfo fieldInfo:
|
||||
value = fieldInfo.GetValue(parent);
|
||||
return true;
|
||||
case PropertyInfo propertyInfo:
|
||||
value = propertyInfo.GetValue(parent);
|
||||
return true;
|
||||
default:
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Type GetValueType() {
|
||||
return member switch {
|
||||
FieldInfo fieldInfo => fieldInfo.FieldType,
|
||||
PropertyInfo propertyInfo => propertyInfo.PropertyType,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
|
||||
public void SetValue(object value) {
|
||||
switch (member) {
|
||||
case FieldInfo fieldInfo:
|
||||
fieldInfo.SetValue(parent, value);
|
||||
break;
|
||||
case PropertyInfo propertyInfo:
|
||||
propertyInfo.SetValue(parent, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasValues => showMembers.Count > 0;
|
||||
|
||||
public ShowInInspectorAttributeHandler(UnityObject target, SerializedObject serializedObject) {
|
||||
this.target = target;
|
||||
this.serializedObject = serializedObject;
|
||||
showMembers = new();
|
||||
}
|
||||
|
||||
public void Update() {
|
||||
serializedObject.Update();
|
||||
Setup();
|
||||
}
|
||||
|
||||
private void Setup() {
|
||||
if (_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
showMembers.Clear();
|
||||
|
||||
int fieldCount = EditorTypes.GetFields(target, out List<FieldInfo> objectFields);
|
||||
for (int i = 0; i < fieldCount; i++) {
|
||||
var showAttribute = Attribute.GetCustomAttribute(objectFields[i], typeof(ShowInInspectorAttribute)) as ShowInInspectorAttribute;
|
||||
if (showAttribute == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SetupMember(objectFields[i], objectFields[i].GetValue(target), showAttribute.Visibility, showAttribute.isEdittingSupported, target);
|
||||
}
|
||||
|
||||
int propertiesCount = EditorTypes.GetProperties(target, out List<PropertyInfo> objectProperties);
|
||||
for (int i = 0; i < propertiesCount; i++) {
|
||||
var showAttribute = Attribute.GetCustomAttribute(objectProperties[i], typeof(ShowInInspectorAttribute)) as ShowInInspectorAttribute;
|
||||
if (showAttribute == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SetupMember(objectProperties[i], objectProperties[i].GetValue(target), showAttribute.Visibility, showAttribute.isEdittingSupported, target);
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
private void SetupMember(MemberInfo memberInfo,
|
||||
object memberValue,
|
||||
PlayModeVisibility visibility,
|
||||
bool isEditingSupported,
|
||||
object parent,
|
||||
int depth = 0,
|
||||
Member parentMember = null) {
|
||||
Member member = new(memberInfo, visibility, isEditingSupported, parent, depth, parentMember);
|
||||
|
||||
if (depth >= MAX_DEPTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
showMembers.Add(member);
|
||||
if (memberValue != null && CanValueExpand(memberValue)) {
|
||||
|
||||
Type memberType = memberValue.GetType();
|
||||
foreach (FieldInfo fieldInfo in memberType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
|
||||
ShowInInspectorAttribute attribute = fieldInfo.GetCustomAttribute<ShowInInspectorAttribute>();
|
||||
if (attribute != null) {
|
||||
SetupMember(fieldInfo, fieldInfo.GetValue(memberValue), attribute.Visibility, attribute.isEdittingSupported, memberValue, depth + 1, member);
|
||||
member.supportsExpansion = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (PropertyInfo propertyInfo in memberType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
|
||||
ShowInInspectorAttribute attribute = propertyInfo.GetCustomAttribute<ShowInInspectorAttribute>();
|
||||
if (attribute != null) {
|
||||
SetupMember(propertyInfo, propertyInfo.GetValue(memberValue), attribute.Visibility, attribute.isEdittingSupported, memberValue, depth + 1, member);
|
||||
member.supportsExpansion = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetValueTypeFromEnumerable(object value, out Type type) {
|
||||
Type[] arguments = value.GetType().GenericTypeArguments;
|
||||
if (arguments.Length > 0) {
|
||||
if (value is IDictionary) {
|
||||
type = value.GetType().GenericTypeArguments[1]; // value
|
||||
return true;
|
||||
}
|
||||
else if (value is IEnumerable) {
|
||||
type = value.GetType().GenericTypeArguments[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
type = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnInspectorGUI(Editor editor) {
|
||||
labelStyle ??= new GUIStyle(EditorStyles.label) {
|
||||
alignment = TextAnchor.UpperLeft,
|
||||
padding = new RectOffset(0, 0, 2, 2)
|
||||
};
|
||||
|
||||
tooltipStyle ??= new GUIStyle(EditorStyles.miniTextField) {
|
||||
fixedHeight = 0f,
|
||||
stretchHeight = true,
|
||||
wordWrap = true,
|
||||
stretchWidth = false,
|
||||
imagePosition = ImagePosition.TextOnly,
|
||||
clipping = TextClipping.Overflow,
|
||||
border = new RectOffset(0, 0, 0, 0)
|
||||
};
|
||||
|
||||
int indentLevel = EditorGUI.indentLevel;
|
||||
|
||||
foreach (Member entry in showMembers) {
|
||||
if (entry.visibility.IsVisible() == false || (entry.IsChild && entry.IsParentExpanded == false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Type valueType = entry.GetValueType();
|
||||
string stringValue = "";
|
||||
object value = null;
|
||||
|
||||
EditorGUI.indentLevel = indentLevel + entry.depth;
|
||||
|
||||
if (entry.TryGetValueAndData(out value)) {
|
||||
if (value is IList list) {
|
||||
stringValue = list.ListToString(true);
|
||||
}
|
||||
else if (value is IEnumerable enumerable) {
|
||||
stringValue = enumerable.EnumerableToString(true);
|
||||
}
|
||||
else {
|
||||
stringValue = value?.ToString();
|
||||
}
|
||||
}
|
||||
else {
|
||||
stringValue = "<NULL>";
|
||||
}
|
||||
|
||||
string labelName = entry.niceName;
|
||||
|
||||
if (entry.supportsExpansion) {
|
||||
entry.expanded = EditorGUILayout.Foldout(entry.expanded, labelName, true);
|
||||
}
|
||||
else {
|
||||
bool isGUIEnabled = GUI.enabled;
|
||||
bool allowEdit = entry.isEditingSupported;
|
||||
GUI.enabled = allowEdit;
|
||||
|
||||
Rect entryRect;
|
||||
|
||||
if (value is IEnumerable enumerable && TryGetValueTypeFromEnumerable(value, out Type enumValueType)) {
|
||||
|
||||
string labelText = $"{labelName}";
|
||||
if (value is ICollection collection) {
|
||||
labelText += $" (Count={collection.Count})";
|
||||
}
|
||||
|
||||
string collectionInfo = $"({value.GetType().Name}<{enumValueType.Name}>)";
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
entry.expanded = EditorGUILayout.Foldout(entry.expanded, labelText, false);
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.Label(collectionInfo, EditorStyles.miniLabel);
|
||||
GUILayout.EndHorizontal();
|
||||
entryRect = GUILayoutUtility.GetLastRect();
|
||||
|
||||
if (entry.expanded) {
|
||||
EditorGUI.indentLevel++;
|
||||
int counter = 0;
|
||||
|
||||
if (enumerable is IDictionary dictionary) {
|
||||
foreach (DictionaryEntry keyValuePair in dictionary) {
|
||||
object newValue = InspectorGUIUtility.DrawField(keyValuePair.Key.ToString(), enumValueType, keyValuePair.Value);
|
||||
if (newValue != value && allowEdit) {
|
||||
entry.SetValue(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach (var item in enumerable) {
|
||||
object newValue = InspectorGUIUtility.DrawField((counter++).ToString(), enumValueType, item);
|
||||
if (newValue != value && allowEdit) {
|
||||
entry.SetValue(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
object newValue = InspectorGUIUtility.DrawField(labelName, valueType, value);
|
||||
entryRect = GUILayoutUtility.GetLastRect();
|
||||
if (newValue != value && allowEdit) {
|
||||
entry.SetValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GUI.enabled = isGUIEnabled;
|
||||
if (entryRect.Contains(Event.current.mousePosition)) {
|
||||
Vector2 tooltipPosition = Event.current.mousePosition;
|
||||
float width = entryRect.width - (tooltipPosition.x - entryRect.x);
|
||||
|
||||
tooltipStyle.fixedWidth = width;
|
||||
|
||||
var tooltipContent = new GUIContent(stringValue, stringValue);
|
||||
float height = tooltipStyle.CalcHeight(tooltipContent, width);
|
||||
Vector2 tooltipSize = new(width, height);
|
||||
tooltipPosition.y -= tooltipSize.y;
|
||||
var tooltipRect = new Rect(tooltipPosition, tooltipSize);
|
||||
GUI.Label(tooltipRect, tooltipContent, tooltipStyle);
|
||||
|
||||
if (Event.current.type == EventType.ContextClick) {
|
||||
GenericMenu menu = new();
|
||||
menu.AddItem(new GUIContent("Copy"), false, () => { EditorGUIUtility.systemCopyBuffer = stringValue; });
|
||||
menu.ShowAsContext();
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
editor.Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel = indentLevel;
|
||||
}
|
||||
|
||||
private static readonly Type[] NonExpandedTypes = new Type[] {
|
||||
typeof(string),
|
||||
typeof(decimal),
|
||||
typeof(DateTime),
|
||||
typeof(DateTimeOffset),
|
||||
typeof(TimeSpan),
|
||||
typeof(Guid)
|
||||
};
|
||||
|
||||
// Use this to stop expansion of types we dont want, including Unity Objects - they just link to the object.
|
||||
private static bool CanValueExpand(object value) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Type type = value.GetType();
|
||||
if (IsSimpleType(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == typeof(UnityEngine.Object) || type.IsSubclassOf(typeof(UnityEngine.Object))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/a/32337906/584774
|
||||
private static bool IsSimpleType(Type type) {
|
||||
return type.IsPrimitive || type.IsEnum || NonExpandedTypes.Contains(type) || Convert.GetTypeCode(type) != TypeCode.Object ||
|
||||
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && IsSimpleType(type.GetGenericArguments()[0]));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user