MutliSelectFieldGUI.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using UnityEngine;
  5. using UnityEditor;
  6. namespace Pack
  7. {
  8. public static class MutliSelectFieldGUI
  9. {
  10. private class MutliSelectCallbackInfo
  11. {
  12. // The global shared popup state
  13. public static MutliSelectCallbackInfo m_Instance;
  14. // Name of the command event sent from the popup menu to OnGUI when user has changed selection
  15. private const string kMutliMenuChangedMessage = "CustomMutliMenuChanged";
  16. // The control ID of the popup menu that is currently displayed.
  17. // Used to pass selection changes back again
  18. private readonly int m_ControlID;
  19. // New mask value
  20. private int m_NewMask;
  21. // Which view should we send it to.
  22. private readonly object m_SourceView;
  23. private MethodInfo m_SendEventMethodInfo;
  24. public MutliSelectCallbackInfo(int controlID)
  25. {
  26. m_ControlID = controlID;
  27. var types = typeof(Editor).Assembly.GetTypes();
  28. foreach (var type in types)
  29. {
  30. if (type.Name == "GUIView")
  31. {
  32. BindingFlags bindingFlags = (BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
  33. m_SourceView = type.GetProperty("current", bindingFlags).GetValue(null);
  34. bindingFlags = (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  35. m_SendEventMethodInfo = type.GetMethod("SendEvent", bindingFlags, null, new Type[] { typeof(Event) }, null);
  36. break;
  37. }
  38. }
  39. }
  40. public static T[] GetSelectedValueForControl<T>(int controlID, T[] canSelectValues, T[] selectedValues, int maxSelectNum, bool isSort)
  41. {
  42. var evt = Event.current;
  43. if (evt.type == EventType.ExecuteCommand && evt.commandName == kMutliMenuChangedMessage)
  44. {
  45. if (m_Instance == null)
  46. {
  47. Debug.LogError("Mask menu has no instance");
  48. return selectedValues;
  49. }
  50. if (m_Instance.m_ControlID == controlID)
  51. {
  52. if (m_Instance.m_NewMask == 0)
  53. {
  54. if (selectedValues.Length > 0)
  55. {
  56. ArrayUtility.Clear(ref selectedValues);
  57. GUI.changed = true;
  58. }
  59. }
  60. else if (m_Instance.m_NewMask == 1)
  61. {
  62. if (!ArrayUtility.ArrayEquals(canSelectValues, selectedValues))
  63. {
  64. ArrayUtility.Clear(ref selectedValues);
  65. if (maxSelectNum >= 0 && maxSelectNum < canSelectValues.Length)
  66. {
  67. for (int i = 0; i < maxSelectNum; i++)
  68. {
  69. ArrayUtility.Add(ref selectedValues, canSelectValues[i]);
  70. }
  71. }
  72. else
  73. {
  74. ArrayUtility.AddRange(ref selectedValues, canSelectValues);
  75. }
  76. GUI.changed = true;
  77. }
  78. }
  79. else
  80. {
  81. T value = canSelectValues[m_Instance.m_NewMask - 2];
  82. if (ArrayUtility.Contains(selectedValues, value))
  83. {
  84. ArrayUtility.Remove(ref selectedValues, value);
  85. GUI.changed = true;
  86. }
  87. else
  88. {
  89. if (maxSelectNum < 0 || selectedValues.Length < maxSelectNum)
  90. {
  91. int index = m_Instance.m_NewMask - 2;
  92. if (isSort)
  93. {
  94. for (int i = 0, iMax = selectedValues.Length; i < iMax; i++)
  95. {
  96. if (index < ArrayUtility.FindIndex(canSelectValues, (x) => x.Equals(selectedValues[i])))
  97. {
  98. ArrayUtility.Insert(ref selectedValues, i, value);
  99. break;
  100. }
  101. }
  102. }
  103. if (!ArrayUtility.Contains(selectedValues, value))
  104. {
  105. ArrayUtility.Add(ref selectedValues, value);
  106. }
  107. }
  108. GUI.changed = true;
  109. }
  110. }
  111. m_Instance = null;
  112. evt.Use();
  113. }
  114. }
  115. return selectedValues;
  116. }
  117. public void SetMaskValueDelegate(object userData, string[] options, int selected)
  118. {
  119. m_NewMask = selected;
  120. if (m_SourceView != null)
  121. m_SendEventMethodInfo.Invoke(m_SourceView, new object[] { EditorGUIUtility.CommandEvent(kMutliMenuChangedMessage) });
  122. }
  123. }
  124. public static T[] DoMutliSelectField<T>(Rect position, int controlID, T[] canSelectValues, T[] selectedValues, T[] noEnableValues, int maxSelectNum, bool isSort, GUIStyle style)
  125. {
  126. selectedValues = MutliSelectCallbackInfo.GetSelectedValueForControl(controlID, canSelectValues, selectedValues, maxSelectNum, isSort);
  127. var selectedFlags = new List<int>();
  128. var fullFlagNames = new List<string> { "Nothing", "Everything" };
  129. List<bool> enables = new List<bool>();
  130. bool hasNoEnable = false;
  131. for (var i = 0; i < canSelectValues.Length; i++)
  132. {
  133. if (noEnableValues != null && ArrayUtility.Contains(noEnableValues, canSelectValues[i]))
  134. {
  135. enables.Add(false);
  136. hasNoEnable = true;
  137. selectedFlags.Add(i + 2);
  138. }
  139. else
  140. {
  141. enables.Add(true);
  142. if ((ArrayUtility.Contains(selectedValues, canSelectValues[i])))
  143. {
  144. selectedFlags.Add(i + 2);
  145. }
  146. }
  147. fullFlagNames.Add(canSelectValues[i].ToString());
  148. }
  149. enables.Insert(0, (maxSelectNum >= 0 ? false : true));
  150. enables.Insert(0, !hasNoEnable);
  151. GUIContent buttonContent = s_MixedValueContent;
  152. if (!EditorGUI.showMixedValue)
  153. {
  154. switch (selectedFlags.Count)
  155. {
  156. case 0:
  157. buttonContent = EditorGUIUtility.TrTempContent("Nothing");
  158. selectedFlags.Add(0);
  159. break;
  160. case 1:
  161. buttonContent = new GUIContent(fullFlagNames[selectedFlags[0]]);
  162. break;
  163. default:
  164. if (selectedFlags.Count >= canSelectValues.Length)
  165. {
  166. buttonContent = EditorGUIUtility.TrTempContent("Everything");
  167. selectedFlags.Add(1);
  168. }
  169. else
  170. buttonContent = EditorGUIUtility.TrTempContent("Mixed ...");
  171. break;
  172. }
  173. }
  174. Event evt = Event.current;
  175. if (evt.type == EventType.Repaint)
  176. {
  177. style.Draw(position, buttonContent, controlID, false);
  178. }
  179. else if ((evt.type == EventType.MouseDown && position.Contains(evt.mousePosition)) || MainActionKeyForControl(evt, controlID))
  180. {
  181. MutliSelectCallbackInfo.m_Instance = new MutliSelectCallbackInfo(controlID);
  182. evt.Use();
  183. displayCustomMenuMethodInfo.Invoke(null, new object[]{position, fullFlagNames.ToArray(),
  184. // Only show selections if we are not multi-editing
  185. enables.ToArray(),
  186. EditorGUI.showMixedValue ? new int[] { } : selectedFlags.ToArray(),
  187. // optionMaskValues is from the pool so use a clone of the values for the current control
  188. (EditorUtility.SelectMenuItemFunction)MutliSelectCallbackInfo.m_Instance.SetMaskValueDelegate, selectedValues
  189. });
  190. EditorGUIUtility.keyboardControl = controlID;
  191. }
  192. return selectedValues;
  193. }
  194. public static T[] DoMutliSelectFieldShowValue<T>(Rect position, int controlID, T[] canSelectValues, T[] selectedValues, T[] noEnableValues, int maxSelectNum, bool isSort, GUIStyle style)
  195. {
  196. if (canSelectValues == null)
  197. {
  198. canSelectValues = new T[0];
  199. }
  200. if (selectedValues == null)
  201. {
  202. selectedValues = new T[0];
  203. }
  204. selectedValues = MutliSelectCallbackInfo.GetSelectedValueForControl(controlID, canSelectValues, selectedValues, maxSelectNum, isSort);
  205. var selectedFlags = new List<int>();
  206. var fullFlagNames = new List<string> { "Nothing", "Everything" };
  207. List<bool> enables = new List<bool>();
  208. bool hasNoEnable = false;
  209. for (var i = 0; i < canSelectValues.Length; i++)
  210. {
  211. if (noEnableValues != null && ArrayUtility.Contains(noEnableValues, canSelectValues[i]))
  212. {
  213. enables.Add(false);
  214. hasNoEnable = true;
  215. selectedFlags.Add(i + 2);
  216. }
  217. else
  218. {
  219. enables.Add(true);
  220. if ((ArrayUtility.Contains(selectedValues, canSelectValues[i])))
  221. {
  222. selectedFlags.Add(i + 2);
  223. }
  224. }
  225. fullFlagNames.Add(canSelectValues[i].ToString());
  226. }
  227. enables.Insert(0, (maxSelectNum >= 0 ? false : true));
  228. enables.Insert(0, !hasNoEnable);
  229. GUIContent buttonContent = s_MixedValueContent;
  230. if (!EditorGUI.showMixedValue)
  231. {
  232. switch (selectedFlags.Count)
  233. {
  234. case 0:
  235. buttonContent = EditorGUIUtility.TrTempContent("(Null)");
  236. selectedFlags.Add(0);
  237. break;
  238. case 1:
  239. buttonContent = new GUIContent(fullFlagNames[selectedFlags[0]]);
  240. break;
  241. default:
  242. if (selectedFlags.Count >= canSelectValues.Length)
  243. {
  244. buttonContent = EditorGUIUtility.TrTempContent(string.Join(";", selectedValues));
  245. selectedFlags.Add(1);
  246. }
  247. else
  248. buttonContent = EditorGUIUtility.TrTempContent(string.Join(";", selectedValues));
  249. break;
  250. }
  251. }
  252. Event evt = Event.current;
  253. if (evt.type == EventType.Repaint)
  254. {
  255. style.Draw(position, buttonContent, controlID, false);
  256. }
  257. else if ((evt.type == EventType.MouseDown && position.Contains(evt.mousePosition)) || MainActionKeyForControl(evt, controlID))
  258. {
  259. MutliSelectCallbackInfo.m_Instance = new MutliSelectCallbackInfo(controlID);
  260. evt.Use();
  261. displayCustomMenuMethodInfo.Invoke(null, new object[]{position, fullFlagNames.ToArray(),
  262. // Only show selections if we are not multi-editing
  263. enables.ToArray(),
  264. EditorGUI.showMixedValue ? new int[] { } : selectedFlags.ToArray(),
  265. // optionMaskValues is from the pool so use a clone of the values for the current control
  266. (EditorUtility.SelectMenuItemFunction)MutliSelectCallbackInfo.m_Instance.SetMaskValueDelegate, selectedValues
  267. });
  268. EditorGUIUtility.keyboardControl = controlID;
  269. }
  270. return selectedValues;
  271. }
  272. internal static bool MainActionKeyForControl(UnityEngine.Event evt, int controlId)
  273. {
  274. if (EditorGUIUtility.keyboardControl != controlId)
  275. return false;
  276. bool anyModifiers = (evt.alt || evt.shift || evt.command || evt.control);
  277. // Block window maximize (on OSX ML, we need to show the menu as part of the KeyCode event, so we can't do the usual check)
  278. if (evt.type == EventType.KeyDown && evt.character == ' ' && !anyModifiers)
  279. {
  280. evt.Use();
  281. return false;
  282. }
  283. // Space or return is action key
  284. return evt.type == EventType.KeyDown &&
  285. (evt.keyCode == KeyCode.Space || evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter) &&
  286. !anyModifiers;
  287. }
  288. private static MethodInfo s_DisplayCustomMenuMethodInfo = null;
  289. private static MethodInfo displayCustomMenuMethodInfo
  290. {
  291. get
  292. {
  293. if (s_DisplayCustomMenuMethodInfo == null)
  294. {
  295. BindingFlags bindingFlags = (BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
  296. s_DisplayCustomMenuMethodInfo = typeof(EditorUtility).GetMethod("DisplayCustomMenu", bindingFlags, null, new Type[] { typeof(Rect), typeof(string[]), typeof(bool[]), typeof(int[]), typeof(EditorUtility.SelectMenuItemFunction), typeof(object) }, null);
  297. }
  298. return s_DisplayCustomMenuMethodInfo;
  299. }
  300. }
  301. private static readonly GUIContent s_MixedValueContent = EditorGUIUtility.TrTextContent("\u2014", "Mixed Values");
  302. }
  303. }