DropdownGUI.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEditor;
  4. using UnityEngine;
  5. namespace Chronos.Controls.Editor
  6. {
  7. /// <summary>
  8. /// Utility class to display complex editor popups.
  9. /// </summary>
  10. public static class DropdownGUI<T>
  11. {
  12. public delegate void SingleCallback(T value);
  13. public delegate void MultipleCallback(IEnumerable<T> value);
  14. /// <summary>
  15. /// Render an editor popup and return the newly selected option.
  16. /// </summary>
  17. /// <param name="position">The position of the control.</param>
  18. /// <param name="callback">The function called when a value is selected.</param>
  19. /// <param name="options">The list of available options.</param>
  20. /// <param name="selectedOption">The selected option, or null for none.</param>
  21. /// <param name="noneOption">The option for "no selection", or null for none.</param>
  22. /// <param name="hasMultipleDifferentValues">Whether the content has multiple different values.</param>
  23. /// <param name="allowOuterOption">Whether a selected option not in range should be allowed.</param>
  24. public static void PopupSingle
  25. (
  26. Rect position,
  27. SingleCallback callback,
  28. IEnumerable<DropdownOption<T>> options,
  29. DropdownOption<T> selectedOption,
  30. DropdownOption<T> noneOption,
  31. bool hasMultipleDifferentValues,
  32. bool allowOuterOption = true
  33. )
  34. {
  35. string label;
  36. if (hasMultipleDifferentValues)
  37. {
  38. label = "\u2014"; // Em Dash
  39. }
  40. else if (selectedOption == null)
  41. {
  42. if (noneOption != null)
  43. {
  44. label = noneOption.label;
  45. }
  46. else
  47. {
  48. label = string.Empty;
  49. }
  50. }
  51. else
  52. {
  53. label = selectedOption.label;
  54. }
  55. if (GUI.Button(position, label, EditorStyles.popup))
  56. {
  57. DropdownSingle
  58. (
  59. new Vector2(position.xMin, position.yMax),
  60. callback,
  61. options,
  62. selectedOption,
  63. noneOption,
  64. hasMultipleDifferentValues
  65. );
  66. }
  67. else if (selectedOption != null && !options.Select(o => o.value).Contains(selectedOption.value) && !allowOuterOption)
  68. {
  69. // Selected option isn't in range
  70. if (hasMultipleDifferentValues)
  71. {
  72. // Do nothing
  73. }
  74. else if (noneOption != null)
  75. {
  76. callback(noneOption.value);
  77. }
  78. else
  79. {
  80. callback(default(T));
  81. }
  82. }
  83. }
  84. public static void DropdownSingle
  85. (
  86. Vector2 position,
  87. SingleCallback callback,
  88. IEnumerable<DropdownOption<T>> options,
  89. DropdownOption<T> selectedOption,
  90. DropdownOption<T> noneOption,
  91. bool hasMultipleDifferentValues
  92. )
  93. {
  94. bool hasOptions = options != null && options.Any();
  95. GenericMenu menu = new GenericMenu();
  96. GenericMenu.MenuFunction2 menuCallback = (o) => { GUI.changed = true; callback((T)o); };
  97. if (noneOption != null)
  98. {
  99. bool on = !hasMultipleDifferentValues && (selectedOption == null || EqualityComparer<T>.Default.Equals(selectedOption.value, noneOption.value));
  100. menu.AddItem(new GUIContent(noneOption.label), on, menuCallback, noneOption.value);
  101. }
  102. if (noneOption != null && hasOptions)
  103. {
  104. menu.AddSeparator("");
  105. }
  106. if (hasOptions)
  107. {
  108. foreach (var option in options)
  109. {
  110. bool on = !hasMultipleDifferentValues && (selectedOption != null && EqualityComparer<T>.Default.Equals(selectedOption.value, option.value));
  111. menu.AddItem(new GUIContent(option.label), on, menuCallback, option.value);
  112. }
  113. }
  114. menu.DropDown(new Rect(position, Vector2.zero));
  115. }
  116. private static IEnumerable<T> SanitizeMultipleOptions(IEnumerable<DropdownOption<T>> options, IEnumerable<T> selectedOptions)
  117. {
  118. // Remove outer options
  119. return selectedOptions.Where(so => options.Any(o => EqualityComparer<T>.Default.Equals(o.value, so)));
  120. }
  121. public static void PopupMultiple
  122. (
  123. Rect position,
  124. MultipleCallback callback,
  125. IEnumerable<DropdownOption<T>> options,
  126. IEnumerable<T> selectedOptions,
  127. bool hasMultipleDifferentValues
  128. )
  129. {
  130. string label;
  131. selectedOptions = SanitizeMultipleOptions(options, selectedOptions);
  132. if (hasMultipleDifferentValues)
  133. {
  134. label = "\u2014"; // Em Dash
  135. }
  136. else
  137. {
  138. var selectedOptionsCount = selectedOptions.Count();
  139. var optionsCount = options.Count();
  140. if (selectedOptionsCount == 0)
  141. {
  142. label = "Nothing";
  143. }
  144. else if (selectedOptionsCount == 1)
  145. {
  146. label = options.First(o => EqualityComparer<T>.Default.Equals(o.value, selectedOptions.First())).label;
  147. }
  148. else if (selectedOptionsCount == optionsCount)
  149. {
  150. label = "Everything";
  151. }
  152. else
  153. {
  154. label = "(Mixed)";
  155. }
  156. }
  157. if (GUI.Button(position, label, EditorStyles.popup))
  158. {
  159. DropdownMultiple
  160. (
  161. new Vector2(position.xMin, position.yMax),
  162. callback,
  163. options,
  164. selectedOptions,
  165. hasMultipleDifferentValues
  166. );
  167. }
  168. }
  169. public static void DropdownMultiple
  170. (
  171. Vector2 position,
  172. MultipleCallback callback,
  173. IEnumerable<DropdownOption<T>> options,
  174. IEnumerable<T> selectedOptions,
  175. bool hasMultipleDifferentValues
  176. )
  177. {
  178. selectedOptions = SanitizeMultipleOptions(options, selectedOptions);
  179. bool hasOptions = options != null && options.Any();
  180. GenericMenu menu = new GenericMenu();
  181. GenericMenu.MenuFunction2 switchCallback = (o) =>
  182. {
  183. GUI.changed = true;
  184. var switchOption = (T)o;
  185. var newSelectedOptions = selectedOptions.ToList();
  186. if (newSelectedOptions.Contains(switchOption))
  187. {
  188. newSelectedOptions.Remove(switchOption);
  189. }
  190. else
  191. {
  192. newSelectedOptions.Add(switchOption);
  193. }
  194. callback(newSelectedOptions);
  195. };
  196. GenericMenu.MenuFunction nothingCallback = () =>
  197. {
  198. GUI.changed = true;
  199. callback(Enumerable.Empty<T>());
  200. };
  201. GenericMenu.MenuFunction everythingCallback = () =>
  202. {
  203. GUI.changed = true;
  204. callback(options.Select((o) => o.value));
  205. };
  206. menu.AddItem(new GUIContent("Nothing"), !hasMultipleDifferentValues && !selectedOptions.Any(), nothingCallback);
  207. 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);
  208. if (hasOptions)
  209. {
  210. menu.AddSeparator(""); // Not in Unity default, but pretty
  211. foreach (var option in options)
  212. {
  213. bool on = !hasMultipleDifferentValues && (selectedOptions.Any(selectedOption => EqualityComparer<T>.Default.Equals(selectedOption, option.value)));
  214. menu.AddItem(new GUIContent(option.label), on, switchCallback, option.value);
  215. }
  216. }
  217. menu.DropDown(new Rect(position, Vector2.zero));
  218. }
  219. }
  220. }