forked from Shardstone/trail-into-darkness
changed directory structure
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.InspectorTools {
|
||||
/// <summary>
|
||||
/// Conditionally Show/Hide field in inspector, based on some other field value
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||
public class ConditionalFieldAttribute : PropertyAttribute {
|
||||
public readonly string FieldToCheck;
|
||||
public readonly string[] CompareValues;
|
||||
public readonly bool Inverse;
|
||||
|
||||
/// <param name="fieldToCheck">String name of field to check value</param>
|
||||
/// <param name="inverse">Inverse check result</param>
|
||||
/// <param name="compareValues">On which values field will be shown in inspector</param>
|
||||
public ConditionalFieldAttribute(string fieldToCheck, bool inverse = false, params object[] compareValues) {
|
||||
FieldToCheck = fieldToCheck;
|
||||
Inverse = inverse;
|
||||
CompareValues = compareValues.Select(c => c.ToString().ToUpper()).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace Jovian.InspectorTools.Internal {
|
||||
using EditorTools;
|
||||
using UnityEditor;
|
||||
|
||||
[CustomPropertyDrawer(typeof(ConditionalFieldAttribute))]
|
||||
public class ConditionalFieldAttributeDrawer : PropertyDrawer {
|
||||
private bool _toShow = true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Key is Associated with drawer type (the T in [CustomPropertyDrawer(typeof(T))])
|
||||
/// Value is PropertyDrawer Type
|
||||
/// </summary>
|
||||
private static Dictionary<Type, Type> _allPropertyDrawersInDomain;
|
||||
|
||||
|
||||
private bool _initialized;
|
||||
private PropertyDrawer _customAttributeDrawer;
|
||||
private PropertyDrawer _customTypeDrawer;
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
|
||||
if(!(attribute is ConditionalFieldAttribute conditional)) return 0;
|
||||
|
||||
Initialize(property);
|
||||
|
||||
var propertyToCheck = ConditionalFieldUtility.FindRelativeProperty(property, conditional.FieldToCheck);
|
||||
_toShow = ConditionalFieldUtility.PropertyIsVisible(propertyToCheck, conditional.Inverse, conditional.CompareValues);
|
||||
if(!_toShow) return 0;
|
||||
|
||||
if(_customAttributeDrawer != null) return _customAttributeDrawer.GetPropertyHeight(property, label);
|
||||
if(_customTypeDrawer != null) return _customTypeDrawer.GetPropertyHeight(property, label);
|
||||
|
||||
return EditorGUI.GetPropertyHeight(property);
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
||||
if(!_toShow) return;
|
||||
|
||||
if(_customAttributeDrawer != null) TryUseAttributeDrawer();
|
||||
else if(_customTypeDrawer != null) TryUseTypeDrawer();
|
||||
else EditorGUI.PropertyField(position, property, label, true);
|
||||
|
||||
|
||||
void TryUseAttributeDrawer() {
|
||||
try {
|
||||
_customAttributeDrawer.OnGUI(position, property, label);
|
||||
}
|
||||
catch(Exception e) {
|
||||
EditorGUI.PropertyField(position, property, label);
|
||||
LogWarning("Unable to use Custom Attribute Drawer " + _customAttributeDrawer.GetType() + " : " + e, property);
|
||||
}
|
||||
}
|
||||
|
||||
void TryUseTypeDrawer() {
|
||||
try {
|
||||
_customTypeDrawer.OnGUI(position, property, label);
|
||||
}
|
||||
catch(Exception e) {
|
||||
EditorGUI.PropertyField(position, property, label);
|
||||
LogWarning("Unable to instantiate " + fieldInfo.FieldType + " : " + e, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Initialize(SerializedProperty property) {
|
||||
if(_initialized) return;
|
||||
|
||||
CacheAllDrawersInDomain();
|
||||
|
||||
TryGetCustomAttributeDrawer();
|
||||
TryGetCustomTypeDrawer();
|
||||
|
||||
_initialized = true;
|
||||
|
||||
|
||||
void CacheAllDrawersInDomain() {
|
||||
if(!_allPropertyDrawersInDomain.IsNullOrEmpty()) return;
|
||||
|
||||
_allPropertyDrawersInDomain = new Dictionary<Type, Type>();
|
||||
var propertyDrawerType = typeof(PropertyDrawer);
|
||||
|
||||
var allDrawerTypesInDomain = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(x => x.GetTypes())
|
||||
.Where(t => propertyDrawerType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
|
||||
|
||||
foreach(var type in allDrawerTypesInDomain) {
|
||||
var drawerAttribute = CustomAttributeData.GetCustomAttributes(type).FirstOrDefault();
|
||||
if(drawerAttribute == null) continue;
|
||||
var associatedType = drawerAttribute.ConstructorArguments.FirstOrDefault().Value as Type;
|
||||
if(associatedType == null) continue;
|
||||
|
||||
if(_allPropertyDrawersInDomain.ContainsKey(associatedType)) continue;
|
||||
_allPropertyDrawersInDomain.Add(associatedType, type);
|
||||
}
|
||||
}
|
||||
|
||||
void TryGetCustomAttributeDrawer() {
|
||||
if(fieldInfo == null) return;
|
||||
//Get the second attribute flag
|
||||
var secondAttribute = (PropertyAttribute)fieldInfo.GetCustomAttributes(typeof(PropertyAttribute), false)
|
||||
.FirstOrDefault(a => !(a is ConditionalFieldAttribute));
|
||||
if(secondAttribute == null) return;
|
||||
var genericAttributeType = secondAttribute.GetType();
|
||||
|
||||
//Get the associated attribute drawer
|
||||
if(!_allPropertyDrawersInDomain.ContainsKey(genericAttributeType)) return;
|
||||
|
||||
var customAttributeDrawerType = _allPropertyDrawersInDomain[genericAttributeType];
|
||||
var customAttributeData = fieldInfo.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType == secondAttribute.GetType());
|
||||
if(customAttributeData == null) return;
|
||||
|
||||
|
||||
//Create drawer for custom attribute
|
||||
try {
|
||||
_customAttributeDrawer = (PropertyDrawer)Activator.CreateInstance(customAttributeDrawerType);
|
||||
var attributeField = customAttributeDrawerType.GetField("m_Attribute", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if(attributeField != null) attributeField.SetValue(_customAttributeDrawer, secondAttribute);
|
||||
}
|
||||
catch(Exception e) {
|
||||
LogWarning("Unable to construct drawer for " + secondAttribute.GetType() + " : " + e, property);
|
||||
}
|
||||
}
|
||||
|
||||
void TryGetCustomTypeDrawer() {
|
||||
if(fieldInfo == null) return;
|
||||
// Skip checks for mscorlib.dll
|
||||
if(fieldInfo.FieldType.Module.ScopeName.Equals(typeof(int).Module.ScopeName)) return;
|
||||
|
||||
|
||||
// Of all property drawers in the assembly we need to find one that affects target type
|
||||
// or one of the base types of target type
|
||||
Type fieldDrawerType = null;
|
||||
Type fieldType = fieldInfo.FieldType;
|
||||
while(fieldType != null) {
|
||||
if(_allPropertyDrawersInDomain.ContainsKey(fieldType)) {
|
||||
fieldDrawerType = _allPropertyDrawersInDomain[fieldType];
|
||||
break;
|
||||
}
|
||||
|
||||
fieldType = fieldType.BaseType;
|
||||
}
|
||||
|
||||
if(fieldDrawerType == null) return;
|
||||
|
||||
//Create instances of each (including the arguments)
|
||||
try {
|
||||
_customTypeDrawer = (PropertyDrawer)Activator.CreateInstance(fieldDrawerType);
|
||||
}
|
||||
catch(Exception e) {
|
||||
LogWarning("No constructor available in " + fieldType + " : " + e, property);
|
||||
return;
|
||||
}
|
||||
|
||||
//Reassign the attribute field in the drawer so it can access the argument values
|
||||
var attributeField = fieldDrawerType.GetField("m_Attribute", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if(attributeField != null) attributeField.SetValue(_customTypeDrawer, attribute);
|
||||
var fieldInfoField = fieldDrawerType.GetField("m_FieldInfo", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if(fieldInfoField != null) fieldInfoField.SetValue(_customTypeDrawer, fieldInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogWarning(string log, SerializedProperty property) {
|
||||
var warning = "Property <color=brown>" + fieldInfo.Name + "</color>";
|
||||
if(fieldInfo != null && fieldInfo.DeclaringType != null)
|
||||
warning += " on behaviour <color=brown>" + fieldInfo.DeclaringType.Name + "</color>";
|
||||
warning += " caused: " + log;
|
||||
|
||||
Debug.LogWarning(warning, property.serializedObject.targetObject);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConditionalFieldUtility {
|
||||
#region Property Is Visible
|
||||
|
||||
public static bool PropertyIsVisible(SerializedProperty property, bool inverse, string[] compareAgainst) {
|
||||
if(property == null) return true;
|
||||
|
||||
string asString = property.AsStringValue().ToUpper();
|
||||
|
||||
if(compareAgainst != null && compareAgainst.Length > 0) {
|
||||
var matchAny = CompareAgainstValues(asString, compareAgainst, IsFlagsEnum());
|
||||
if(inverse) matchAny = !matchAny;
|
||||
return matchAny;
|
||||
}
|
||||
|
||||
bool someValueAssigned = asString != "FALSE" && asString != "0" && asString != "NULL";
|
||||
if(someValueAssigned) return !inverse;
|
||||
|
||||
return inverse;
|
||||
|
||||
|
||||
bool IsFlagsEnum() {
|
||||
if(property.propertyType != SerializedPropertyType.Enum) return false;
|
||||
var value = property.GetValue();
|
||||
if(value == null) return false;
|
||||
return value.GetType().GetCustomAttribute<FlagsAttribute>() != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// True if the property value matches any of the values in '_compareValues'
|
||||
/// </summary>
|
||||
private static bool CompareAgainstValues(string propertyValueAsString, string[] compareAgainst, bool handleFlags) {
|
||||
if(!handleFlags) return ValueMatches(propertyValueAsString);
|
||||
|
||||
var separateFlags = propertyValueAsString.Split(',');
|
||||
foreach(var flag in separateFlags) {
|
||||
if(ValueMatches(flag.Trim())) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
|
||||
bool ValueMatches(string value) {
|
||||
foreach(var compare in compareAgainst) if(value == compare) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Find Relative Property
|
||||
|
||||
public static SerializedProperty FindRelativeProperty(SerializedProperty property, string propertyName) {
|
||||
if(property.depth == 0) return property.serializedObject.FindProperty(propertyName);
|
||||
|
||||
var path = property.propertyPath.Replace(".Array.data[", "[");
|
||||
var elements = path.Split('.');
|
||||
|
||||
var nestedProperty = NestedPropertyOrigin(property, elements);
|
||||
|
||||
// if nested property is null = we hit an array property
|
||||
if(nestedProperty == null) {
|
||||
var cleanPath = path.Substring(0, path.IndexOf('['));
|
||||
var arrayProp = property.serializedObject.FindProperty(cleanPath);
|
||||
var target = arrayProp.serializedObject.targetObject;
|
||||
|
||||
var who = "Property <color=brown>" + arrayProp.name + "</color> in object <color=brown>" + target.name + "</color> caused: ";
|
||||
var warning = who + "Array fields is not supported by [ConditionalFieldAttribute]. Consider to use <color=blue>CollectionWrapper</color>";
|
||||
|
||||
Debug.LogWarning(warning, target);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return nestedProperty.FindPropertyRelative(propertyName);
|
||||
}
|
||||
|
||||
// For [Serialized] types with [Conditional] fields
|
||||
private static SerializedProperty NestedPropertyOrigin(SerializedProperty property, string[] elements) {
|
||||
SerializedProperty parent = null;
|
||||
|
||||
for(int i = 0; i < elements.Length - 1; i++) {
|
||||
var element = elements[i];
|
||||
int index = -1;
|
||||
if(element.Contains("[")) {
|
||||
index = Convert.ToInt32(element.Substring(element.IndexOf("[", StringComparison.Ordinal))
|
||||
.Replace("[", "").Replace("]", ""));
|
||||
element = element.Substring(0, element.IndexOf("[", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
parent = i == 0
|
||||
? property.serializedObject.FindProperty(element)
|
||||
: parent != null
|
||||
? parent.FindPropertyRelative(element)
|
||||
: null;
|
||||
|
||||
if(index >= 0 && parent != null) parent = parent.GetArrayElementAtIndex(index);
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Behaviour Property Is Visible
|
||||
|
||||
public static bool BehaviourPropertyIsVisible(UnityEngine.Object obj, string propertyName, ConditionalFieldAttribute appliedAttribute) {
|
||||
if(string.IsNullOrEmpty(appliedAttribute.FieldToCheck)) return true;
|
||||
|
||||
var so = new SerializedObject(obj);
|
||||
var property = so.FindProperty(propertyName);
|
||||
var targetProperty = FindRelativeProperty(property, appliedAttribute.FieldToCheck);
|
||||
|
||||
return PropertyIsVisible(targetProperty, appliedAttribute.Inverse, appliedAttribute.CompareValues);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user