Guest User

Untitled

a guest
Oct 25th, 2023
18
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.36 KB | None | 0 0
  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using System.Reflection;
  6.  
  7. namespace MyBox
  8. {
  9. /// <summary>
  10. /// Conditionally Show/Hide field in inspector, based on some other field value
  11. /// </summary>
  12. [AttributeUsage(AttributeTargets.Field)]
  13. public class ConditionalHelpBoxFieldAttribute : PropertyAttribute
  14. {
  15. public readonly string FieldToCheck;
  16. public readonly string[] CompareValues;
  17. public readonly bool Inverse;
  18. public readonly string Message;
  19.  
  20. /// <param name="fieldToCheck">String name of field to check value</param>
  21. /// <param name="inverse">Inverse check result</param>
  22. /// <param name="compareValues">On which values field will be shown in inspector</param>
  23. public ConditionalHelpBoxFieldAttribute(string fieldToCheck, string message, bool inverse = false, params object[] compareValues)
  24. {
  25. Message = message;
  26. FieldToCheck = fieldToCheck;
  27. Inverse = inverse;
  28. CompareValues = compareValues.Select(c => c.ToString().ToUpper()).ToArray();
  29. }
  30. }
  31. }
  32.  
  33. #if UNITY_EDITOR
  34. namespace MyBox.Internal
  35. {
  36. using UnityEditor;
  37. using EditorTools;
  38.  
  39. [CustomPropertyDrawer(typeof(ConditionalHelpBoxFieldAttribute))]
  40. public class ConditionalHelpBoxFieldAttributeDrawer : PropertyDrawer
  41. {
  42. private bool _toShow = true;
  43.  
  44.  
  45. /// <summary>
  46. /// Key is Associated with drawer type (the T in [CustomPropertyDrawer(typeof(T))])
  47. /// Value is PropertyDrawer Type
  48. /// </summary>
  49. private static Dictionary<Type, Type> _allPropertyDrawersInDomain;
  50.  
  51.  
  52. private bool _initialized;
  53. private PropertyDrawer _customAttributeDrawer;
  54. private PropertyDrawer _customTypeDrawer;
  55.  
  56. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
  57. {
  58. if (!(attribute is ConditionalHelpBoxFieldAttribute conditional)) return 0;
  59.  
  60. Initialize(property);
  61.  
  62. var propertyToCheck = ConditionalHelpBoxFieldUtility.FindRelativeProperty(property, conditional.FieldToCheck);
  63. _toShow = ConditionalHelpBoxFieldUtility.PropertyIsVisible(propertyToCheck, conditional.Inverse, conditional.CompareValues);
  64. if (!_toShow) return EditorGUI.GetPropertyHeight(property);
  65.  
  66. if (_customAttributeDrawer != null) return _customAttributeDrawer.GetPropertyHeight(property, label);
  67. if (_customTypeDrawer != null) return _customTypeDrawer.GetPropertyHeight(property, label);
  68.  
  69. return EditorGUI.GetPropertyHeight(property);
  70. }
  71.  
  72. public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
  73. {
  74. //combining attributes here:
  75.  
  76. MinValueAttribute minValueAttribute = FoldoutAttributeHandler.FindAttribute<MinValueAttribute>(property);
  77. if(minValueAttribute != null) {
  78. minValueAttribute.ValidateProperty(property);
  79. }
  80.  
  81. if (_toShow && attribute is ConditionalHelpBoxFieldAttribute conditional)
  82. EditorGUILayout.HelpBox(conditional.Message, MessageType.Warning);
  83.  
  84. if (_customAttributeDrawer != null) TryUseAttributeDrawer();
  85. else if (_customTypeDrawer != null) TryUseTypeDrawer();
  86. else EditorGUI.PropertyField(position, property, label, true);
  87.  
  88. void TryUseAttributeDrawer()
  89. {
  90. try
  91. {
  92. _customAttributeDrawer.OnGUI(position, property, label);
  93. }
  94. catch (Exception e)
  95. {
  96. EditorGUI.PropertyField(position, property, label);
  97. LogWarning("Unable to use Custom Attribute Drawer " + _customAttributeDrawer.GetType() + " : " + e, property);
  98. }
  99. }
  100.  
  101. void TryUseTypeDrawer()
  102. {
  103. try
  104. {
  105. _customTypeDrawer.OnGUI(position, property, label);
  106. }
  107. catch (Exception e)
  108. {
  109. EditorGUI.PropertyField(position, property, label);
  110. LogWarning("Unable to instantiate " + fieldInfo.FieldType + " : " + e, property);
  111. }
  112. }
  113. }
  114.  
  115.  
  116. private void Initialize(SerializedProperty property)
  117. {
  118. if (_initialized) return;
  119.  
  120. CacheAllDrawersInDomain();
  121.  
  122. TryGetCustomAttributeDrawer();
  123. TryGetCustomTypeDrawer();
  124.  
  125. _initialized = true;
  126.  
  127.  
  128. void CacheAllDrawersInDomain()
  129. {
  130. if (!_allPropertyDrawersInDomain.IsNullOrEmpty()) return;
  131.  
  132. _allPropertyDrawersInDomain = new Dictionary<Type, Type>();
  133. var propertyDrawerType = typeof(PropertyDrawer);
  134.  
  135. var allDrawerTypesInDomain = AppDomain.CurrentDomain.GetAssemblies()
  136. .SelectMany(x => x.GetTypes())
  137. .Where(t => propertyDrawerType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
  138.  
  139. foreach (var type in allDrawerTypesInDomain)
  140. {
  141. var drawerAttribute = CustomAttributeData.GetCustomAttributes(type).FirstOrDefault();
  142. if (drawerAttribute == null) continue;
  143. var associatedType = drawerAttribute.ConstructorArguments.FirstOrDefault().Value as Type;
  144. if (associatedType == null) continue;
  145.  
  146. if (_allPropertyDrawersInDomain.ContainsKey(associatedType)) continue;
  147. _allPropertyDrawersInDomain.Add(associatedType, type);
  148. }
  149. }
  150.  
  151. void TryGetCustomAttributeDrawer()
  152. {
  153. if (fieldInfo == null) return;
  154. //Get the second attribute flag
  155. var secondAttribute = (PropertyAttribute) fieldInfo.GetCustomAttributes(typeof(PropertyAttribute), false)
  156. .FirstOrDefault(a => !(a is ConditionalHelpBoxFieldAttribute));
  157. if (secondAttribute == null) return;
  158. var genericAttributeType = secondAttribute.GetType();
  159.  
  160. //Get the associated attribute drawer
  161. if (!_allPropertyDrawersInDomain.ContainsKey(genericAttributeType)) return;
  162.  
  163. var customAttributeDrawerType = _allPropertyDrawersInDomain[genericAttributeType];
  164. var customAttributeData = fieldInfo.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType == secondAttribute.GetType());
  165. if (customAttributeData == null) return;
  166.  
  167.  
  168. //Create drawer for custom attribute
  169. try
  170. {
  171. _customAttributeDrawer = (PropertyDrawer) Activator.CreateInstance(customAttributeDrawerType);
  172. var attributeField = customAttributeDrawerType.GetField("m_Attribute", BindingFlags.Instance | BindingFlags.NonPublic);
  173. if (attributeField != null) attributeField.SetValue(_customAttributeDrawer, secondAttribute);
  174. }
  175. catch (Exception e)
  176. {
  177. LogWarning("Unable to construct drawer for " + secondAttribute.GetType() + " : " + e, property);
  178. }
  179. }
  180.  
  181. void TryGetCustomTypeDrawer()
  182. {
  183. if (fieldInfo == null) return;
  184. // Skip checks for mscorlib.dll
  185. if (fieldInfo.FieldType.Module.ScopeName.Equals(typeof(int).Module.ScopeName)) return;
  186.  
  187.  
  188. // Of all property drawers in the assembly we need to find one that affects target type
  189. // or one of the base types of target type
  190. Type fieldDrawerType = null;
  191. Type fieldType = fieldInfo.FieldType;
  192. while (fieldType != null)
  193. {
  194. if (_allPropertyDrawersInDomain.ContainsKey(fieldType))
  195. {
  196. fieldDrawerType = _allPropertyDrawersInDomain[fieldType];
  197. break;
  198. }
  199.  
  200. fieldType = fieldType.BaseType;
  201. }
  202.  
  203. if (fieldDrawerType == null) return;
  204.  
  205. //Create instances of each (including the arguments)
  206. try
  207. {
  208. _customTypeDrawer = (PropertyDrawer) Activator.CreateInstance(fieldDrawerType);
  209. }
  210. catch (Exception e)
  211. {
  212. LogWarning("No constructor available in " + fieldType + " : " + e, property);
  213. return;
  214. }
  215.  
  216. //Reassign the attribute field in the drawer so it can access the argument values
  217. var attributeField = fieldDrawerType.GetField("m_Attribute", BindingFlags.Instance | BindingFlags.NonPublic);
  218. if (attributeField != null) attributeField.SetValue(_customTypeDrawer, attribute);
  219. var fieldInfoField = fieldDrawerType.GetField("m_FieldInfo", BindingFlags.Instance | BindingFlags.NonPublic);
  220. if (fieldInfoField != null) fieldInfoField.SetValue(_customTypeDrawer, fieldInfo);
  221. }
  222. }
  223.  
  224. private void LogWarning(string log, SerializedProperty property)
  225. {
  226. var warning = "Property <color=brown>" + fieldInfo.Name + "</color>";
  227. if (fieldInfo != null && fieldInfo.DeclaringType != null)
  228. warning += " on behaviour <color=brown>" + fieldInfo.DeclaringType.Name + "</color>";
  229. warning += " caused: " + log;
  230.  
  231. WarningsPool.LogWarning(warning, property.serializedObject.targetObject);
  232. }
  233. }
  234.  
  235. public static class ConditionalHelpBoxFieldUtility
  236. {
  237. #region Property Is Visible
  238.  
  239. public static bool PropertyIsVisible(SerializedProperty property, bool inverse, string[] compareAgainst)
  240. {
  241. if (property == null) return true;
  242.  
  243. string asString = property.AsStringValue().ToUpper();
  244. //Debug.Log(asString);
  245.  
  246. if (compareAgainst != null && compareAgainst.Length > 0)
  247. {
  248. var matchAny = CompareAgainstValues(asString, compareAgainst, IsFlagsEnum());
  249. if (inverse) matchAny = !matchAny;
  250. return matchAny;
  251. }
  252.  
  253. bool someValueAssigned = asString != "FALSE" && asString != "0" && asString != "(0.0, 0.0, 0.0)" && asString != "(0.0, 0.0)" && asString != "" && asString != "NULL";
  254. if (someValueAssigned) return !inverse;
  255.  
  256. return inverse;
  257.  
  258.  
  259. bool IsFlagsEnum()
  260. {
  261. if (property.propertyType != SerializedPropertyType.Enum) return false;
  262. var value = property.GetValue();
  263. if (value == null) return false;
  264. return value.GetType().GetCustomAttribute<FlagsAttribute>() != null;
  265. }
  266. }
  267.  
  268.  
  269. /// <summary>
  270. /// True if the property value matches any of the values in '_compareValues'
  271. /// </summary>
  272. public static bool CompareAgainstValues(string propertyValueAsString, string[] compareAgainst, bool handleFlags)
  273. {
  274. if (!handleFlags) return ValueMatches(propertyValueAsString);
  275.  
  276. var separateFlags = propertyValueAsString.Split(',');
  277. foreach (var flag in separateFlags)
  278. {
  279. if (ValueMatches(flag.Trim())) return true;
  280. }
  281.  
  282. return false;
  283.  
  284.  
  285. bool ValueMatches(string value)
  286. {
  287. foreach (var compare in compareAgainst) if (value == compare) return true;
  288. return false;
  289. }
  290. }
  291.  
  292. #endregion
  293.  
  294.  
  295. #region Find Relative Property
  296.  
  297. public static SerializedProperty FindRelativeProperty(SerializedProperty property, string propertyName)
  298. {
  299. if (property.depth == 0) return property.serializedObject.FindProperty(propertyName);
  300.  
  301. var path = property.propertyPath.Replace(".Array.data[", "[");
  302. var elements = path.Split('.');
  303.  
  304. var nestedProperty = NestedPropertyOrigin(property, elements);
  305.  
  306. // if nested property is null = we hit an array property
  307. if (nestedProperty == null)
  308. {
  309. var cleanPath = path.Substring(0, path.IndexOf('['));
  310. var arrayProp = property.serializedObject.FindProperty(cleanPath);
  311. var target = arrayProp.serializedObject.targetObject;
  312.  
  313. var who = "Property <color=brown>" + arrayProp.name + "</color> in object <color=brown>" + target.name + "</color> caused: ";
  314. var warning = who + "Array fields is not supported by [ConditionalHelpBoxFieldAttribute]. Consider to use <color=blue>CollectionWrapper</color>";
  315.  
  316. WarningsPool.LogWarning(warning, target);
  317.  
  318. return null;
  319. }
  320.  
  321. return nestedProperty.FindPropertyRelative(propertyName);
  322. }
  323.  
  324. // For [Serialized] types with [Conditional] fields
  325. private static SerializedProperty NestedPropertyOrigin(SerializedProperty property, string[] elements)
  326. {
  327. SerializedProperty parent = null;
  328.  
  329. for (int i = 0; i < elements.Length - 1; i++)
  330. {
  331. var element = elements[i];
  332. int index = -1;
  333. if (element.Contains("["))
  334. {
  335. index = Convert.ToInt32(element.Substring(element.IndexOf("[", StringComparison.Ordinal))
  336. .Replace("[", "").Replace("]", ""));
  337. element = element.Substring(0, element.IndexOf("[", StringComparison.Ordinal));
  338. }
  339.  
  340. parent = i == 0
  341. ? property.serializedObject.FindProperty(element)
  342. : parent != null
  343. ? parent.FindPropertyRelative(element)
  344. : null;
  345.  
  346. if (index >= 0 && parent != null) parent = parent.GetArrayElementAtIndex(index);
  347. }
  348.  
  349. return parent;
  350. }
  351.  
  352. #endregion
  353.  
  354. #region Behaviour Property Is Visible
  355.  
  356. public static bool BehaviourPropertyIsVisible(UnityEngine.Object obj, string propertyName, ConditionalHelpBoxFieldAttribute appliedAttribute)
  357. {
  358. if (string.IsNullOrEmpty(appliedAttribute.FieldToCheck)) return true;
  359.  
  360. var so = new SerializedObject(obj);
  361. var property = so.FindProperty(propertyName);
  362. var targetProperty = FindRelativeProperty(property, appliedAttribute.FieldToCheck);
  363.  
  364. return PropertyIsVisible(targetProperty, appliedAttribute.Inverse, appliedAttribute.CompareValues);
  365. }
  366.  
  367. #endregion
  368. }
  369. }
  370. #endif
Advertisement
Add Comment
Please, Sign In to add comment