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)); } } }