Skip to content

Instantly share code, notes, and snippets.

@zledas
Created June 20, 2026 20:09
Show Gist options
  • Select an option

  • Save zledas/33e3992344220d8eea2667739c1ce0e6 to your computer and use it in GitHub Desktop.

Select an option

Save zledas/33e3992344220d8eea2667739c1ce0e6 to your computer and use it in GitHub Desktop.
Improved searchable `enum` value selector for Unity

Improved searchable enum value selector for Unity

This is an improved enum value selector for Unity editor. It offers value selection popup with searchable list of enum values.

Under the hood it uses Unitys AdvancedDropdown class, but as it has a lot of internal and private functionality, this "exposes" some of it to have nicer behaviour. No external dependencies.

To use it, add [SearchableEnum] attribute to the enum field.

Also:

  • Use [InspectorName("")] on enum option to hide enum value from the list.
  • Use [InspectorName("-")] on enum option to add a separator. You will need to declare separate enum option, but keep in mind that you can reuse value for that option.

Examples

An example how to use [SearchableEnum] attribute on the field and how to add a separator.

public enum Weekdays {
	Monday = 1,
	Tuesday = 2,
	Wednesday = 3,
	Thursday = 4,
	Friday = 5,
	[InspectorName("-")]
	_separator = Friday,
	Saturday = 6,
	Sunday = 7,
}

[SearchableEnum]
public Weekdays weekday;

Example image 1

Another example that shows how to limit selector popup max height. It is extremely useful if you have a big list of values. This example also shows how to hide some values from the selection list.

public enum EuropeanUnionCountries {
	Austria,
	Belgium,
	Bulgaria,
	Croatia,
	Cyprus,
	CzechRepublic,
	Denmark,
	Estonia,
	Finland,
	France,
	Germany,
	Greece,
	Hungary,
	Ireland,
	Italy,
	Latvia,
	Lithuania,
	Luxembourg,
	Malta,
	Netherlands,
	Poland,
	Portugal,
	Romania,
	Slovakia,
	Slovenia,
	Spain,
	Sweden,

	[InspectorName("")]
	HiddenItem1,
	[InspectorName("")]
	HiddenItem2,
}

[SearchableEnum(300)]
public EuropeanUnionCountries country;

Example image 2

License

MIT No Attribution

