|
|
|
using UnityEditor;
|
|
|
|
using UnityEngine;
|
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
namespace Fungus.EditorUtils
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Common base for PopupWindowContent that is a search filterable list a la AddComponent
|
|
|
|
///
|
|
|
|
/// Inspired by https://github.com/roboryantron/UnityEditorJunkie/blob/master/Assets/SearchableEnum/Code/Editor/SearchablePopup.cs
|
|
|
|
/// </summary>
|
|
|
|
public abstract class BasePopupWindowContent : PopupWindowContent
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Called when the user has confirmed an item from the menu.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="index">Index of into the original list of items to show given to the popupcontent</param>
|
|
|
|
abstract protected void SelectByOrigIndex(int index);
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Called during Base Ctor, must fill allItems list so the ctor can continue to fill
|
|
|
|
/// the visible items and current selected index.
|
|
|
|
/// </summary>
|
|
|
|
abstract protected void PrepareAllItems();
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Internal representation of 1 row of our popup list
|
|
|
|
/// </summary>
|
|
|
|
public class FilteredListItem
|
|
|
|
{
|
|
|
|
public FilteredListItem(int index, string str, string tip = "")
|
|
|
|
{
|
|
|
|
origIndex = index;
|
|
|
|
name = str;
|
|
|
|
lowerName = str.ToLowerInvariant();
|
|
|
|
content = new GUIContent(str, tip);
|
|
|
|
}
|
|
|
|
public int origIndex;
|
|
|
|
public string name, lowerName;
|
|
|
|
public GUIContent content;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected int hoverIndex;
|
|
|
|
protected readonly string SEARCH_CONTROL_NAME = "PopupSearchControlName";
|
|
|
|
protected readonly float ROW_HEIGHT = EditorGUIUtility.singleLineHeight;
|
|
|
|
protected List<FilteredListItem> allItems = new List<FilteredListItem>(),
|
|
|
|
visibleItems = new List<FilteredListItem>();
|
|
|
|
protected string currentFilter = string.Empty;
|
|
|
|
protected Vector2 scroll;
|
|
|
|
protected int scrollToIndex;
|
|
|
|
protected float scrollOffset;
|
|
|
|
protected int currentIndex;
|
|
|
|
protected Vector2 size;
|
|
|
|
protected bool hasNoneOption = false;
|
|
|
|
|
|
|
|
static readonly char[] SEARCH_SPLITS = new char[]{ CATEGORY_CHAR, ' ' };
|
|
|
|
protected static readonly char CATEGORY_CHAR = '/';
|
|
|
|
|
|
|
|
public BasePopupWindowContent(string currentHandlerName, int width, int height, bool showNoneOption = false)
|
|
|
|
{
|
|
|
|
this.size = new Vector2(width, height);
|
|
|
|
hasNoneOption = showNoneOption;
|
|
|
|
|
|
|
|
PrepareAllItems();
|
|
|
|
|
|
|
|
allItems.Sort((lhs, rhs) =>
|
|
|
|
{
|
|
|
|
//order root level objects first
|
|
|
|
var islhsRoot = lhs.lowerName.IndexOf(CATEGORY_CHAR) != -1;
|
|
|
|
var isrhsRoot = rhs.lowerName.IndexOf(CATEGORY_CHAR) != -1;
|
|
|
|
|
|
|
|
if(islhsRoot == isrhsRoot)
|
|
|
|
return lhs.lowerName.CompareTo(rhs.lowerName);
|
|
|
|
return islhsRoot ? 1 : -1;
|
|
|
|
});
|
|
|
|
UpdateFilter();
|
|
|
|
currentIndex = Mathf.Max(0, visibleItems.FindIndex(x=>x.name.Contains(currentHandlerName)));
|
|
|
|
hoverIndex = currentIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override Vector2 GetWindowSize()
|
|
|
|
{
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
GUI.FocusControl(SEARCH_CONTROL_NAME);
|
|
|
|
GUI.SetNextControlName(SEARCH_CONTROL_NAME);
|
|
|
|
var prevFilter = currentFilter;
|
|
|
|
currentFilter = GUI.TextField(searchRect, currentFilter);
|
|
|
|
|
|
|
|
if (prevFilter != currentFilter)
|
|
|
|
{
|
|
|
|
UpdateFilter();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void UpdateFilter()
|
|
|
|
{
|
|
|
|
var curlower = currentFilter.ToLowerInvariant();
|
|
|
|
var lowers = curlower.Split(SEARCH_SPLITS);
|
|
|
|
lowers = lowers.Where(x => x.Length > 0).ToArray();
|
|
|
|
|
|
|
|
if (lowers == null || lowers.Length == 0)
|
|
|
|
{
|
|
|
|
visibleItems.AddRange(allItems);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
visibleItems = allItems.Where(x =>
|
|
|
|
{
|
|
|
|
//we want all tokens
|
|
|
|
foreach (var item in lowers)
|
|
|
|
{
|
|
|
|
if (!x.lowerName.Contains(item))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}).ToList();
|
|
|
|
}
|
|
|
|
|
|
|
|
hoverIndex = 0;
|
|
|
|
scroll = Vector2.zero;
|
|
|
|
if(hasNoneOption)
|
|
|
|
visibleItems.Insert(0, new FilteredListItem(-1, "None"));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void DrawSelectionArea(Rect scrollRect)
|
|
|
|
{
|
|
|
|
Rect contentRect = new Rect(0, 0,
|
|
|
|
scrollRect.width - GUI.skin.verticalScrollbar.fixedWidth,
|
|
|
|
visibleItems.Count * ROW_HEIGHT);
|
|
|
|
|
|
|
|
scroll = GUI.BeginScrollView(scrollRect, scroll, contentRect);
|
|
|
|
|
|
|
|
Rect rowRect = new Rect(0, 0, scrollRect.width, ROW_HEIGHT);
|
|
|
|
|
|
|
|
for (int i = 0; i < visibleItems.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)
|
|
|
|
{
|
|
|
|
//if new item force update so it's snappier
|
|
|
|
if (hoverIndex != 1)
|
|
|
|
{
|
|
|
|
this.editorWindow.Repaint();
|
|
|
|
}
|
|
|
|
|
|
|
|
hoverIndex = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Event.current.type == EventType.MouseDown)
|
|
|
|
{
|
|
|
|
//onSelectionMade(list.Entries[i].Index);
|
|
|
|
SelectByOrigIndex(visibleItems[i].origIndex);
|
|
|
|
EditorWindow.focusedWindow.Close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DrawRow(rowRect, i);
|
|
|
|
|
|
|
|
rowRect.y = rowRect.yMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
GUI.EndScrollView();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void DrawBox(Rect rect, Color tint)
|
|
|
|
{
|
|
|
|
Color c = GUI.color;
|
|
|
|
GUI.color = tint;
|
|
|
|
GUI.Box(rect, "");
|
|
|
|
GUI.color = c;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void DrawRow(Rect rowRect, int i)
|
|
|
|
{
|
|
|
|
if (i == 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, visibleItems[i].content);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Process keyboard input to navigate the choices or make a selection.
|
|
|
|
/// </summary>
|
|
|
|
private void HandleKeyboard()
|
|
|
|
{
|
|
|
|
if (Event.current.type == EventType.KeyDown)
|
|
|
|
{
|
|
|
|
if (Event.current.keyCode == KeyCode.DownArrow)
|
|
|
|
{
|
|
|
|
hoverIndex = Mathf.Min(visibleItems.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 < visibleItems.Count)
|
|
|
|
{
|
|
|
|
SelectByOrigIndex(visibleItems[hoverIndex].origIndex);
|
|
|
|
EditorWindow.focusedWindow.Close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Event.current.keyCode == KeyCode.Escape)
|
|
|
|
{
|
|
|
|
EditorWindow.focusedWindow.Close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|