forked from Shardstone/trail-into-darkness
323 lines
13 KiB
C#
323 lines
13 KiB
C#
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
|