// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Editor.CustomDrawers { #if UNITY_EDITOR && ODIN_INSPECTOR using System; using System.Collections.Generic; using System.Reflection; using Sirenix.OdinInspector.Editor; using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; using WallstopStudios.UnityHelpers.Editor.CustomDrawers.Utils; /// /// Odin Inspector attribute drawer for . /// Conditionally shows or hides properties based on the value of another field. /// /// /// This drawer ensures WShowIf works correctly when Odin Inspector is installed /// and classes derive from SerializedMonoBehaviour or SerializedScriptableObject, /// where Unity's standard PropertyDrawer system is bypassed. /// public sealed class WShowIfOdinDrawer : OdinAttributeDrawer { private static readonly Dictionary<(Type, string), MemberInfo> MemberCache = new(); /// /// Clears all cached state. Called during domain reload via /// . /// internal static void ClearCache() { MemberCache.Clear(); } protected override void DrawPropertyLayout(GUIContent label) { WShowIfAttribute showIf = Attribute; if (showIf == null) { CallNextDrawer(label); return; } object parentValue = Property.Parent?.ValueEntry?.WeakSmartValue; if (parentValue == null) { CallNextDrawer(label); return; } object conditionValue = GetConditionValue(parentValue, showIf.conditionField); if ( !ShowIfConditionEvaluator.TryEvaluateCondition( conditionValue, showIf, out bool shouldShow ) ) { CallNextDrawer(label); return; } if (shouldShow) { CallNextDrawer(label); } } private static object GetConditionValue(object parent, string conditionField) { if (parent == null || string.IsNullOrEmpty(conditionField)) { return null; } Type parentType = parent.GetType(); (Type, string) cacheKey = (parentType, conditionField); if (!MemberCache.TryGetValue(cacheKey, out MemberInfo memberInfo)) { memberInfo = ResolveMember(parentType, conditionField); MemberCache[cacheKey] = memberInfo; } if (memberInfo == null) { return null; } return GetMemberValue(memberInfo, parent); } private static MemberInfo ResolveMember(Type type, string memberName) { FieldInfo field = type.GetField( memberName, ShowIfConditionEvaluator.MemberBindingFlags ); if (field != null) { return field; } PropertyInfo property = type.GetProperty( memberName, ShowIfConditionEvaluator.MemberBindingFlags ); if (property != null && property.CanRead) { return property; } MethodInfo method = type.GetMethod( memberName, ShowIfConditionEvaluator.MemberBindingFlags, null, Type.EmptyTypes, null ); if (method != null && method.ReturnType != typeof(void)) { return method; } return null; } private static object GetMemberValue(MemberInfo memberInfo, object target) { if (memberInfo is FieldInfo fieldInfo) { return fieldInfo.GetValue(target); } if (memberInfo is PropertyInfo propertyInfo) { return propertyInfo.GetValue(target); } if (memberInfo is MethodInfo methodInfo) { return methodInfo.Invoke(target, null); } return null; } /// /// Gets the value of the condition field from the parent object. /// /// /// This method is internal to allow testing without reflection. /// /// The parent object containing the condition field. /// The name of the condition field. /// The value of the condition field, or null if not found. internal static object GetConditionValueForTest(object parent, string conditionField) { return GetConditionValue(parent, conditionField); } } #endif }