AnimancerNodeDrawer.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using UnityEditor;
  5. using UnityEngine;
  6. using UnityEngine.Playables;
  7. using Object = UnityEngine.Object;
  8. namespace Animancer.Editor
  9. {
  10. /// <summary>[Editor-Only] Draws the Inspector GUI for an <see cref="AnimancerNode"/>.</summary>
  11. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/IAnimancerNodeDrawer
  12. ///
  13. public interface IAnimancerNodeDrawer
  14. {
  15. /// <summary>Draws the details and controls for the target node in the Inspector.</summary>
  16. void DoGUI();
  17. }
  18. /************************************************************************************************************************/
  19. /// <summary>[Editor-Only] Draws the Inspector GUI for an <see cref="AnimancerNode"/>.</summary>
  20. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerNodeDrawer_1
  21. ///
  22. public abstract class AnimancerNodeDrawer<T> : IAnimancerNodeDrawer where T : AnimancerNode
  23. {
  24. /************************************************************************************************************************/
  25. /// <summary>The node being managed.</summary>
  26. public T Target { get; protected set; }
  27. /// <summary>If true, the details of the <see cref="Target"/> will be expanded in the Inspector.</summary>
  28. public ref bool IsExpanded => ref Target._IsInspectorExpanded;
  29. /************************************************************************************************************************/
  30. /// <summary>The <see cref="GUIStyle"/> used for the area encompassing this drawer.</summary>
  31. protected abstract GUIStyle RegionStyle { get; }
  32. /************************************************************************************************************************/
  33. /// <summary>Draws the details and controls for the target <see cref="Target"/> in the Inspector.</summary>
  34. public virtual void DoGUI()
  35. {
  36. if (!Target.IsValid)
  37. return;
  38. AnimancerGUI.BeginVerticalBox(RegionStyle);
  39. {
  40. DoHeaderGUI();
  41. DoDetailsGUI();
  42. }
  43. AnimancerGUI.EndVerticalBox(RegionStyle);
  44. CheckContextMenu(GUILayoutUtility.GetLastRect());
  45. }
  46. /************************************************************************************************************************/
  47. /// <summary>
  48. /// Draws the name and other details of the <see cref="Target"/> in the GUI.
  49. /// </summary>
  50. protected virtual void DoHeaderGUI()
  51. {
  52. var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.Before);
  53. DoLabelGUI(area);
  54. DoFoldoutGUI(area);
  55. }
  56. /// <summary>
  57. /// Draws a field for the <see cref="AnimancerState.MainObject"/> if it has one, otherwise just a simple text
  58. /// label.
  59. /// </summary>
  60. protected abstract void DoLabelGUI(Rect area);
  61. /// <summary>Draws a foldout arrow to expand/collapse the node details.</summary>
  62. protected abstract void DoFoldoutGUI(Rect area);
  63. /// <summary>Draws the details of the <see cref="Target"/> in the GUI.</summary>
  64. protected abstract void DoDetailsGUI();
  65. /************************************************************************************************************************/
  66. /// <summary>
  67. /// Draws controls for <see cref="AnimancerState.IsPlaying"/>, <see cref="AnimancerNode.Speed"/>, and
  68. /// <see cref="AnimancerNode.Weight"/>.
  69. /// </summary>
  70. protected void DoNodeDetailsGUI()
  71. {
  72. var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.Before);
  73. area.xMin += EditorGUI.indentLevel * AnimancerGUI.IndentSize;
  74. var xMin = area.xMin;
  75. var xMax = area.xMax;
  76. var labelWidth = EditorGUIUtility.labelWidth;
  77. var indentLevel = EditorGUI.indentLevel;
  78. EditorGUI.indentLevel = 0;
  79. // Is Playing.
  80. var state = Target as AnimancerState;
  81. if (state != null)
  82. {
  83. var label = AnimancerGUI.BeginTightLabel("Is Playing");
  84. area.width = EditorGUIUtility.labelWidth + 16;
  85. state.IsPlaying = EditorGUI.Toggle(area, label, state.IsPlaying);
  86. AnimancerGUI.EndTightLabel();
  87. area.x += area.width;
  88. area.xMax = xMax;
  89. }
  90. AnimancerGUI.SplitHorizontally(area, "Speed", "Weight",
  91. out var speedWidth, out var weightWidth, out var speedRect, out var weightRect);
  92. // Speed.
  93. EditorGUIUtility.labelWidth = speedWidth;
  94. EditorGUI.BeginChangeCheck();
  95. var speed = EditorGUI.FloatField(speedRect, "Speed", Target.Speed);
  96. if (EditorGUI.EndChangeCheck())
  97. Target.Speed = speed;
  98. if (AnimancerGUI.TryUseClickEvent(speedRect, 2))
  99. Target.Speed = Target.Speed != 1 ? 1 : 0;
  100. // Weight.
  101. EditorGUIUtility.labelWidth = weightWidth;
  102. EditorGUI.BeginChangeCheck();
  103. var weight = EditorGUI.FloatField(weightRect, "Weight", Target.Weight);
  104. if (EditorGUI.EndChangeCheck())
  105. SetWeight(Mathf.Max(weight, 0));
  106. if (AnimancerGUI.TryUseClickEvent(weightRect, 2))
  107. SetWeight(Target.Weight != 1 ? 1 : 0);
  108. // Not really sure why this is necessary.
  109. // It allows the dummy ID added when the Real Speed is hidden to work properly.
  110. GUIUtility.GetControlID(FocusType.Passive);
  111. // Real Speed (Mixer Synchronization changes the internal Playable Speed without setting the State Speed).
  112. speed = (float)Target._Playable.GetSpeed();
  113. if (Target.Speed != speed)
  114. {
  115. using (new EditorGUI.DisabledScope(true))
  116. {
  117. area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.Before);
  118. area.xMin = xMin;
  119. var label = AnimancerGUI.BeginTightLabel("Real Speed");
  120. EditorGUIUtility.labelWidth = AnimancerGUI.CalculateLabelWidth(label);
  121. EditorGUI.FloatField(area, label, speed);
  122. AnimancerGUI.EndTightLabel();
  123. }
  124. }
  125. else// Add a dummy ID so that subsequent IDs don't change when the Real Speed appears or disappears.
  126. {
  127. GUIUtility.GetControlID(FocusType.Passive);
  128. }
  129. EditorGUI.indentLevel = indentLevel;
  130. EditorGUIUtility.labelWidth = labelWidth;
  131. DoFadeDetailsGUI();
  132. }
  133. /************************************************************************************************************************/
  134. /// <summary>Indicates whether changing the <see cref="AnimancerNode.Weight"/> should normalize its siblings.</summary>
  135. protected virtual bool AutoNormalizeSiblingWeights => false;
  136. private void SetWeight(float weight)
  137. {
  138. if (weight < 0 ||
  139. weight > 1 ||
  140. Mathf.Approximately(Target.Weight, 1) ||
  141. !AutoNormalizeSiblingWeights)
  142. goto JustSetWeight;
  143. var parent = Target.Parent;
  144. if (parent == null)
  145. goto JustSetWeight;
  146. var totalWeight = 0f;
  147. var siblingCount = parent.ChildCount;
  148. for (int i = 0; i < siblingCount; i++)
  149. {
  150. var sibling = parent.GetChild(i);
  151. if (sibling.IsValid())
  152. totalWeight += sibling.Weight;
  153. }
  154. // If the weights weren't previously normalized, don't normalize them now.
  155. if (!Mathf.Approximately(totalWeight, 1))
  156. goto JustSetWeight;
  157. var siblingWeightMultiplier = (totalWeight - weight) / (totalWeight - Target.Weight);
  158. for (int i = 0; i < siblingCount; i++)
  159. {
  160. var sibling = parent.GetChild(i);
  161. if (sibling != Target && sibling.IsValid())
  162. sibling.Weight *= siblingWeightMultiplier;
  163. }
  164. JustSetWeight:
  165. Target.Weight = weight;
  166. }
  167. /************************************************************************************************************************/
  168. /// <summary>Draws the <see cref="AnimancerNode.FadeSpeed"/> and <see cref="AnimancerNode.TargetWeight"/>.</summary>
  169. private void DoFadeDetailsGUI()
  170. {
  171. var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.Before);
  172. area = EditorGUI.IndentedRect(area);
  173. var speedLabel = AnimancerGUI.GetNarrowText("Fade Speed");
  174. var targetLabel = AnimancerGUI.GetNarrowText("Target Weight");
  175. AnimancerGUI.SplitHorizontally(area, speedLabel, targetLabel,
  176. out var speedWidth, out var weightWidth, out var speedRect, out var weightRect);
  177. var labelWidth = EditorGUIUtility.labelWidth;
  178. var indentLevel = EditorGUI.indentLevel;
  179. EditorGUI.indentLevel = 0;
  180. EditorGUI.BeginChangeCheck();
  181. // Fade Speed.
  182. EditorGUIUtility.labelWidth = speedWidth;
  183. Target.FadeSpeed = EditorGUI.DelayedFloatField(speedRect, speedLabel, Target.FadeSpeed);
  184. if (AnimancerGUI.TryUseClickEvent(speedRect, 2))
  185. {
  186. Target.FadeSpeed = Target.FadeSpeed != 0 || AnimancerPlayable.DefaultFadeDuration == 0 ?
  187. 0 :
  188. Math.Abs(Target.Weight - Target.TargetWeight) / AnimancerPlayable.DefaultFadeDuration;
  189. }
  190. // Target Weight.
  191. EditorGUIUtility.labelWidth = weightWidth;
  192. Target.TargetWeight = Mathf.Max(0, EditorGUI.FloatField(weightRect, targetLabel, Target.TargetWeight));
  193. if (AnimancerGUI.TryUseClickEvent(weightRect, 2))
  194. {
  195. if (Target.TargetWeight != Target.Weight)
  196. Target.TargetWeight = Target.Weight;
  197. else if (Target.TargetWeight != 1)
  198. Target.TargetWeight = 1;
  199. else
  200. Target.TargetWeight = 0;
  201. }
  202. if (EditorGUI.EndChangeCheck() && Target.FadeSpeed != 0)
  203. Target.StartFade(Target.TargetWeight, 1 / Target.FadeSpeed);
  204. EditorGUI.indentLevel = indentLevel;
  205. EditorGUIUtility.labelWidth = labelWidth;
  206. }
  207. /************************************************************************************************************************/
  208. #region Context Menu
  209. /************************************************************************************************************************/
  210. /// <summary>
  211. /// The menu label prefix used for details about the <see cref="Target"/>.
  212. /// </summary>
  213. protected const string DetailsPrefix = "Details/";
  214. /// <summary>
  215. /// Checks if the current event is a context menu click within the `clickArea` and opens a context menu with various
  216. /// functions for the <see cref="Target"/>.
  217. /// </summary>
  218. protected void CheckContextMenu(Rect clickArea)
  219. {
  220. if (!AnimancerGUI.TryUseClickEvent(clickArea, 1))
  221. return;
  222. var menu = new GenericMenu();
  223. menu.AddDisabledItem(new GUIContent(Target.ToString()));
  224. PopulateContextMenu(menu);
  225. menu.AddItem(new GUIContent(DetailsPrefix + "Log Details"), false,
  226. () => Debug.Log(Target.GetDescription(), Target.Root?.Component as Object));
  227. menu.AddItem(new GUIContent(DetailsPrefix + "Log Details Of Everything"), false,
  228. () => Debug.Log(Target.Root.GetDescription(), Target.Root?.Component as Object));
  229. AnimancerPlayableDrawer.AddPlayableGraphVisualizerFunction(menu, DetailsPrefix, Target.Root._Graph);
  230. menu.ShowAsContext();
  231. }
  232. /// <summary>Adds functions relevant to the <see cref="Target"/>.</summary>
  233. protected abstract void PopulateContextMenu(GenericMenu menu);
  234. /************************************************************************************************************************/
  235. #endregion
  236. /************************************************************************************************************************/
  237. }
  238. }
  239. #endif