|
|
|
|
// UltEvents // Copyright 2020 Kybernetik //
|
|
|
|
|
// Copied from Kybernetik.Core.
|
|
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace UltEvents.Editor
|
|
|
|
|
{
|
|
|
|
|
/// <summary>[Editor-Only] Allows you to draw GUI fields which can be used to pick an object from a list.</summary>
|
|
|
|
|
public static class ObjectPicker
|
|
|
|
|
{
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
#region Main Drawing Methods
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
/// <summary>Draws a field which lets you pick an object from a list and returns the selected object.</summary>
|
|
|
|
|
public static T Draw<T>(Rect area, T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop,
|
|
|
|
|
GUIStyle style)
|
|
|
|
|
{
|
|
|
|
|
var id = CheckCommand(ref selected);
|
|
|
|
|
|
|
|
|
|
if (GUI.Button(area, getLabel(selected), style))
|
|
|
|
|
ObjectPickerWindow.Show(id, selected, getOptions(), suggestions, getLabel);
|
|
|
|
|
|
|
|
|
|
CheckDragAndDrop(area, ref selected, getOptions, getDragAndDrop);
|
|
|
|
|
|
|
|
|
|
return selected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Draws a field which lets you pick an object from a list and returns the selected object.</summary>
|
|
|
|
|
public static T Draw<T>(Rect area, T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop)
|
|
|
|
|
{
|
|
|
|
|
return Draw(area, selected, getOptions, suggestions, getLabel, getDragAndDrop, InternalGUI.TypeButtonStyle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
/// <summary>Draws a field (using GUILayout) which lets you pick an object from a list and returns the selected object.</summary>
|
|
|
|
|
public static T DrawLayout<T>(T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop,
|
|
|
|
|
GUIStyle style, params GUILayoutOption[] layoutOptions)
|
|
|
|
|
{
|
|
|
|
|
var id = CheckCommand(ref selected);
|
|
|
|
|
|
|
|
|
|
if (GUILayout.Button(getLabel(selected), style, layoutOptions))
|
|
|
|
|
ObjectPickerWindow.Show(id, selected, getOptions(), suggestions, getLabel);
|
|
|
|
|
|
|
|
|
|
CheckDragAndDrop(GUILayoutUtility.GetLastRect(), ref selected, getOptions, getDragAndDrop);
|
|
|
|
|
|
|
|
|
|
return selected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Draws a field (using GUILayout) which lets you pick an object from a list and returns the selected object.</summary>
|
|
|
|
|
public static T DrawLayout<T>(T selected, Func<List<T>> getOptions, int suggestions, Func<T, GUIContent> getLabel, Func<T> getDragAndDrop,
|
|
|
|
|
params GUILayoutOption[] layoutOptions)
|
|
|
|
|
{
|
|
|
|
|
return DrawLayout(selected, getOptions, suggestions, getLabel, getDragAndDrop, InternalGUI.TypeButtonStyle, layoutOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Draws a field (as an inspector field using GUILayout) which lets you pick an object from a list and returns
|
|
|
|
|
/// the selected object.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static T DrawEditorLayout<T>(GUIContent label, T selected, Func<List<T>> getOptions, int suggestions,
|
|
|
|
|
Func<T, GUIContent> getLabel, Func<T> getDragAndDrop, GUIStyle style, params GUILayoutOption[] layoutOptions)
|
|
|
|
|
{
|
|
|
|
|
GUILayout.BeginHorizontal();
|
|
|
|
|
{
|
|
|
|
|
GUILayout.Label(label, GUILayout.Width(EditorGUIUtility.labelWidth - 4));
|
|
|
|
|
selected = DrawLayout(selected, getOptions, suggestions, getLabel, getDragAndDrop, style, layoutOptions);
|
|
|
|
|
}
|
|
|
|
|
GUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
|
|
return selected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Draws a field (as an inspector field using GUILayout) which lets you pick an object from a list and returns
|
|
|
|
|
/// the selected object.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static T DrawEditorLayout<T>(GUIContent label, T selected, Func<List<T>> getOptions, int suggestions,
|
|
|
|
|
Func<T, GUIContent> getLabel, Func<T> getDragAndDrop, params GUILayoutOption[] options)
|
|
|
|
|
{
|
|
|
|
|
return DrawEditorLayout(label, selected, getOptions, suggestions, getLabel, getDragAndDrop, InternalGUI.TypeButtonStyle, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
#endregion
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
#region Type Field
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
/// <summary>Draws a field which lets you pick a <see cref="Type"></see> from a list and returns the selected type.</summary>
|
|
|
|
|
public static Type DrawTypeField(Rect area, Type selected, Func<List<Type>> getOptions, int suggestions, GUIStyle style)
|
|
|
|
|
{
|
|
|
|
|
return Draw(area, selected, getOptions, suggestions,
|
|
|
|
|
getLabel: (type) => new GUIContent(type != null ? type.GetNameCS() : "null"),
|
|
|
|
|
getDragAndDrop: () => DragAndDrop.objectReferences[0].GetType(),
|
|
|
|
|
style: style);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
/// <summary>Draws a field which lets you pick an asset <see cref="Type"></see> from a list and returns the selected type.</summary>
|
|
|
|
|
public static Type DrawAssetTypeField(Rect area, Type selected, Func<List<Type>> getOptions, int suggestions, GUIStyle style)
|
|
|
|
|
{
|
|
|
|
|
return Draw(area, selected, getOptions, suggestions,
|
|
|
|
|
getLabel: (type) => new GUIContent(type != null ? type.GetNameCS() : "null", AssetPreview.GetMiniTypeThumbnail(type)),
|
|
|
|
|
getDragAndDrop: () => DragAndDrop.objectReferences[0].GetType(),
|
|
|
|
|
style: style);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
/// <summary>Draws a field which lets you pick a <see cref="Type"></see> from a list and returns the selected <see cref="Type.AssemblyQualifiedName"/>.</summary>
|
|
|
|
|
public static string DrawTypeField(Rect area, string selectedTypeName, Func<List<Type>> getOptions, int suggestions, GUIStyle style)
|
|
|
|
|
{
|
|
|
|
|
var selected = Type.GetType(selectedTypeName);
|
|
|
|
|
|
|
|
|
|
selected = Draw(area, selected, getOptions, suggestions,
|
|
|
|
|
getLabel: (type) => new GUIContent(type != null ? type.GetNameCS() : "No Type Selected"),
|
|
|
|
|
getDragAndDrop: () => DragAndDrop.objectReferences[0].GetType(),
|
|
|
|
|
style: style);
|
|
|
|
|
|
|
|
|
|
return selected != null ? selected.AssemblyQualifiedName : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
#endregion
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
#region Utils
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Removes any duplicates of the first few elements in `options` (from 0 to `suggestions`) from anywhere later
|
|
|
|
|
/// in the list.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static void RemoveDuplicateSuggestions<T>(List<T> options, int suggestions) where T : class
|
|
|
|
|
{
|
|
|
|
|
for (int i = options.Count - 1; i >= suggestions; i--)
|
|
|
|
|
{
|
|
|
|
|
var obj = options[i];
|
|
|
|
|
for (int j = 0; j < suggestions; j++)
|
|
|
|
|
{
|
|
|
|
|
if (obj == options[j])
|
|
|
|
|
{
|
|
|
|
|
options.RemoveAt(j);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private static int CheckCommand<T>(ref T selected)
|
|
|
|
|
{
|
|
|
|
|
var id = GUIUtility.GetControlID(FocusType.Passive);
|
|
|
|
|
ObjectPickerWindow.TryGetPickedObject(id, ref selected);
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private static void CheckDragAndDrop<T>(Rect area, ref T selected, Func<List<T>> getOptions, Func<T> getDragAndDrop)
|
|
|
|
|
{
|
|
|
|
|
var currentEvent = Event.current;
|
|
|
|
|
if (DragAndDrop.objectReferences.Length == 1 && area.Contains(currentEvent.mousePosition))
|
|
|
|
|
{
|
|
|
|
|
var drop = getDragAndDrop();
|
|
|
|
|
|
|
|
|
|
// If the dragged object is a valid type, continue.
|
|
|
|
|
if (!getOptions().Contains(drop))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (currentEvent.type == EventType.DragUpdated || currentEvent.type == EventType.MouseDrag)
|
|
|
|
|
{
|
|
|
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
|
|
|
|
|
}
|
|
|
|
|
else if (currentEvent.type == EventType.DragPerform)
|
|
|
|
|
{
|
|
|
|
|
selected = drop;
|
|
|
|
|
DragAndDrop.AcceptDrag();
|
|
|
|
|
GUI.changed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private static class InternalGUI
|
|
|
|
|
{
|
|
|
|
|
public static readonly GUIStyle
|
|
|
|
|
TypeButtonStyle;
|
|
|
|
|
|
|
|
|
|
static InternalGUI()
|
|
|
|
|
{
|
|
|
|
|
TypeButtonStyle = new GUIStyle(EditorStyles.miniButton)
|
|
|
|
|
{
|
|
|
|
|
alignment = TextAnchor.MiddleLeft
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
#endregion
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
internal sealed class ObjectPickerWindow : EditorWindow
|
|
|
|
|
{
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private static int _FieldID;
|
|
|
|
|
private static bool _HasPickedObject;
|
|
|
|
|
private static object _PickedObject;
|
|
|
|
|
|
|
|
|
|
private readonly List<GUIContent>
|
|
|
|
|
Labels = new List<GUIContent>(),
|
|
|
|
|
SearchedLabels = new List<GUIContent>();
|
|
|
|
|
private readonly List<object>
|
|
|
|
|
SearchedObjects = new List<object>();
|
|
|
|
|
|
|
|
|
|
[NonSerialized]
|
|
|
|
|
private object _SelectedObject;
|
|
|
|
|
|
|
|
|
|
[NonSerialized]
|
|
|
|
|
private IList _Objects;
|
|
|
|
|
|
|
|
|
|
[NonSerialized]
|
|
|
|
|
private int _Suggestions;
|
|
|
|
|
|
|
|
|
|
[NonSerialized]
|
|
|
|
|
private int _LabelWidthCalculationProgress;
|
|
|
|
|
|
|
|
|
|
[NonSerialized]
|
|
|
|
|
private float _MaxLabelWidth;
|
|
|
|
|
|
|
|
|
|
[NonSerialized]
|
|
|
|
|
private string _SearchText = "";
|
|
|
|
|
|
|
|
|
|
[NonSerialized]
|
|
|
|
|
private Vector2 _ScrollPosition;
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private bool HasSearchText
|
|
|
|
|
{
|
|
|
|
|
get { return !string.IsNullOrEmpty(_SearchText); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
public static void Show<T>(int fieldID, T selected, List<T> objects, int suggestions, Func<T, GUIContent> getLabel)
|
|
|
|
|
{
|
|
|
|
|
if (objects == null || objects.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError("'objects' list is null or empty.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_FieldID = fieldID;
|
|
|
|
|
_HasPickedObject = false;
|
|
|
|
|
|
|
|
|
|
var window = CreateInstance<ObjectPickerWindow>();
|
|
|
|
|
window.titleContent = new GUIContent("Pick a " + typeof(T).GetNameCS());
|
|
|
|
|
window.minSize = new Vector2(112, 100);
|
|
|
|
|
|
|
|
|
|
if (window.Labels.Capacity < objects.Count)
|
|
|
|
|
window.Labels.Capacity = objects.Count;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < objects.Count; i++)
|
|
|
|
|
window.Labels.Add(getLabel(objects[i]));
|
|
|
|
|
|
|
|
|
|
//Debug.LogTemp("Showing Object Picker Window: " + window._Labels.DeepToString());
|
|
|
|
|
|
|
|
|
|
window._SelectedObject = selected;
|
|
|
|
|
window._Objects = objects;
|
|
|
|
|
window._Suggestions = suggestions;
|
|
|
|
|
|
|
|
|
|
// Auto-Scroll to the selected object.
|
|
|
|
|
if (selected != null)
|
|
|
|
|
{
|
|
|
|
|
object sel = selected;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < window._Objects.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
if (sel == window._Objects[i])
|
|
|
|
|
{
|
|
|
|
|
window._ScrollPosition = new Vector2(0, i * InternalGUI.LabelHeight);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//window._Objects.LogErrorIfModified("the '" + nameof(objects) + "' list passed into " + Reflection.GetCallingMethod(2).GetNameCS());
|
|
|
|
|
|
|
|
|
|
window.ShowAuxWindow();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
public static void TryGetPickedObject<T>(int fieldID, ref T picked)
|
|
|
|
|
{
|
|
|
|
|
if (_HasPickedObject && _FieldID == fieldID)
|
|
|
|
|
{
|
|
|
|
|
picked = (T)_PickedObject;
|
|
|
|
|
_PickedObject = null;
|
|
|
|
|
_HasPickedObject = false;
|
|
|
|
|
GUI.changed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void PickAndClose()
|
|
|
|
|
{
|
|
|
|
|
_PickedObject = _SelectedObject;
|
|
|
|
|
_HasPickedObject = true;
|
|
|
|
|
Close();
|
|
|
|
|
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void OnGUI()
|
|
|
|
|
{
|
|
|
|
|
switch (Event.current.type)
|
|
|
|
|
{
|
|
|
|
|
case EventType.MouseMove:
|
|
|
|
|
case EventType.Layout:
|
|
|
|
|
case EventType.DragUpdated:
|
|
|
|
|
case EventType.DragPerform:
|
|
|
|
|
case EventType.DragExited:
|
|
|
|
|
case EventType.Ignore:
|
|
|
|
|
case EventType.Used:
|
|
|
|
|
case EventType.ValidateCommand:
|
|
|
|
|
case EventType.ExecuteCommand:
|
|
|
|
|
case EventType.ContextClick:
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (CheckInput())
|
|
|
|
|
{
|
|
|
|
|
Event.current.Use();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UpdateLabelWidthCalculation();
|
|
|
|
|
|
|
|
|
|
var area = new Rect(0, 0, position.width, position.height);
|
|
|
|
|
|
|
|
|
|
DrawSearchBar(ref area);
|
|
|
|
|
|
|
|
|
|
area.yMax = position.height;
|
|
|
|
|
|
|
|
|
|
var viewRect = CalculateViewRect(area.height);
|
|
|
|
|
|
|
|
|
|
// Selection List.
|
|
|
|
|
_ScrollPosition = GUI.BeginScrollView(area, _ScrollPosition, viewRect);
|
|
|
|
|
{
|
|
|
|
|
// Figure out how many fields are actually visible.
|
|
|
|
|
int firstVisibleField, lastVisibleField;
|
|
|
|
|
DetermineVisibleRange(out firstVisibleField, out lastVisibleField);
|
|
|
|
|
|
|
|
|
|
if (HasSearchText)// Active Search.
|
|
|
|
|
{
|
|
|
|
|
DrawSearchedOptions(viewRect, firstVisibleField, lastVisibleField);
|
|
|
|
|
}
|
|
|
|
|
else// No Search.
|
|
|
|
|
{
|
|
|
|
|
DrawAllOptions(viewRect, firstVisibleField, lastVisibleField);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
GUI.EndScrollView(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void UpdateLabelWidthCalculation()
|
|
|
|
|
{
|
|
|
|
|
if (_LabelWidthCalculationProgress < Labels.Count)
|
|
|
|
|
{
|
|
|
|
|
var calculationCount = 0;
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
var label = Labels[_LabelWidthCalculationProgress];
|
|
|
|
|
|
|
|
|
|
var width = InternalGUI.ButtonStyle.CalcSize(label).x;
|
|
|
|
|
if (_MaxLabelWidth < width)
|
|
|
|
|
_MaxLabelWidth = width;
|
|
|
|
|
}
|
|
|
|
|
while (++_LabelWidthCalculationProgress < Labels.Count && calculationCount++ < 100);
|
|
|
|
|
|
|
|
|
|
Repaint();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private bool CheckInput()
|
|
|
|
|
{
|
|
|
|
|
var currentEvent = Event.current;
|
|
|
|
|
if (currentEvent.type == EventType.KeyUp)
|
|
|
|
|
{
|
|
|
|
|
switch (currentEvent.keyCode)
|
|
|
|
|
{
|
|
|
|
|
case KeyCode.Return:
|
|
|
|
|
PickAndClose();
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case KeyCode.Escape:
|
|
|
|
|
Close();
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case KeyCode.UpArrow:
|
|
|
|
|
OffsetSelectedIndex(-1);
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case KeyCode.DownArrow:
|
|
|
|
|
OffsetSelectedIndex(1);
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void DrawSearchBar(ref Rect area)
|
|
|
|
|
{
|
|
|
|
|
area.height = InternalGUI.SearchBarHeight;
|
|
|
|
|
GUI.BeginGroup(area, EditorStyles.toolbar);
|
|
|
|
|
{
|
|
|
|
|
area.x += 2;
|
|
|
|
|
area.y += 2;
|
|
|
|
|
area.width -= InternalGUI.SearchBarEndStyle.fixedWidth + 4;
|
|
|
|
|
|
|
|
|
|
GUI.SetNextControlName("SearchFilter");
|
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
var searchText = GUI.TextField(area, _SearchText, InternalGUI.SearchBarStyle);
|
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
|
|
|
OnSearchTextChanged(searchText);
|
|
|
|
|
EditorGUI.FocusTextInControl("SearchFilter");
|
|
|
|
|
|
|
|
|
|
area.x = area.xMax;
|
|
|
|
|
area.width = InternalGUI.SearchBarEndStyle.fixedWidth;
|
|
|
|
|
if (HasSearchText)
|
|
|
|
|
{
|
|
|
|
|
if (GUI.Button(area, "", InternalGUI.SearchBarCancelStyle))
|
|
|
|
|
{
|
|
|
|
|
_SearchText = "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else GUI.Box(area, "", InternalGUI.SearchBarEndStyle);
|
|
|
|
|
}
|
|
|
|
|
GUI.EndGroup();
|
|
|
|
|
|
|
|
|
|
area.x = 0;
|
|
|
|
|
area.width = position.width;
|
|
|
|
|
area.y += area.height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void OnSearchTextChanged(string text)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(text))
|
|
|
|
|
{
|
|
|
|
|
SearchedLabels.Clear();
|
|
|
|
|
SearchedObjects.Clear();
|
|
|
|
|
}
|
|
|
|
|
// If the search text starts the same as before, it will include only a subset of the previous options.
|
|
|
|
|
// So we can just remove objects from the previous search list instead of checking the full list again.
|
|
|
|
|
else if (SearchedLabels.Count > 0 && text.StartsWith(_SearchText))
|
|
|
|
|
{
|
|
|
|
|
for (int i = SearchedLabels.Count - 1; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
if (!IsVisibleInSearch(text, SearchedLabels[i].text))
|
|
|
|
|
{
|
|
|
|
|
SearchedLabels.RemoveAt(i);
|
|
|
|
|
SearchedObjects.RemoveAt(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Otherwise clear the search list and re-gather any visible objects from the full list.
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SearchedLabels.Clear();
|
|
|
|
|
SearchedObjects.Clear();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < Labels.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
var label = Labels[i];
|
|
|
|
|
if (IsVisibleInSearch(text, label.text))
|
|
|
|
|
{
|
|
|
|
|
SearchedLabels.Add(label);
|
|
|
|
|
SearchedObjects.Add(_Objects[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_SearchText = text;
|
|
|
|
|
|
|
|
|
|
if (!SearchedObjects.Contains(_SelectedObject))
|
|
|
|
|
_SelectedObject = SearchedObjects.Count > 0 ? SearchedObjects[0] : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool IsVisibleInSearch(string search, string text)
|
|
|
|
|
{
|
|
|
|
|
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(text, search, CompareOptions.IgnoreCase) >= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private Rect CalculateViewRect(float height)
|
|
|
|
|
{
|
|
|
|
|
var area = new Rect();
|
|
|
|
|
|
|
|
|
|
if (HasSearchText)
|
|
|
|
|
{
|
|
|
|
|
area.height = InternalGUI.LabelHeight * SearchedLabels.Count;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
area.height = InternalGUI.LabelHeight * Labels.Count;
|
|
|
|
|
|
|
|
|
|
if (_Suggestions > 0)
|
|
|
|
|
area.height += InternalGUI.HeaddingStyle.fixedHeight * 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_MaxLabelWidth < position.width)
|
|
|
|
|
{
|
|
|
|
|
area.width = position.width;
|
|
|
|
|
|
|
|
|
|
if (area.height > height)
|
|
|
|
|
area.width -= 16;
|
|
|
|
|
}
|
|
|
|
|
else area.width = _MaxLabelWidth;
|
|
|
|
|
|
|
|
|
|
return area;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void DetermineVisibleRange(out int firstVisibleField, out int lastVisibleField)
|
|
|
|
|
{
|
|
|
|
|
var top = _ScrollPosition.y;
|
|
|
|
|
var bottom = top + position.height - InternalGUI.SearchBarHeight;
|
|
|
|
|
if (_Suggestions > 0)
|
|
|
|
|
{
|
|
|
|
|
top -= InternalGUI.HeaddingStyle.fixedHeight * 2;
|
|
|
|
|
bottom += InternalGUI.HeaddingStyle.fixedHeight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
firstVisibleField = Mathf.Max(0, (int)(top / InternalGUI.LabelHeight));
|
|
|
|
|
lastVisibleField = Mathf.Min(Labels.Count, Mathf.CeilToInt(bottom / InternalGUI.LabelHeight));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void DrawAllOptions(Rect area, int firstVisibleField, int lastVisibleField)
|
|
|
|
|
{
|
|
|
|
|
if (_Suggestions == 0 || _Suggestions >= Labels.Count)
|
|
|
|
|
{
|
|
|
|
|
area.y = firstVisibleField * InternalGUI.LabelHeight;
|
|
|
|
|
DrawRange(ref area, Labels, _Objects, firstVisibleField, lastVisibleField);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
area.height = InternalGUI.HeaddingStyle.fixedHeight;
|
|
|
|
|
GUI.Label(area, "Suggestions", InternalGUI.HeaddingStyle);
|
|
|
|
|
|
|
|
|
|
area.y = area.yMax + firstVisibleField * InternalGUI.LabelHeight;
|
|
|
|
|
DrawRange(ref area, Labels, _Objects, firstVisibleField, Mathf.Min(lastVisibleField, _Suggestions));
|
|
|
|
|
|
|
|
|
|
area.height = InternalGUI.HeaddingStyle.fixedHeight;
|
|
|
|
|
GUI.Label(area, "Other Options", InternalGUI.HeaddingStyle);
|
|
|
|
|
area.y = area.yMax;
|
|
|
|
|
|
|
|
|
|
DrawRange(ref area, Labels, _Objects, Mathf.Max(_Suggestions, firstVisibleField), lastVisibleField);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void DrawSearchedOptions(Rect area, int firstVisibleField, int lastVisibleField)
|
|
|
|
|
{
|
|
|
|
|
area.y = firstVisibleField * InternalGUI.LabelHeight;
|
|
|
|
|
DrawRange(ref area, SearchedLabels, SearchedObjects, firstVisibleField, lastVisibleField);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void DrawRange(ref Rect area, List<GUIContent> labels, IList objects, int start, int end)
|
|
|
|
|
{
|
|
|
|
|
area.height = InternalGUI.LabelHeight;
|
|
|
|
|
|
|
|
|
|
if (end > labels.Count)
|
|
|
|
|
end = labels.Count;
|
|
|
|
|
|
|
|
|
|
for (; start < end; start++)
|
|
|
|
|
{
|
|
|
|
|
DrawOption(area, labels, objects, start);
|
|
|
|
|
area.y = area.yMax;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void DrawOption(Rect area, List<GUIContent> labels, IList objects, int index)
|
|
|
|
|
{
|
|
|
|
|
var obj = objects[index];
|
|
|
|
|
var wasOn = obj == _SelectedObject;
|
|
|
|
|
var isOn = GUI.Toggle(area, wasOn, labels[index], wasOn ? InternalGUI.SelectedButtonStyle : InternalGUI.ButtonStyle);
|
|
|
|
|
if (isOn != wasOn)
|
|
|
|
|
{
|
|
|
|
|
if (wasOn)
|
|
|
|
|
{
|
|
|
|
|
PickAndClose();
|
|
|
|
|
}
|
|
|
|
|
else if (isOn)
|
|
|
|
|
{
|
|
|
|
|
_SelectedObject = obj;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void Update()
|
|
|
|
|
{
|
|
|
|
|
if (focusedWindow != this)
|
|
|
|
|
Close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private void OffsetSelectedIndex(int offset)
|
|
|
|
|
{
|
|
|
|
|
var objects = HasSearchText ? SearchedObjects : _Objects;
|
|
|
|
|
|
|
|
|
|
if (objects.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var index = objects.IndexOf(_SelectedObject);
|
|
|
|
|
if (index >= 0)
|
|
|
|
|
_SelectedObject = objects[Mathf.Clamp(index + offset, 0, objects.Count)];
|
|
|
|
|
else
|
|
|
|
|
_SelectedObject = objects[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
|
|
|
|
|
private static class InternalGUI
|
|
|
|
|
{
|
|
|
|
|
public static readonly GUIStyle
|
|
|
|
|
SearchBarStyle,
|
|
|
|
|
SearchBarEndStyle,
|
|
|
|
|
SearchBarCancelStyle,
|
|
|
|
|
HeaddingStyle,
|
|
|
|
|
ButtonStyle,
|
|
|
|
|
SelectedButtonStyle;
|
|
|
|
|
|
|
|
|
|
public static float SearchBarHeight
|
|
|
|
|
{
|
|
|
|
|
get { return EditorStyles.toolbar.fixedHeight; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static float LabelHeight
|
|
|
|
|
{
|
|
|
|
|
get { return ButtonStyle.fixedHeight; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static InternalGUI()
|
|
|
|
|
{
|
|
|
|
|
SearchBarStyle = GUI.skin.FindStyle("ToolbarSeachTextField");
|
|
|
|
|
SearchBarEndStyle = GUI.skin.FindStyle("ToolbarSeachCancelButtonEmpty");
|
|
|
|
|
SearchBarCancelStyle = GUI.skin.FindStyle("ToolbarSeachCancelButton");
|
|
|
|
|
|
|
|
|
|
HeaddingStyle = new GUIStyle(EditorStyles.boldLabel)
|
|
|
|
|
{
|
|
|
|
|
fontSize = 12,
|
|
|
|
|
alignment = TextAnchor.MiddleLeft,
|
|
|
|
|
fixedHeight = 22
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ButtonStyle = new GUIStyle(EditorStyles.toolbarButton)
|
|
|
|
|
{
|
|
|
|
|
alignment = TextAnchor.MiddleLeft,
|
|
|
|
|
fontSize = 12
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SelectedButtonStyle = new GUIStyle(ButtonStyle)
|
|
|
|
|
{
|
|
|
|
|
fontStyle = FontStyle.Bold
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|