EditorGUIx.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using UnityEditor;
  4. namespace Pathfinding {
  5. /// <summary>Simple GUI utility functions</summary>
  6. public static class GUIUtilityx {
  7. static Stack<Color> colors = new Stack<Color>();
  8. public static void PushTint (Color tint) {
  9. colors.Push(GUI.color);
  10. GUI.color *= tint;
  11. }
  12. public static void PopTint () {
  13. GUI.color = colors.Pop();
  14. }
  15. }
  16. /// <summary>
  17. /// Editor helper for hiding and showing a group of GUI elements.
  18. /// Call order in OnInspectorGUI should be:
  19. /// - Begin
  20. /// - Header/HeaderLabel (optional)
  21. /// - BeginFade
  22. /// - [your gui elements] (if BeginFade returns true)
  23. /// - End
  24. /// </summary>
  25. public class FadeArea {
  26. Rect lastRect;
  27. float value;
  28. float lastUpdate;
  29. GUIStyle labelStyle;
  30. GUIStyle areaStyle;
  31. bool visible;
  32. Editor editor;
  33. /// <summary>
  34. /// Is this area open.
  35. /// This is not the same as if any contents are visible, use <see cref="BeginFade"/> for that.
  36. /// </summary>
  37. public bool open;
  38. /// <summary>Animate dropdowns when they open and close</summary>
  39. public static bool fancyEffects;
  40. const float animationSpeed = 100f;
  41. public FadeArea (bool open, Editor editor, GUIStyle areaStyle, GUIStyle labelStyle = null) {
  42. this.areaStyle = areaStyle;
  43. this.labelStyle = labelStyle;
  44. this.editor = editor;
  45. visible = this.open = open;
  46. value = open ? 1 : 0;
  47. }
  48. void Tick () {
  49. if (Event.current.type == EventType.Repaint) {
  50. float deltaTime = Time.realtimeSinceStartup-lastUpdate;
  51. // Right at the start of a transition the deltaTime will
  52. // not be reliable, so use a very small value instead
  53. // until the next repaint
  54. if (value == 0f || value == 1f) deltaTime = 0.001f;
  55. deltaTime = Mathf.Clamp(deltaTime, 0.00001F, 0.1F);
  56. // Larger regions fade slightly slower
  57. deltaTime /= Mathf.Sqrt(Mathf.Max(lastRect.height, 100));
  58. lastUpdate = Time.realtimeSinceStartup;
  59. float targetValue = open ? 1F : 0F;
  60. if (!Mathf.Approximately(targetValue, value)) {
  61. value += deltaTime*animationSpeed*Mathf.Sign(targetValue-value);
  62. value = Mathf.Clamp01(value);
  63. editor.Repaint();
  64. if (!fancyEffects) {
  65. value = targetValue;
  66. }
  67. } else {
  68. value = targetValue;
  69. }
  70. }
  71. }
  72. public void Begin () {
  73. if (areaStyle != null) {
  74. lastRect = EditorGUILayout.BeginVertical(areaStyle);
  75. } else {
  76. lastRect = EditorGUILayout.BeginVertical();
  77. }
  78. }
  79. public void HeaderLabel (string label) {
  80. GUILayout.Label(label, labelStyle);
  81. }
  82. public void Header (string label) {
  83. Header(label, ref open);
  84. }
  85. public void Header (string label, ref bool open) {
  86. if (GUILayout.Button(label, labelStyle)) {
  87. open = !open;
  88. editor.Repaint();
  89. }
  90. this.open = open;
  91. }
  92. /// <summary>Hermite spline interpolation</summary>
  93. static float Hermite (float start, float end, float value) {
  94. return Mathf.Lerp(start, end, value * value * (3.0f - 2.0f * value));
  95. }
  96. public bool BeginFade () {
  97. var hermite = Hermite(0, 1, value);
  98. visible = EditorGUILayout.BeginFadeGroup(hermite);
  99. GUIUtilityx.PushTint(new Color(1, 1, 1, hermite));
  100. Tick();
  101. // Another vertical group is necessary to work around
  102. // a kink of the BeginFadeGroup implementation which
  103. // causes the padding to change when value!=0 && value!=1
  104. EditorGUILayout.BeginVertical();
  105. return visible;
  106. }
  107. public void End () {
  108. EditorGUILayout.EndVertical();
  109. if (visible) {
  110. // Some space that cannot be placed in the GUIStyle unfortunately
  111. GUILayout.Space(4);
  112. }
  113. EditorGUILayout.EndFadeGroup();
  114. EditorGUILayout.EndVertical();
  115. GUIUtilityx.PopTint();
  116. }
  117. }
  118. /// <summary>Handles fading effects and also some custom GUI functions such as LayerMaskField</summary>
  119. public static class EditorGUILayoutx {
  120. static Dictionary<int, string[]> layerNames = new Dictionary<int, string[]>();
  121. static long lastUpdateTick;
  122. /// <summary>
  123. /// Tag names and an additional 'Edit Tags...' entry.
  124. /// Used for SingleTagField
  125. /// </summary>
  126. static string[] tagNamesAndEditTagsButton;
  127. /// <summary>
  128. /// Last time tagNamesAndEditTagsButton was updated.
  129. /// Uses EditorApplication.timeSinceStartup
  130. /// </summary>
  131. static double timeLastUpdatedTagNames;
  132. public static int TagField (string label, int value, System.Action editCallback) {
  133. // Make sure the tagNamesAndEditTagsButton is relatively up to date
  134. if (tagNamesAndEditTagsButton == null || EditorApplication.timeSinceStartup - timeLastUpdatedTagNames > 1) {
  135. timeLastUpdatedTagNames = EditorApplication.timeSinceStartup;
  136. var tagNames = AstarPath.FindTagNames();
  137. tagNamesAndEditTagsButton = new string[tagNames.Length+1];
  138. tagNames.CopyTo(tagNamesAndEditTagsButton, 0);
  139. tagNamesAndEditTagsButton[tagNamesAndEditTagsButton.Length-1] = "Edit Tags...";
  140. }
  141. // Tags are between 0 and 31
  142. value = Mathf.Clamp(value, 0, 31);
  143. var newValue = EditorGUILayout.IntPopup(label, value, tagNamesAndEditTagsButton, new [] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1 });
  144. // Last element corresponds to the 'Edit Tags...' entry. Open the tag editor
  145. if (newValue == -1) {
  146. editCallback();
  147. } else {
  148. value = newValue;
  149. }
  150. return value;
  151. }
  152. public static bool UnityTagMaskList (GUIContent label, bool foldout, List<string> tagMask) {
  153. if (tagMask == null) throw new System.ArgumentNullException("tagMask");
  154. if (EditorGUILayout.Foldout(foldout, label)) {
  155. EditorGUI.indentLevel++;
  156. GUILayout.BeginVertical();
  157. for (int i = 0; i < tagMask.Count; i++) {
  158. tagMask[i] = EditorGUILayout.TagField(tagMask[i]);
  159. }
  160. GUILayout.BeginHorizontal();
  161. if (GUILayout.Button("Add Tag")) tagMask.Add("Untagged");
  162. EditorGUI.BeginDisabledGroup(tagMask.Count == 0);
  163. if (GUILayout.Button("Remove Last")) tagMask.RemoveAt(tagMask.Count-1);
  164. EditorGUI.EndDisabledGroup();
  165. GUILayout.EndHorizontal();
  166. GUILayout.EndVertical();
  167. EditorGUI.indentLevel--;
  168. return true;
  169. }
  170. return false;
  171. }
  172. /// <summary>Displays a LayerMask field.</summary>
  173. /// <param name="label">Label to display</param>
  174. /// <param name="selected">Current LayerMask</param>
  175. public static LayerMask LayerMaskField (string label, LayerMask selected) {
  176. if (Event.current.type == EventType.Layout && System.DateTime.UtcNow.Ticks - lastUpdateTick > 10000000L) {
  177. layerNames.Clear();
  178. lastUpdateTick = System.DateTime.UtcNow.Ticks;
  179. }
  180. string[] currentLayerNames;
  181. if (!layerNames.TryGetValue(selected.value, out currentLayerNames)) {
  182. var layers = Pathfinding.Util.ListPool<string>.Claim();
  183. int emptyLayers = 0;
  184. for (int i = 0; i < 32; i++) {
  185. string layerName = LayerMask.LayerToName(i);
  186. if (layerName != "") {
  187. for (; emptyLayers > 0; emptyLayers--) layers.Add("Layer "+(i-emptyLayers));
  188. layers.Add(layerName);
  189. } else {
  190. emptyLayers++;
  191. if (((selected.value >> i) & 1) != 0 && selected.value != -1) {
  192. for (; emptyLayers > 0; emptyLayers--) layers.Add("Layer "+(i+1-emptyLayers));
  193. }
  194. }
  195. }
  196. currentLayerNames = layerNames[selected.value] = layers.ToArray();
  197. Pathfinding.Util.ListPool<string>.Release(ref layers);
  198. }
  199. selected.value = EditorGUILayout.MaskField(label, selected.value, currentLayerNames);
  200. return selected;
  201. }
  202. }
  203. }