|
// 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; |
|
} |
|
} |
|
} |