// This code is part of the Fungus library (https://github.com/snozbot/fungus)
// It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE)
using UnityEditor;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Fungus.EditorUtils
{
///
/// 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
///
public abstract class BasePopupWindowContent : PopupWindowContent
{
///
/// Called when the user has confirmed an item from the menu.
///
/// Index of into the original list of items to show given to the popupcontent
abstract protected void SelectByOrigIndex(int index);
///
/// Called during Base Ctor, must fill allItems list so the ctor can continue to fill
/// the visible items and current selected index.
///
abstract protected void PrepareAllItems();
///
/// Internal representation of 1 row of our popup list
///
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 allItems = new List(),
visibleItems = new List();
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);
}
///
/// 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(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();
}
}
}
}
}