// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Core.Attributes { using System; using UnityEngine; /// /// Draws an integer field as a dropdown list using a predefined set of numeric options. /// Designed for authoring config objects, ScriptableObjects, and MonoBehaviours that should be restricted to a known set of valid identifiers (layer masks, level IDs, refresh rates, etc.). /// /// /// /// The attribute supports inline value lists, static providers resolved through reflection, and instance method providers. /// Late-binding enables values to track data sets (for example, enums translated to int IDs or editor preferences stored elsewhere). /// /// /// This attribute is implemented using the same underlying infrastructure as , /// specialized for integer values with a type-safe API. /// /// /// /// Inline list: /// /// [IntDropDown(0, 30, 60, 120)] /// public int frameRate; /// /// Static provider: /// /// [IntDropDown(typeof(FrameRateLibrary), nameof(FrameRateLibrary.GetSupportedFrameRates))] /// public int frameRate; /// /// private static class FrameRateLibrary /// { /// internal static IEnumerable<int> GetSupportedFrameRates() => new[] { 30, 60, 120 }; /// } /// /// Instance provider: /// /// [IntDropDown(nameof(GetAvailableIds))] /// public int selectedId; /// /// private IEnumerable<int> GetAvailableIds() => cachedIds; /// /// public sealed class IntDropDownAttribute : PropertyAttribute { private static readonly int[] Empty = Array.Empty(); private readonly WValueDropDownAttribute _backingAttribute; private object[] _cachedSourceOptions; private int[] _cachedIntOptions; /// /// Gets the underlying that powers this attribute. /// This enables sharing of infrastructure between both attribute types. /// internal WValueDropDownAttribute BackingAttribute => _backingAttribute; /// /// Initializes the attribute with an inline list of integer values that should be exposed in the inspector. /// /// One or more selectable integer values. The order defined here becomes the dropdown order. public IntDropDownAttribute(params int[] options) { _backingAttribute = new WValueDropDownAttribute(options ?? Array.Empty()); } /// /// Initializes the attribute using a static provider method that supplies integer values. /// /// /// The provider must be a parameterless static method that returns either [] or of . /// The method is evaluated each time the inspector needs to render the property, allowing the dropdown contents to stay in sync with external state. /// /// Type containing the static provider method (for example, typeof(SettingsCache)). /// Name of the parameterless static method returning the options (for example, nameof(SettingsCache.GetDifficultyIds)). public IntDropDownAttribute(Type providerType, string methodName) { _backingAttribute = new WValueDropDownAttribute(providerType, methodName, typeof(int)); } /// /// Uses a method on the decorated object's type to obtain the allowed integers. /// The method can be instance or static, must be parameterless, and return either int[] or IEnumerable<int>. /// /// Method name declared on the target object's type. public IntDropDownAttribute(string methodName) { _backingAttribute = new WValueDropDownAttribute(methodName, typeof(int)); } /// /// Gets the set of allowed integer values that the dropdown will display without context. /// Note: when the attribute targets an instance method, this returns an empty array. /// The array is fetched from the configured provider whenever the inspector requests it. /// public int[] Options => GetOptions(null); /// /// Retrieves the allowed integer options for the supplied context object. /// /// The object declaring the field/property. Required for instance providers. /// Resolved option list (never null). public int[] GetOptions(object context) { if (_backingAttribute == null) { return Empty; } object[] options = _backingAttribute.GetOptions(context); if (options == null || options.Length == 0) { return Empty; } if (ReferenceEquals(options, _cachedSourceOptions) && _cachedIntOptions != null) { return _cachedIntOptions; } int[] result = new int[options.Length]; for (int i = 0; i < options.Length; i++) { if (options[i] is int intValue) { result[i] = intValue; } else if (options[i] != null) { try { result[i] = Convert.ToInt32(options[i]); } catch { result[i] = 0; } } } _cachedSourceOptions = options; _cachedIntOptions = result; return result; } /// /// Indicates whether this attribute uses an instance method provider. /// internal bool RequiresInstanceContext => _backingAttribute?.RequiresInstanceContext ?? false; } }