UNPKG

com.wallstop-studios.unity-helpers

Version:

Treasure chest of Unity developer tools

865 lines (648 loc) 23.2 kB
# Inspector Selection Attributes **Transform selection controls from dropdowns to designer-friendly interfaces.** Unity Helpers provides four powerful selection attributes that replace standard dropdowns with toggle buttons, searchable lists, and type-safe selection controls. Perfect for flags, enums, predefined values, and dynamic option lists. --- ## Table of Contents - [WEnumToggleButtons](#wenumtogglebuttons) - [WValueDropDown](#wvaluedropdown) - [IntDropDown](#intdropdown) - [StringInList](#stringinlist) - [Comparison Table](#comparison-table) - [Best Practices](#best-practices) - [Examples](#examples) --- ## WEnumToggleButtons Draws enum and flag enum fields as a toolbar of toggle buttons. ### Basic Usage ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class EntityPermissions : MonoBehaviour { [System.Flags] public enum Permissions { None = 0, Move = 1 << 0, Attack = 1 << 1, UseItems = 1 << 2, CastSpells = 1 << 3, } [WEnumToggleButtons] public Permissions currentPermissions = Permissions.Move | Permissions.Attack; } ``` > **Visual Reference** > > ![WEnumToggleButtons with flag enum](../../images/inspector/selection/wenum-toggle-buttons-basic.gif) > > _Flag enum permissions rendered as toggle buttons_ --- ### Parameters ```csharp [WEnumToggleButtons( int buttonsPerRow = 0, // Force column count (0 = automatic) bool showSelectAll = false, // Show "Select All" button (flags only) bool showSelectNone = false, // Show "Select None" button (flags only) bool enablePagination = true, // Allow pagination for many options int pageSize = -1, // Override page size (defaults to project setting) string colorKey = "Default" // Color palette key )] ``` --- ### Flag Enums ```csharp [System.Flags] public enum DamageTypes { None = 0, Physical = 1 << 0, Fire = 1 << 1, Ice = 1 << 2, Lightning = 1 << 3, Poison = 1 << 4, } [WEnumToggleButtons(showSelectAll: true, showSelectNone: true, buttonsPerRow: 3)] public DamageTypes resistances = DamageTypes.Fire | DamageTypes.Ice; ``` > **Visual Reference** > > ![WEnumToggleButtons with Select All/None buttons](../../images/inspector/selection/wenum-toggle-buttons-flags-select.png) > > _Flag enum with Select All and Select None quick action buttons_ **Behavior:** - Each flag value gets its own toggle button - Multiple flags can be active simultaneously - "Select All" and "Select None" buttons for quick configuration --- ### Standard Enums (Radio Buttons) ```csharp public enum WeaponType { Melee, Ranged, Magic } [WEnumToggleButtons(buttonsPerRow: 3, colorKey: "Default-Dark")] public WeaponType weaponType = WeaponType.Melee; ``` > **Visual Reference** > > ![Radio-style toggle buttons](../../images/inspector/selection/wenum-toggle-buttons-radio.png) > > _Standard enum rendered as radio-style toggle buttons (only one active)_ **Behavior:** - Only one option can be selected at a time - Clicking a button deselects others --- ### Pagination ```csharp [System.Flags] public enum AllAbilities { None = 0, Ability1 = 1 << 0, Ability2 = 1 << 1, // ... 20 more abilities ... Ability22 = 1 << 21, } [WEnumToggleButtons(enablePagination: true, pageSize: 10)] public AllAbilities unlockedAbilities; ``` > **Visual Reference** > > ![Paginated toggle buttons](../../images/inspector/selection/wenum-toggle-buttons-pagination.gif) > > _Paginated toggle buttons with First, Previous, Next, Last navigation controls_ **Features:** - Automatic pagination for many options - Page size controlled by `pageSize` parameter or `UnityHelpersSettings.EnumToggleButtonsPageSize` - Navigation: First, Previous, Next, Last buttons - Summary badge shows selections on other pages --- ### Layout Control ```csharp // Automatic layout (fits to inspector width) [WEnumToggleButtons] public Permissions autoLayout; // Force 2 columns [WEnumToggleButtons(buttonsPerRow: 2)] public Permissions twoColumns; // Force single column [WEnumToggleButtons(buttonsPerRow: 1)] public Permissions singleColumn; ``` > **Visual Reference** > > ![Toggle button layouts](../../images/inspector/selection/wenum-toggle-buttons-layouts.png) > > _Different column layouts: automatic, 2 columns, and single column_ --- ### Color Theming ```csharp [WEnumToggleButtons(colorKey: "Default-Dark")] public Permissions darkTheme; [WEnumToggleButtons(colorKey: "Default-Light")] public Permissions lightTheme; ``` > **Visual Reference** > > ![Toggle button themes](../../images/inspector/selection/wenum-toggle-buttons-themes.png) > > _Toggle buttons with dark and light color themes_ --- ### Combining with Other Attributes ```csharp // Works with IntDropDown/StringInList/WValueDropDown! [IntDropDown(0, 30, 60, 120)] [WEnumToggleButtons] public int frameRate = 60; // Shows as toggle buttons instead of dropdown ``` --- ### Composite Flags Behavior The drawer intentionally filters out composite flag values (e.g., `ReadWrite = Read | Write`). This keeps the UI focused on atomic toggles and avoids ambiguous interactions. Use the "Select All" and "Select None" buttons for bulk operations. ### Best Practices for WEnumToggleButtons - Keep option counts manageable toggle groups work best for short lists where designers can see everything without scrolling - Name enum members descriptively so automatic labels remain readable - Combine with conditional attributes like `WShowIf` to build adaptive, context-aware authoring tools --- ## WValueDropDown Generic dropdown for any type with fixed values or provider methods. ### Basic Usage (Fixed Values) ```csharp // Primitive types [WValueDropDown(0, 25, 50, 100)] public int staminaThreshold = 50; [WValueDropDown(0.5f, 1.0f, 1.5f, 2.0f)] public float damageMultiplier = 1.0f; [WValueDropDown("Easy", "Normal", "Hard", "Insane")] public string difficulty = "Normal"; ``` > **Visual Reference** > > ![WValueDropDown with predefined values](../../images/inspector/selection/wvalue-dropdown-basic.gif) > > _Dropdown showing predefined integer, float, and string values_ --- ### Provider Methods ```csharp using System.Collections.Generic; using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; [CreateAssetMenu(fileName = "PowerUpDefinition", menuName = "Power-Up Definition")] public class PowerUpDefinition : ScriptableObject { public string name; public Sprite icon; } public class PowerUpConfig : MonoBehaviour { [WValueDropDown(typeof(PowerUpLibrary), nameof(PowerUpLibrary.GetAvailablePowerUps))] public PowerUpDefinition selectedPowerUp; } public static class PowerUpLibrary { public static IEnumerable<PowerUpDefinition> GetAvailablePowerUps() { // Return all power-ups from database/resources/etc. return Resources.LoadAll<PowerUpDefinition>("PowerUps"); } } ``` > **Visual Reference** > ![Powerups in Resources](../../images/inspector/selection/powerup-resources.png) > > _PowerUp definitions loaded from Resources folder_ > > ![WValueDropDown provider method](../../images/inspector/selection/wvalue-dropdown-provider.gif) > > _Dropdown populated dynamically from a provider method_ --- ### Primitive Overloads ```csharp using System.Collections.Generic; using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class WValueDropDownPrimitives : MonoBehaviour { // All primitive types supported: [WValueDropDown(true, false)] // bool public bool boolValue; [WValueDropDown(true, false)] // bool public List<bool> boolValues; [WValueDropDown('A', 'B', 'C')] // char public char charValue; [WValueDropDown((byte)1, (byte)2, (byte)3)] // byte public byte byteValue; [WValueDropDown((short)10, (short)20, (short)30)] // short public short shortValue; [WValueDropDown(100, 200, 300)] // int public int intValue; [WValueDropDown(1000L, 2000L, 3000L)] // long public long longValue; [WValueDropDown(0.1f, 0.5f, 1.0f)] // float public float floatValue; [WValueDropDown(0.1, 0.5, 1.0)] // double public double doubleValue; } ``` > **Visual Reference** > > ![WValueDropDown primitive types](../../images/inspector/selection/wvalue-dropdown-primitives.png) > > _Multiple dropdowns showing all supported primitive types_ --- ### Instance Provider (context-aware) ```csharp public class DynamicOptions : MonoBehaviour { public string prefix = "Option"; public int optionCount = 5; // Instance method - uses object state to build options [WValueDropDown(nameof(GetAvailableOptions), typeof(string))] public string selectedOption; private IEnumerable<string> GetAvailableOptions() { for (int i = 1; i <= optionCount; i++) { yield return $"{prefix}_{i}"; } } } ``` > Passing only a method name instructs the drawer to search the decorated type for a parameterless > method. Instance methods run on the serialized object; static methods are also supported. > The second parameter specifies the value type for proper dropdown rendering. **Alternate Constructor Forms:** ```csharp // Form 1: Instance method with explicit value type [WValueDropDown(nameof(GetOptions), typeof(int))] public int selection; // Form 2: Static provider from external type [WValueDropDown(typeof(DataProvider), nameof(DataProvider.GetItems))] public Item selected; // Form 3: Static provider with explicit value type conversion [WValueDropDown(typeof(DataProvider), nameof(DataProvider.GetIds), typeof(int))] public int selectedId; ``` --- ### Custom Types ```csharp using System; using System.Collections.Generic; using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; [Serializable] public class Preset { public string name; public float value; public override string ToString() => name; // Used for dropdown label } public class Config : MonoBehaviour { [WValueDropDown(typeof(Config), nameof(GetPresets))] public Preset selectedPreset; public static IEnumerable<Preset> GetPresets() { return new[] { new Preset { name = "Low", value = 0.5f }, new Preset { name = "Medium", value = 1.0f }, new Preset { name = "High", value = 2.0f }, }; } } ``` > **Visual Reference** > > ![WValueDropDown with custom types](../../images/inspector/selection/wvalue-dropdown-custom-type.png) > > _Dropdown showing custom serializable class options using ToString() for labels_ --- ### Constructor Reference | Constructor | Use Case | | -------------------------------------------------------------- | --------------------------------------------------------- | | `WValueDropDown(params T[] values)` | Fixed list of primitive values (int, float, string, etc.) | | `WValueDropDown(Type provider, string method)` | Static or instance method on external type | | `WValueDropDown(Type provider, string method, Type valueType)` | Provider with explicit value type conversion | | `WValueDropDown(string method, Type valueType)` | Instance/static method on the decorated type itself | | `WValueDropDown(Type valueType, params object[] values)` | Fixed list with explicit type specification | --- ### Best Practices for WValueDropDown - Override `ToString()` on custom types to control dropdown labels - Use static providers for data shared across objects - Use instance providers when options depend on the object state - Provider methods are called each render keep them efficient or cache results - Return empty collection instead of null from providers --- ## IntDropDown Integer field rendered as a dropdown with predefined options. ### Basic Usage ```csharp [IntDropDown(0, 30, 60, 120, 240)] public int refreshRate = 60; [IntDropDown(1, 2, 4, 8, 16, 32)] public int threadCount = 4; ``` > **Visual Reference** > > ![IntDropDown with predefined values](../../images/inspector/selection/int-dropdown-basic.gif) > > _Integer dropdown showing predefined frame rate options_ --- ### Provider Method ```csharp public class FrameRateConfig : MonoBehaviour { [IntDropDown(typeof(FrameRateLibrary), nameof(FrameRateLibrary.GetSupportedFrameRates))] public int targetFrameRate = 60; } public static class FrameRateLibrary { public static IEnumerable<int> GetSupportedFrameRates() { return new[] { 30, 60, 90, 120, 144, 240 }; } } ``` --- ### Fallback Behavior ```csharp [IntDropDown(10, 20, 30)] public int value = 25; // Not in list! Shows as standard IntField ``` **Note:** If the current value isn't in the list, falls back to standard IntField for editing. --- ## StringInList String field constrained to a set of allowed values with an inline dropdown that opens a searchable popup. ### Basic Usage ```csharp [StringInList("Easy", "Normal", "Hard", "Nightmare")] public string difficulty = "Normal"; [StringInList("Red", "Green", "Blue", "Yellow", "Purple")] public string teamColor = "Red"; ``` > **Visual Reference** > > ![StringInList with search popup](../../images/inspector/selection/string-in-list-basic.gif) > > _String dropdown with an inline popup showing search bar and results_ --- ### Provider Method ```csharp public class LocalizationConfig : MonoBehaviour { [StringInList(typeof(LocalizationKeys), nameof(LocalizationKeys.GetAllKeys))] public string dialogKey; } public static class LocalizationKeys { public static IEnumerable<string> GetAllKeys() { // Return keys from localization database return new[] { "DIALOG_GREETING", "DIALOG_FAREWELL", "DIALOG_QUEST_START" }; } } ``` --- ### Instance Provider (context-aware) ```csharp public class StateMachine : MonoBehaviour { [StringInList(nameof(BuildAvailableStates))] public string currentState; private IEnumerable<string> BuildAvailableStates() { // Instance data drives the dropdown yield return $"{gameObject.name}_Idle"; yield return $"{gameObject.name}_Run"; } public static IEnumerable<string> StaticStates() { // Static helpers still work when referenced by name return new[] { "Global_A", "Global_B" }; } } ``` > Passing only a method name instructs the drawer to search the decorated type for a parameterless > method. Instance methods run on the serialized object; static methods are also supported. --- ### Search and Pagination ```csharp [StringInList(typeof(SceneLibrary), nameof(SceneLibrary.GetAllSceneNames))] public string targetScene; public static class SceneLibrary { public static IEnumerable<string> GetAllSceneNames() { // Return 100+ scene names return EditorBuildSettings.scenes.Select(s => s.path); } } ``` When the menu contains more entries than the configured limit, clicking the field opens a popup that embeds the search bar and pagination controls directly in the dropdown itself. The inspector row remains single-line, but the popup still supports fast filtering and page navigation. > **Visual Reference** > > ![StringInList search popup](../../images/inspector/selection/string-in-list-search.gif) > > _Popup window with a search box filtering results in real-time_ **Features:** - Dedicated popup hosts search + pagination when the option count exceeds `UnityHelpersSettings.StringInListPageLimit` - Inspector row stays single-line; the popup includes filtering, paging, and keyboard navigation - Works in both IMGUI and UI Toolkit inspectors (including `SerializableTypeDrawer`) - Accepts fixed lists, static provider methods, or instance provider methods resolved on the component --- ### Use Cases **Scene Names:** ```csharp [StringInList(typeof(SceneHelper), nameof(SceneHelper.GetAllScenes))] public string loadScene; ``` **Animation States:** ```csharp [StringInList("Idle", "Walk", "Run", "Jump", "Attack")] public string currentAnimation = "Idle"; ``` **Localization Keys:** ```csharp [StringInList(typeof(LocaleManager), nameof(LocaleManager.GetKeys))] public string textKey; ``` **Tag/Layer Names:** ```csharp [StringInList("Player", "Enemy", "Projectile", "Environment")] public string entityTag = "Player"; ``` --- ## Comparison Table | Feature | WEnumToggleButtons | WValueDropDown | IntDropDown | StringInList | | ---------------------- | ------------------ | -------------- | ----------- | ----------------- | | **Primary Use** | Enums, flag enums | Any type | Integers | Strings | | **Visual Style** | Toggle buttons | Dropdown | Dropdown | Dropdown + search | | **Multiple Selection** | Yes (flags) | No | No | No | | **Pagination** | Yes | No | No | Yes | | **Search** | No | No | No | Yes | | **Provider Methods** | No | Yes | Yes | Yes | | **Custom Types** | No | Yes | No | No | | **Color Theming** | Yes | No | No | No | --- ## Best Practices ### 1. Choose the Right Attribute ```csharp // GOOD: WEnumToggleButtons for flags (visual toggle state) [System.Flags] public enum Features { None = 0, Feature1 = 1, Feature2 = 2, Feature3 = 4 } [WEnumToggleButtons] public Features enabledFeatures; // GOOD: WValueDropDown for fixed type-safe options [WValueDropDown(0.5f, 1.0f, 1.5f, 2.0f)] public float speedMultiplier; // GOOD: IntDropDown for integer-specific options [IntDropDown(30, 60, 120, 240)] public int frameRate; // GOOD: StringInList for string validation [StringInList("Easy", "Normal", "Hard")] public string difficulty; // BAD: Using strings when enum would be better [StringInList("Easy", "Normal", "Hard")] public string difficulty; // Should be an enum! ``` --- ### 2. Use Provider Methods for Dynamic Data ```csharp // GOOD: Provider method for data that changes [WValueDropDown(typeof(AssetDatabase), nameof(GetAllPrefabs))] public GameObject prefab; public static IEnumerable<GameObject> GetAllPrefabs() { return Resources.LoadAll<GameObject>("Prefabs"); } // BAD: Hardcoded values for dynamic data [WValueDropDown/* hardcoded list of 50 prefabs */] public GameObject prefab; // Nightmare to maintain! ``` --- ### 3. Enable Helpful UI Features ```csharp // GOOD: Select All/None for flag enums [System.Flags] public enum Permissions { None = 0, Read = 1, Write = 2, Execute = 4 } [WEnumToggleButtons(showSelectAll: true, showSelectNone: true)] public Permissions permissions; // GOOD: Pagination for many options [WEnumToggleButtons(enablePagination: true, pageSize: 10)] public ManyOptionsEnum options; // BAD: No pagination with 50 options (cluttered inspector!) [WEnumToggleButtons(enablePagination: false)] public ManyOptionsEnum options; ``` --- ## Examples ### Example 1: Damage Type Resistances ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class Armor : MonoBehaviour { [System.Flags] public enum DamageType { None = 0, Physical = 1 << 0, Fire = 1 << 1, Ice = 1 << 2, Lightning = 1 << 3, Poison = 1 << 4, Magic = 1 << 5, } [WEnumToggleButtons(showSelectAll: true, showSelectNone: true, buttonsPerRow: 3, colorKey: "Default-Dark")] public DamageType resistances = DamageType.Physical; [WEnumToggleButtons(showSelectAll: true, showSelectNone: true, buttonsPerRow: 3, colorKey: "Default-Light")] public DamageType vulnerabilities = DamageType.None; } ``` --- ### Example 2: Graphics Presets ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class GraphicsConfig : MonoBehaviour { [System.Serializable] public class QualityPreset { public string name; public int textureQuality; public int shadowDistance; public override string ToString() => name; } [WValueDropDown(typeof(GraphicsConfig), nameof(GetPresets))] public QualityPreset selectedPreset; public static IEnumerable<QualityPreset> GetPresets() { return new[] { new QualityPreset { name = "Low", textureQuality = 0, shadowDistance = 50 }, new QualityPreset { name = "Medium", textureQuality = 1, shadowDistance = 100 }, new QualityPreset { name = "High", textureQuality = 2, shadowDistance = 200 }, new QualityPreset { name = "Ultra", textureQuality = 3, shadowDistance = 500 }, }; } } ``` --- ### Example 3: Scene Selection ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; using System.Linq; public class SceneLoader : MonoBehaviour { [StringInList(typeof(SceneLoader), nameof(GetAllSceneNames))] public string mainMenuScene; [StringInList(typeof(SceneLoader), nameof(GetAllSceneNames))] public string gameplayScene; [StringInList(typeof(SceneLoader), nameof(GetAllSceneNames))] public string creditsScene; public static IEnumerable<string> GetAllSceneNames() { #if UNITY_EDITOR return UnityEditor.EditorBuildSettings.scenes .Select(s => System.IO.Path.GetFileNameWithoutExtension(s.path)); #else return new string[0]; #endif } } ``` --- ### Example 4: Framerate Configuration ```csharp using UnityEngine; using WallstopStudios.UnityHelpers.Core.Attributes; public class PerformanceSettings : MonoBehaviour { [IntDropDown(30, 60, 90, 120, 144, 240, -1)] public int targetFrameRate = 60; // -1 = unlimited [IntDropDown(0, 1, 2, 3, 4)] public int vsyncCount = 0; // 0 = disabled [WEnumToggleButtons] public enum FrameLimitMode { Unlimited, Target, VSync } public FrameLimitMode limitMode = FrameLimitMode.Target; private void OnValidate() { Application.targetFrameRate = targetFrameRate; QualitySettings.vSyncCount = vsyncCount; } } ``` --- ## See Also - **[Inspector Overview](./inspector-overview.md)** - Complete inspector features overview - **[Inspector Grouping Attributes](./inspector-grouping-attributes.md)** - WGroup layouts - **[Inspector Conditional Display](./inspector-conditional-display.md)** - WShowIf - **[Inspector Settings](./inspector-settings.md)** - Configuration reference --- **Next Steps:** - Replace flag enum dropdowns with `[WEnumToggleButtons]` for better UX - Use `[StringInList]` for scene names, animation states, localization keys - Create provider methods for dynamic option lists - Experiment with `[WValueDropDown]` for type-safe selection controls