diff --git a/.idea/.idea.UnityQuickTips/.idea/indexLayout.xml b/.idea/.idea.UnityQuickTips/.idea/indexLayout.xml
new file mode 100644
index 0000000..f1feadf
--- /dev/null
+++ b/.idea/.idea.UnityQuickTips/.idea/indexLayout.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Assets/SearchableEnum.meta b/Assets/SearchableEnum.meta
new file mode 100644
index 0000000..ed2fde5
--- /dev/null
+++ b/Assets/SearchableEnum.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d0a31054c67400e48b3130f94fbb12cd
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/SearchableEnum/Code.meta b/Assets/SearchableEnum/Code.meta
new file mode 100644
index 0000000..c03d3b2
--- /dev/null
+++ b/Assets/SearchableEnum/Code.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 45ca3fd6dbb69a4408a782f191a3d61e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/SearchableEnum/Code/Editor.meta b/Assets/SearchableEnum/Code/Editor.meta
new file mode 100644
index 0000000..4e37292
--- /dev/null
+++ b/Assets/SearchableEnum/Code/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b00926b4d8db49a4ab61958bba0fc7af
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/SearchableEnum/Code/Editor/SearchableEnumDrawer.cs b/Assets/SearchableEnum/Code/Editor/SearchableEnumDrawer.cs
new file mode 100644
index 0000000..9c16bb5
--- /dev/null
+++ b/Assets/SearchableEnum/Code/Editor/SearchableEnumDrawer.cs
@@ -0,0 +1,97 @@
+// ----------------------------------------------------------------------------
+// Author: Ryan Hipple
+// Date: 05/01/2018
+// ----------------------------------------------------------------------------
+
+using System;
+using UnityEditor;
+using UnityEngine;
+
+namespace RoboRyanTron.SearchableEnum.Editor
+{
+ ///
+ /// Draws the custom enum selector popup for enum fileds using the
+ /// SearchableEnumAttribute.
+ ///
+ [CustomPropertyDrawer(typeof(SearchableEnumAttribute))]
+ public class SearchableEnumDrawer : PropertyDrawer
+ {
+ private const string TYPE_ERROR =
+ "SearchableEnum can only be used on enum fields.";
+
+ ///
+ /// Cache of the hash to use to resolve the ID for the drawer.
+ ///
+ private int idHash;
+
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ // If this is not used on an eunum, show an error
+ if (property.type != "Enum")
+ {
+ GUIStyle errorStyle = "CN EntryErrorIconSmall";
+ Rect r = new Rect(position);
+ r.width = errorStyle.fixedWidth;
+ position.xMin = r.xMax;
+ GUI.Label(r, "", errorStyle);
+ GUI.Label(position, TYPE_ERROR);
+ return;
+ }
+
+ // By manually creating the control ID, we can keep the ID for the
+ // label and button the same. This lets them be selected together
+ // with the keyboard in the inspector, much like a normal popup.
+ if (idHash == 0) idHash = "SearchableEnumDrawer".GetHashCode();
+ int id = GUIUtility.GetControlID(idHash, FocusType.Keyboard, position);
+
+ label = EditorGUI.BeginProperty(position, label, property);
+ position = EditorGUI.PrefixLabel(position, id, label);
+
+ GUIContent buttonText =
+ new GUIContent(property.enumDisplayNames[property.enumValueIndex]);
+ if (DropdownButton(id, position, buttonText))
+ {
+ Action onSelect = i =>
+ {
+ property.enumValueIndex = i;
+ property.serializedObject.ApplyModifiedProperties();
+ };
+
+ SearchablePopup.Show(position, property.enumDisplayNames,
+ property.enumValueIndex, onSelect);
+ }
+ EditorGUI.EndProperty();
+ }
+
+ ///
+ /// A custom button drawer that allows for a controlID so that we can
+ /// sync the button ID and the label ID to allow for keyboard
+ /// navigation like the built-in enum drawers.
+ ///
+ private static bool DropdownButton(int id, Rect position, GUIContent content)
+ {
+ Event current = Event.current;
+ switch (current.type)
+ {
+ case EventType.MouseDown:
+ if (position.Contains(current.mousePosition) && current.button == 0)
+ {
+ Event.current.Use();
+ return true;
+ }
+ break;
+ case EventType.KeyDown:
+ if (GUIUtility.keyboardControl == id && current.character =='\n')
+ {
+ Event.current.Use();
+ return true;
+ }
+ break;
+ case EventType.Repaint:
+ EditorStyles.popup.Draw(position, content, id, false);
+ break;
+ }
+ return false;
+ }
+ }
+}
diff --git a/Assets/SearchableEnum/Code/Editor/SearchableEnumDrawer.cs.meta b/Assets/SearchableEnum/Code/Editor/SearchableEnumDrawer.cs.meta
new file mode 100644
index 0000000..4146bc6
--- /dev/null
+++ b/Assets/SearchableEnum/Code/Editor/SearchableEnumDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 82586c3ea06182a4999528926f6e441b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/SearchableEnum/Code/Editor/SearchablePopup.cs b/Assets/SearchableEnum/Code/Editor/SearchablePopup.cs
new file mode 100644
index 0000000..9916730
--- /dev/null
+++ b/Assets/SearchableEnum/Code/Editor/SearchablePopup.cs
@@ -0,0 +1,366 @@
+// ----------------------------------------------------------------------------
+// Author: Ryan Hipple
+// Date: 05/01/2018
+// ----------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+
+namespace RoboRyanTron.SearchableEnum.Editor
+{
+ ///
+ /// A popup window that displays a list of options and may use a search
+ /// string to filter the displayed content.
+ ///
+ public class SearchablePopup : PopupWindowContent
+ {
+ #region -- Constants --------------------------------------------------
+ /// Height of each element in the popup list.
+ private const float ROW_HEIGHT = 16.0f;
+
+ /// How far to indent list entries.
+ private const float ROW_INDENT = 8.0f;
+
+ /// Name to use for the text field for search.
+ private const string SEARCH_CONTROL_NAME = "EnumSearchText";
+ #endregion -- Constants -----------------------------------------------
+
+ #region -- Static Functions -------------------------------------------
+ /// Show a new SearchablePopup.
+ ///
+ /// Rectangle of the button that triggered the popup.
+ ///
+ /// List of strings to choose from.
+ ///
+ /// Index of the currently selected string.
+ ///
+ ///
+ /// Callback to trigger when a choice is made.
+ ///
+ public static void Show(Rect activatorRect, string[] options, int current, Action onSelectionMade)
+ {
+ SearchablePopup win =
+ new SearchablePopup(options, current, onSelectionMade);
+ PopupWindow.Show(activatorRect, win);
+ }
+
+ ///
+ /// Force the focused window to redraw. This can be used to make the
+ /// popup more responsive to mouse movement.
+ ///
+ private static void Repaint()
+ { EditorWindow.focusedWindow.Repaint(); }
+
+ /// Draw a generic box.
+ /// Where to draw.
+ /// Color to tint the box.
+ private static void DrawBox(Rect rect, Color tint)
+ {
+ Color c = GUI.color;
+ GUI.color = tint;
+ GUI.Box(rect, "", Selection);
+ GUI.color = c;
+ }
+ #endregion -- Static Functions ----------------------------------------
+
+ #region -- Helper Classes ---------------------------------------------
+ ///
+ /// Stores a list of strings and can return a subset of that list that
+ /// matches a given filter string.
+ ///
+ private class FilteredList
+ {
+ ///
+ /// An entry in the filtererd list, mapping the text to the
+ /// original index.
+ ///
+ public struct Entry
+ {
+ public int index;
+ public string text;
+ }
+
+ /// All posibile items in the list.
+ private readonly string[] allItems;
+
+ /// Create a new filtered list.
+ /// All The items to filter.
+ public FilteredList(string[] items)
+ {
+ allItems = items;
+ Entries = new List();
+ UpdateFilter("");
+ }
+
+ /// The current string filtering the list.
+ public string Filter { get; private set; }
+
+ /// All valid entries for the current filter.
+ public List Entries { get; private set; }
+
+ /// Total possible entries in the list.
+ public int MaxLength
+ { get { return allItems.Length; } }
+
+ ///
+ /// Sets a new filter string and updates the Entries that match the
+ /// new filter if it has changed.
+ ///
+ /// String to use to filter the list.
+ ///
+ /// True if the filter is updated, false if newFilter is the same
+ /// as the current Filter and no update is necessary.
+ ///
+ public bool UpdateFilter(string filter)
+ {
+ if (Filter == filter)
+ return false;
+
+ Filter = filter;
+ Entries.Clear();
+
+ for (int i = 0; i < allItems.Length; i++)
+ {
+ if (string.IsNullOrEmpty(Filter) || allItems[i].ToLower().Contains(Filter.ToLower()))
+ {
+ Entry entry = new Entry
+ {
+ index = i,
+ text = allItems[i]
+ };
+ if (string.Equals(allItems[i], Filter, StringComparison.CurrentCultureIgnoreCase))
+ Entries.Insert(0, entry);
+ else
+ Entries.Add(entry);
+ }
+ }
+ return true;
+ }
+ }
+ #endregion -- Helper Classes ------------------------------------------
+
+ #region -- Private Variables ------------------------------------------
+ /// Callback to trigger when an item is selected.
+ private readonly Action onSelectionMade;
+
+ ///
+ /// Index of the item that was selected when the list was opened.
+ ///
+ private readonly int currentIndex;
+
+ ///
+ /// Container for all available options that does the actual string
+ /// filtering of the content.
+ ///
+ private readonly FilteredList list;
+
+ /// Scroll offset for the vertical scroll area.
+ private Vector2 scroll;
+
+ ///
+ /// Index of the item under the mouse or selected with the keyboard.
+ ///
+ private int hoverIndex;
+
+ ///
+ /// An item index to scroll to on the next draw.
+ ///
+ private int scrollToIndex;
+
+ ///
+ /// An offset to apply after scrolling to scrollToIndex. This can be
+ /// used to control if the selection appears at the top, bottom, or
+ /// center of the popup.
+ ///
+ private float scrollOffset;
+ #endregion -- Private Variables ---------------------------------------
+
+ #region -- GUI Styles -------------------------------------------------
+ // GUIStyles implicitly cast from a string. This triggers a lookup into
+ // the current skin which will be the editor skin and lets us get some
+ // built-in styles.
+
+ private static GUIStyle SearchBox = "ToolbarSeachTextField";
+ private static GUIStyle CancelButton = "ToolbarSeachCancelButton";
+ private static GUIStyle DisabledCancelButton = "ToolbarSeachCancelButtonEmpty";
+ private static GUIStyle Selection = "SelectionRect";
+ #endregion -- GUI Styles ----------------------------------------------
+
+ #region -- Initialization ---------------------------------------------
+ private SearchablePopup(string[] names, int currentIndex, Action onSelectionMade)
+ {
+ list = new FilteredList(names);
+ this.currentIndex = currentIndex;
+ this.onSelectionMade = onSelectionMade;
+
+ hoverIndex = currentIndex;
+ scrollToIndex = currentIndex;
+ scrollOffset = GetWindowSize().y - ROW_HEIGHT * 2;
+ }
+ #endregion -- Initialization ------------------------------------------
+
+ #region -- PopupWindowContent Overrides -------------------------------
+ public override void OnOpen()
+ {
+ base.OnOpen();
+ // Force a repaint every frame to be responsive to mouse hover.
+ EditorApplication.update += Repaint;
+ }
+
+ public override void OnClose()
+ {
+ base.OnClose();
+ EditorApplication.update -= Repaint;
+ }
+
+ public override Vector2 GetWindowSize()
+ {
+ return new Vector2(base.GetWindowSize().x,
+ Mathf.Min(600, list.MaxLength * ROW_HEIGHT +
+ EditorStyles.toolbar.fixedHeight));
+ }
+
+ public override void OnGUI(Rect rect)
+ {
+ Rect searchRect = new Rect(0, 0, rect.width, EditorStyles.toolbar.fixedHeight);
+ Rect scrollRect = Rect.MinMaxRect(0, searchRect.yMax, rect.xMax, rect.yMax);
+
+ HandleKeyboard();
+ DrawSearch(searchRect);
+ DrawSelectionArea(scrollRect);
+ }
+ #endregion -- PopupWindowContent Overrides ----------------------------
+
+ #region -- GUI --------------------------------------------------------
+ private void DrawSearch(Rect rect)
+ {
+ if (Event.current.type == EventType.Repaint)
+ EditorStyles.toolbar.Draw(rect, false, false, false, false);
+
+ Rect searchRect = new Rect(rect);
+ searchRect.xMin += 6;
+ searchRect.xMax -= 6;
+ searchRect.y += 2;
+ searchRect.width -= CancelButton.fixedWidth;
+
+ GUI.FocusControl(SEARCH_CONTROL_NAME);
+ GUI.SetNextControlName(SEARCH_CONTROL_NAME);
+ string newText = GUI.TextField(searchRect, list.Filter, SearchBox);
+
+ if (list.UpdateFilter(newText))
+ {
+ hoverIndex = 0;
+ scroll = Vector2.zero;
+ }
+
+ searchRect.x = searchRect.xMax;
+ searchRect.width = CancelButton.fixedWidth;
+
+ if (string.IsNullOrEmpty(list.Filter))
+ GUI.Box(searchRect, GUIContent.none, DisabledCancelButton);
+ else if (GUI.Button(searchRect, "x", CancelButton))
+ {
+ list.UpdateFilter("");
+ scroll = Vector2.zero;
+ }
+ }
+
+ private void DrawSelectionArea(Rect scrollRect)
+ {
+ Rect contentRect = new Rect(0, 0,
+ scrollRect.width - GUI.skin.verticalScrollbar.fixedWidth,
+ list.Entries.Count * ROW_HEIGHT);
+
+ scroll = GUI.BeginScrollView(scrollRect, scroll, contentRect);
+
+ Rect rowRect = new Rect(0, 0, scrollRect.width, ROW_HEIGHT);
+
+ for (int i = 0; i < list.Entries.Count; i++)
+ {
+ if (scrollToIndex == i &&
+ (Event.current.type == EventType.Repaint
+ || Event.current.type == EventType.Layout))
+ {
+ Rect r = new Rect(rowRect);
+ r.y += scrollOffset;
+ GUI.ScrollTo(r);
+ scrollToIndex = -1;
+ scroll.x = 0;
+ }
+
+ if (rowRect.Contains(Event.current.mousePosition))
+ {
+ if (Event.current.type == EventType.MouseMove ||
+ Event.current.type == EventType.ScrollWheel)
+ hoverIndex = i;
+ if (Event.current.type == EventType.MouseDown)
+ {
+ onSelectionMade(list.Entries[i].index);
+ EditorWindow.focusedWindow.Close();
+ }
+ }
+
+ DrawRow(rowRect, i);
+
+ rowRect.y = rowRect.yMax;
+ }
+
+ GUI.EndScrollView();
+ }
+
+ private void DrawRow(Rect rowRect, int i)
+ {
+ if (list.Entries[i].index == currentIndex)
+ DrawBox(rowRect, Color.cyan);
+ else if (i == hoverIndex)
+ DrawBox(rowRect, Color.white);
+
+ Rect labelRect = new Rect(rowRect);
+ labelRect.xMin += ROW_INDENT;
+
+ GUI.Label(labelRect, list.Entries[i].text);
+ }
+
+ ///
+ /// Process keyboard input to navigate the choices or make a selection.
+ ///
+ private void HandleKeyboard()
+ {
+ if (Event.current.type == EventType.KeyDown)
+ {
+ if (Event.current.keyCode == KeyCode.DownArrow)
+ {
+ hoverIndex = Mathf.Min(list.Entries.Count - 1, hoverIndex + 1);
+ Event.current.Use();
+ scrollToIndex = hoverIndex;
+ scrollOffset = ROW_HEIGHT;
+ }
+
+ if (Event.current.keyCode == KeyCode.UpArrow)
+ {
+ hoverIndex = Mathf.Max(0, hoverIndex - 1);
+ Event.current.Use();
+ scrollToIndex = hoverIndex;
+ scrollOffset = -ROW_HEIGHT;
+ }
+
+ if (Event.current.keyCode == KeyCode.Return)
+ {
+ if (hoverIndex >= 0 && hoverIndex < list.Entries.Count)
+ {
+ onSelectionMade(list.Entries[hoverIndex].index);
+ EditorWindow.focusedWindow.Close();
+ }
+ }
+
+ if (Event.current.keyCode == KeyCode.Escape)
+ {
+ EditorWindow.focusedWindow.Close();
+ }
+ }
+ }
+ #endregion -- GUI -----------------------------------------------------
+ }
+}
\ No newline at end of file
diff --git a/Assets/SearchableEnum/Code/Editor/SearchablePopup.cs.meta b/Assets/SearchableEnum/Code/Editor/SearchablePopup.cs.meta
new file mode 100644
index 0000000..7a37faf
--- /dev/null
+++ b/Assets/SearchableEnum/Code/Editor/SearchablePopup.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 50e2e887cad444af9c64f4e7de140858
+timeCreated: 1525227782
\ No newline at end of file
diff --git a/Assets/SearchableEnum/Code/SearchableEnumAttribute.cs b/Assets/SearchableEnum/Code/SearchableEnumAttribute.cs
new file mode 100644
index 0000000..9071dac
--- /dev/null
+++ b/Assets/SearchableEnum/Code/SearchableEnumAttribute.cs
@@ -0,0 +1,18 @@
+// ----------------------------------------------------------------------------
+// Author: Ryan Hipple
+// Date: 05/01/2018
+// ----------------------------------------------------------------------------
+
+using System;
+using UnityEngine;
+
+namespace RoboRyanTron.SearchableEnum
+{
+ ///
+ /// Put this attribute on a public (or SerialzeField) enum in a
+ /// MonoBehaviour or ScriptableObject to get an improved enum selector
+ /// popup. The enum list is scrollable and can be filtered by typing.
+ ///
+ [AttributeUsage(AttributeTargets.Field)]
+ public class SearchableEnumAttribute : PropertyAttribute {}
+}
diff --git a/Assets/SearchableEnum/Code/SearchableEnumAttribute.cs.meta b/Assets/SearchableEnum/Code/SearchableEnumAttribute.cs.meta
new file mode 100644
index 0000000..624e1e2
--- /dev/null
+++ b/Assets/SearchableEnum/Code/SearchableEnumAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5d57bf0a9f2e56043b171bac62dcd313
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/SearchableEnum/Code/SearchableEnumDemo.cs b/Assets/SearchableEnum/Code/SearchableEnumDemo.cs
new file mode 100644
index 0000000..be419c4
--- /dev/null
+++ b/Assets/SearchableEnum/Code/SearchableEnumDemo.cs
@@ -0,0 +1,23 @@
+// ----------------------------------------------------------------------------
+// Author: Ryan Hipple
+// Date: 05/01/2018
+// ----------------------------------------------------------------------------
+
+using UnityEngine;
+
+namespace RoboRyanTron.SearchableEnum
+{
+ ///
+ /// A demo of the SearchableEnumPopup on a ScriptableObject.
+ ///
+ [CreateAssetMenu]
+ public class SearchableEnumDemo : ScriptableObject
+ {
+ [Tooltip("This enum is fucking miserable.")]
+ public KeyCode LameKeyCode;
+
+ [Tooltip("The finest enum browsing experience one can have.")]
+ [SearchableEnum]
+ public KeyCode AwesomeKeyCode;
+ }
+}
diff --git a/Assets/SearchableEnum/Code/SearchableEnumDemo.cs.meta b/Assets/SearchableEnum/Code/SearchableEnumDemo.cs.meta
new file mode 100644
index 0000000..ab60d1b
--- /dev/null
+++ b/Assets/SearchableEnum/Code/SearchableEnumDemo.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7234ba95444a32844ac7025034e83bd2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/SearchableEnum/SearchableEnumDemo.asset b/Assets/SearchableEnum/SearchableEnumDemo.asset
new file mode 100644
index 0000000..fcaf798
--- /dev/null
+++ b/Assets/SearchableEnum/SearchableEnumDemo.asset
@@ -0,0 +1,16 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 7234ba95444a32844ac7025034e83bd2, type: 3}
+ m_Name: SearchableEnumDemo
+ m_EditorClassIdentifier:
+ AwesomeKeyCode: 97
+ LameKeyCode: 273
+ Node: 0
diff --git a/Assets/SearchableEnum/SearchableEnumDemo.asset.meta b/Assets/SearchableEnum/SearchableEnumDemo.asset.meta
new file mode 100644
index 0000000..2aa9194
--- /dev/null
+++ b/Assets/SearchableEnum/SearchableEnumDemo.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b0179ebdc142a954aa7579c2c09de6b9
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/manifest.json b/Packages/manifest.json
new file mode 100644
index 0000000..526aca6
--- /dev/null
+++ b/Packages/manifest.json
@@ -0,0 +1,4 @@
+{
+ "dependencies": {
+ }
+}
diff --git a/ProjectSettings/PresetManager.asset b/ProjectSettings/PresetManager.asset
new file mode 100644
index 0000000..636a595
--- /dev/null
+++ b/ProjectSettings/PresetManager.asset
@@ -0,0 +1,6 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1386491679 &1
+PresetManager:
+ m_ObjectHideFlags: 0
+ m_DefaultList: []
diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt
index a211ccd..b55040d 100644
--- a/ProjectSettings/ProjectVersion.txt
+++ b/ProjectSettings/ProjectVersion.txt
@@ -1 +1 @@
-m_EditorVersion: 2017.1.1f1
+m_EditorVersion: 2018.1.0f1