using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Chronos.Controls.Editor
{
///
/// Utility class to display complex editor popups.
///
public static class DropdownGUI
{
public delegate void SingleCallback(T value);
public delegate void MultipleCallback(IEnumerable value);
///
/// Render an editor popup and return the newly selected option.
///
/// The position of the control.
/// The function called when a value is selected.
/// The list of available options.
/// The selected option, or null for none.
/// The option for "no selection", or null for none.
/// Whether the content has multiple different values.
/// Whether a selected option not in range should be allowed.
public static void PopupSingle
(
Rect position,
SingleCallback callback,
IEnumerable> options,
DropdownOption selectedOption,
DropdownOption noneOption,
bool hasMultipleDifferentValues,
bool allowOuterOption = true
)
{
string label;
if (hasMultipleDifferentValues)
{
label = "\u2014"; // Em Dash
}
else if (selectedOption == null)
{
if (noneOption != null)
{
label = noneOption.label;
}
else
{
label = string.Empty;
}
}
else
{
label = selectedOption.label;
}
if (GUI.Button(position, label, EditorStyles.popup))
{
DropdownSingle
(
new Vector2(position.xMin, position.yMax),
callback,
options,
selectedOption,
noneOption,
hasMultipleDifferentValues
);
}
else if (selectedOption != null && !options.Select(o => o.value).Contains(selectedOption.value) && !allowOuterOption)
{
// Selected option isn't in range
if (hasMultipleDifferentValues)
{
// Do nothing
}
else if (noneOption != null)
{
callback(noneOption.value);
}
else
{
callback(default(T));
}
}
}
public static void DropdownSingle
(
Vector2 position,
SingleCallback callback,
IEnumerable> options,
DropdownOption selectedOption,
DropdownOption noneOption,
bool hasMultipleDifferentValues
)
{
bool hasOptions = options != null && options.Any();
GenericMenu menu = new GenericMenu();
GenericMenu.MenuFunction2 menuCallback = (o) => { GUI.changed = true; callback((T)o); };
if (noneOption != null)
{
bool on = !hasMultipleDifferentValues && (selectedOption == null || EqualityComparer.Default.Equals(selectedOption.value, noneOption.value));
menu.AddItem(new GUIContent(noneOption.label), on, menuCallback, noneOption.value);
}
if (noneOption != null && hasOptions)
{
menu.AddSeparator("");
}
if (hasOptions)
{
foreach (var option in options)
{
bool on = !hasMultipleDifferentValues && (selectedOption != null && EqualityComparer.Default.Equals(selectedOption.value, option.value));
menu.AddItem(new GUIContent(option.label), on, menuCallback, option.value);
}
}
menu.DropDown(new Rect(position, Vector2.zero));
}
private static IEnumerable SanitizeMultipleOptions(IEnumerable> options, IEnumerable selectedOptions)
{
// Remove outer options
return selectedOptions.Where(so => options.Any(o => EqualityComparer.Default.Equals(o.value, so)));
}
public static void PopupMultiple
(
Rect position,
MultipleCallback callback,
IEnumerable> options,
IEnumerable selectedOptions,
bool hasMultipleDifferentValues
)
{
string label;
selectedOptions = SanitizeMultipleOptions(options, selectedOptions);
if (hasMultipleDifferentValues)
{
label = "\u2014"; // Em Dash
}
else
{
var selectedOptionsCount = selectedOptions.Count();
var optionsCount = options.Count();
if (selectedOptionsCount == 0)
{
label = "Nothing";
}
else if (selectedOptionsCount == 1)
{
label = options.First(o => EqualityComparer.Default.Equals(o.value, selectedOptions.First())).label;
}
else if (selectedOptionsCount == optionsCount)
{
label = "Everything";
}
else
{
label = "(Mixed)";
}
}
if (GUI.Button(position, label, EditorStyles.popup))
{
DropdownMultiple
(
new Vector2(position.xMin, position.yMax),
callback,
options,
selectedOptions,
hasMultipleDifferentValues
);
}
}
public static void DropdownMultiple
(
Vector2 position,
MultipleCallback callback,
IEnumerable> options,
IEnumerable selectedOptions,
bool hasMultipleDifferentValues
)
{
selectedOptions = SanitizeMultipleOptions(options, selectedOptions);
bool hasOptions = options != null && options.Any();
GenericMenu menu = new GenericMenu();
GenericMenu.MenuFunction2 switchCallback = (o) =>
{
GUI.changed = true;
var switchOption = (T)o;
var newSelectedOptions = selectedOptions.ToList();
if (newSelectedOptions.Contains(switchOption))
{
newSelectedOptions.Remove(switchOption);
}
else
{
newSelectedOptions.Add(switchOption);
}
callback(newSelectedOptions);
};
GenericMenu.MenuFunction nothingCallback = () =>
{
GUI.changed = true;
callback(Enumerable.Empty());
};
GenericMenu.MenuFunction everythingCallback = () =>
{
GUI.changed = true;
callback(options.Select((o) => o.value));
};
menu.AddItem(new GUIContent("Nothing"), !hasMultipleDifferentValues && !selectedOptions.Any(), nothingCallback);
menu.AddItem(new GUIContent("Everything"), !hasMultipleDifferentValues && selectedOptions.Count() == options.Count() && Enumerable.SequenceEqual(selectedOptions.OrderBy(t => t), options.Select(o => o.value).OrderBy(t => t)), everythingCallback);
if (hasOptions)
{
menu.AddSeparator(""); // Not in Unity default, but pretty
foreach (var option in options)
{
bool on = !hasMultipleDifferentValues && (selectedOptions.Any(selectedOption => EqualityComparer.Default.Equals(selectedOption, option.value)));
menu.AddItem(new GUIContent(option.label), on, switchCallback, option.value);
}
}
menu.DropDown(new Rect(position, Vector2.zero));
}
}
}