Copyright 2026 Žilvinas Ledas

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// MIT No Attribution
//
// Copyright 2026 Žilvinas Ledas
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using UnityEngine;
/// <summary>
/// Show value selection popup with searchable list of enum values.
/// `[InspectorName("")]` to hide enum value from the list.
/// `[InspectorName("-")]` to add a separator. You will need to declare separate enum option, but keep in mind that you can reuse value for that option.
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public class SearchableEnumAttribute: PropertyAttribute {
public readonly int maxHeight;
public SearchableEnumAttribute(int maxHeight = -1) {
this.maxHeight = maxHeight;
}
}
// MIT No Attribution
//
// Copyright 2026 Žilvinas Ledas
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using UnityEditor;
using UnityEngine;
using UnityEditor.IMGUI.Controls;
using System.Reflection;
using System.Collections.Generic;
namespace tojPlugin {
[CustomPropertyDrawer(typeof(SearchableEnumAttribute))]
public class SearchableEnumPropertyDrawer: PropertyDrawer {
private StringOptionsAdvancedDropdown _dropdown;
private SerializedProperty _property;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return EditorGUI.GetPropertyHeight(property, true);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
if (property.propertyType != SerializedPropertyType.Enum) {
EditorGUI.PropertyField(position, property, label);
return;
}
if (_dropdown == null) {
_dropdown = new StringOptionsAdvancedDropdown(property.enumDisplayNames, property.displayName, new AdvancedDropdownState());
_dropdown.onOptionSelected += OnDropdownOptionSelected;
}
position = EditorGUI.PrefixLabel(position, label);
if (
GUI.Button(
position,
new GUIContent((property.enumValueIndex == -1) ? property.longValue.ToString() : property.enumDisplayNames[property.enumValueIndex]),
EditorStyles.popup
)
) {
_property = property;
SearchableEnumAttribute searchableEnumAttribute = (SearchableEnumAttribute)this.attribute;
_dropdown.Show(position, property.enumValueIndex, searchableEnumAttribute.maxHeight);
}
}
private void OnDropdownOptionSelected(int enumValueIndex) {
_property.enumValueIndex = enumValueIndex;
_property.serializedObject.ApplyModifiedProperties();
}
}
public class StringOptionsAdvancedDropdown: AdvancedDropdown {
private string[] _optionNames;
private Dictionary<int, int> _optionIndexToSelectionItemIndex;
private AdvancedDropdownState _advancedDropdownState;
private AdvancedDropdownItem _root;
public event Action<int> onOptionSelected;
public StringOptionsAdvancedDropdown(string[] optionNames, string title, AdvancedDropdownState advancedDropdownState): base(advancedDropdownState) {
_optionNames = optionNames;
_advancedDropdownState = advancedDropdownState;
_optionIndexToSelectionItemIndex = new Dictionary<int, int>(optionNames.Length);
_root = new AdvancedDropdownItem(title);
int selectionItemIndex = 0;
for (int i = 0; i < _optionNames.Length; i++) {
if (string.IsNullOrEmpty(_optionNames[i])) {
continue;
}
if (_optionNames[i] == "-") {
_root.AddSeparator();
} else {
var item = new AdvancedDropdownItem(_optionNames[i]);
_root.AddChild(item);
// `id` is overriden in `AddChild()`, so we set it aterwards.
item.id = i;
_optionIndexToSelectionItemIndex.Add(i, selectionItemIndex);
}
selectionItemIndex++;
}
}
private static MethodInfo AdvancedDropdown_maximumSizeGetMethod = typeof(AdvancedDropdown).GetProperty("maximumSize", BindingFlags.NonPublic | BindingFlags.Instance).GetGetMethod(true);
private static MethodInfo AdvancedDropdown_maximumSizeSetMethod = typeof(AdvancedDropdown).GetProperty("maximumSize", BindingFlags.NonPublic | BindingFlags.Instance).GetSetMethod(true);
private static readonly object[] oneParam = new object[1];
static readonly MethodInfo AdvancedDropdownState_SetSelectedIndexMethod = typeof(AdvancedDropdownState).GetMethod("SetSelectedIndex", BindingFlags.Instance | BindingFlags.NonPublic);
static readonly object[] twoParams = new object[2];
private static FieldInfo AdvancedDropdown_m_WindowInstance = typeof(AdvancedDropdown).GetField("m_WindowInstance", BindingFlags.NonPublic | BindingFlags.Instance);
private static FieldInfo AdvancedDropdownWindow_m_InitialSelectionPositionField = null; //typeof(AdvancedDropdownWindow).GetField("m_InitialSelectionPosition", BindingFlags.NonPublic | BindingFlags.Instance);
public void Show(Rect buttonRect, int optionIndex, float maxHeight = -1) {
if (maxHeight != -1) {
Vector2 maximumSize = (Vector2)AdvancedDropdown_maximumSizeGetMethod.Invoke(this, null);
maximumSize.y = maxHeight;
oneParam[0] = maximumSize;
AdvancedDropdown_maximumSizeSetMethod.Invoke(this, oneParam);
}
if (_optionIndexToSelectionItemIndex.TryGetValue(optionIndex, out int selectionIndex)) {
twoParams[0] = _root;
twoParams[1] = selectionIndex;
AdvancedDropdownState_SetSelectedIndexMethod.Invoke(_advancedDropdownState, twoParams);
}
Show(buttonRect);
if ((maxHeight != -1) && (optionIndex != -1)) {
var v = AdvancedDropdown_m_WindowInstance.GetValue(this);
if (AdvancedDropdownWindow_m_InitialSelectionPositionField == null) {
AdvancedDropdownWindow_m_InitialSelectionPositionField = v.GetType().GetField("m_InitialSelectionPosition", BindingFlags.NonPublic | BindingFlags.Instance);
}
// At least on Unity 6.3.11, without this - selected item is hidden behind top header and search bar...
AdvancedDropdownWindow_m_InitialSelectionPositionField.SetValue(v, (float)AdvancedDropdownWindow_m_InitialSelectionPositionField.GetValue(v) - 43f);
}
}
protected override void ItemSelected(AdvancedDropdownItem item) {
if (item.name != "SEPARATOR") {
onOptionSelected?.Invoke(item.id);
}
}
protected override AdvancedDropdownItem BuildRoot() {
return _root;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment