AnimancerLayerDrawer.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEditor;
  6. using UnityEngine;
  7. namespace Animancer.Editor
  8. {
  9. /// <summary>[Editor-Only]
  10. /// A custom Inspector for an <see cref="AnimancerLayer"/> which sorts and exposes some of its internal values.
  11. /// </summary>
  12. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerLayerDrawer
  13. ///
  14. public class AnimancerLayerDrawer : AnimancerNodeDrawer<AnimancerLayer>
  15. {
  16. /************************************************************************************************************************/
  17. /// <summary>The states in the target layer which have non-zero <see cref="AnimancerNode.Weight"/>.</summary>
  18. public readonly List<AnimancerState> ActiveStates = new List<AnimancerState>();
  19. /// <summary>The states in the target layer which have zero <see cref="AnimancerNode.Weight"/>.</summary>
  20. public readonly List<AnimancerState> InactiveStates = new List<AnimancerState>();
  21. /************************************************************************************************************************/
  22. /// <summary>The <see cref="GUIStyle"/> used for the area encompassing this drawer is <see cref="GUISkin.box"/>.</summary>
  23. protected override GUIStyle RegionStyle => GUI.skin.box;
  24. /************************************************************************************************************************/
  25. #region Gathering
  26. /************************************************************************************************************************/
  27. /// <summary>
  28. /// Initializes an editor in the list for each layer in the `animancer`.
  29. /// <para></para>
  30. /// The `count` indicates the number of elements actually being used. Spare elements are kept in the list in
  31. /// case they need to be used again later.
  32. /// </summary>
  33. internal static void GatherLayerEditors(AnimancerPlayable animancer, List<AnimancerLayerDrawer> editors, out int count)
  34. {
  35. count = animancer.Layers.Count;
  36. for (int i = 0; i < count; i++)
  37. {
  38. AnimancerLayerDrawer editor;
  39. if (editors.Count <= i)
  40. {
  41. editor = new AnimancerLayerDrawer();
  42. editors.Add(editor);
  43. }
  44. else
  45. {
  46. editor = editors[i];
  47. }
  48. editor.GatherStates(animancer.Layers[i]);
  49. }
  50. }
  51. /************************************************************************************************************************/
  52. /// <summary>
  53. /// Sets the target `layer` and sorts its states and their keys into the active/inactive lists.
  54. /// </summary>
  55. private void GatherStates(AnimancerLayer layer)
  56. {
  57. Target = layer;
  58. ActiveStates.Clear();
  59. InactiveStates.Clear();
  60. foreach (var state in layer)
  61. {
  62. if (AnimancerPlayableDrawer.HideInactiveStates && state.Weight == 0)
  63. continue;
  64. if (!AnimancerPlayableDrawer.SeparateActiveFromInactiveStates || state.Weight != 0)
  65. {
  66. ActiveStates.Add(state);
  67. }
  68. else
  69. {
  70. InactiveStates.Add(state);
  71. }
  72. }
  73. SortAndGatherKeys(ActiveStates);
  74. SortAndGatherKeys(InactiveStates);
  75. }
  76. /************************************************************************************************************************/
  77. /// <summary>
  78. /// Sorts any entries that use another state as their key to come right after that state.
  79. /// See <see cref="AnimancerPlayable.Play(AnimancerState, float, FadeMode)"/>.
  80. /// </summary>
  81. private static void SortAndGatherKeys(List<AnimancerState> states)
  82. {
  83. var count = states.Count;
  84. if (count == 0)
  85. return;
  86. if (AnimancerPlayableDrawer.SortStatesByName)
  87. {
  88. states.Sort((x, y) =>
  89. {
  90. if (x.MainObject == null)
  91. return y.MainObject == null ? 0 : 1;
  92. else if (y.MainObject == null)
  93. return -1;
  94. return x.MainObject.name.CompareTo(y.MainObject.name);
  95. });
  96. }
  97. // Sort any states that use another state as their key to be right after the key.
  98. for (int i = 0; i < count; i++)
  99. {
  100. var state = states[i];
  101. var key = state.Key;
  102. var keyState = key as AnimancerState;
  103. if (keyState == null)
  104. continue;
  105. var keyStateIndex = states.IndexOf(keyState);
  106. if (keyStateIndex < 0 || keyStateIndex + 1 == i)
  107. continue;
  108. states.RemoveAt(i);
  109. if (keyStateIndex < i)
  110. keyStateIndex++;
  111. states.Insert(keyStateIndex, state);
  112. i--;
  113. }
  114. }
  115. /************************************************************************************************************************/
  116. #endregion
  117. /************************************************************************************************************************/
  118. /// <summary>Draws the layer's name and weight.</summary>
  119. protected override void DoLabelGUI(Rect area)
  120. {
  121. var label = Target.IsAdditive ? "Additive" : "Override";
  122. if (Target._Mask != null)
  123. label = $"{label} ({Target._Mask.name})";
  124. area.xMin += FoldoutIndent;
  125. AnimancerGUI.DoWeightLabel(ref area, Target.Weight);
  126. EditorGUIUtility.labelWidth -= FoldoutIndent;
  127. EditorGUI.LabelField(area, Target.ToString(), label);
  128. EditorGUIUtility.labelWidth += FoldoutIndent;
  129. }
  130. /************************************************************************************************************************/
  131. /// <summary>The number of pixels of indentation required to fit the foldout arrow.</summary>
  132. const float FoldoutIndent = 12;
  133. /// <inheritdoc/>
  134. protected override void DoFoldoutGUI(Rect area)
  135. {
  136. var hierarchyMode = EditorGUIUtility.hierarchyMode;
  137. EditorGUIUtility.hierarchyMode = true;
  138. area.xMin += FoldoutIndent;
  139. IsExpanded = EditorGUI.Foldout(area, IsExpanded, GUIContent.none, true);
  140. EditorGUIUtility.hierarchyMode = hierarchyMode;
  141. }
  142. /************************************************************************************************************************/
  143. /// <inheritdoc/>
  144. protected override void DoDetailsGUI()
  145. {
  146. if (IsExpanded)
  147. {
  148. EditorGUI.indentLevel++;
  149. GUILayout.BeginHorizontal();
  150. GUILayout.Space(FoldoutIndent);
  151. GUILayout.BeginVertical();
  152. DoLayerDetailsGUI();
  153. DoNodeDetailsGUI();
  154. GUILayout.EndVertical();
  155. GUILayout.EndHorizontal();
  156. EditorGUI.indentLevel--;
  157. }
  158. DoStatesGUI();
  159. }
  160. /************************************************************************************************************************/
  161. /// <summary>
  162. /// Draws controls for <see cref="AnimancerLayer.IsAdditive"/> and <see cref="AnimancerLayer._Mask"/>.
  163. /// </summary>
  164. private void DoLayerDetailsGUI()
  165. {
  166. var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.Before);
  167. area = EditorGUI.IndentedRect(area);
  168. var labelWidth = EditorGUIUtility.labelWidth;
  169. var indentLevel = EditorGUI.indentLevel;
  170. EditorGUI.indentLevel = 0;
  171. var additiveLabel = AnimancerGUI.GetNarrowText("Is Additive");
  172. var additiveWidth = GUI.skin.toggle.CalculateWidth(additiveLabel);
  173. var maskRect = AnimancerGUI.StealFromRight(ref area, area.width - additiveWidth);
  174. // Additive.
  175. EditorGUIUtility.labelWidth = AnimancerGUI.CalculateLabelWidth(additiveLabel);
  176. EditorGUI.BeginChangeCheck();
  177. var isAdditive = EditorGUI.Toggle(area, additiveLabel, Target.IsAdditive);
  178. if (EditorGUI.EndChangeCheck())
  179. Target.IsAdditive = isAdditive;
  180. // Mask.
  181. using (ObjectPool.Disposable.AcquireContent(out var label, "Mask"))
  182. {
  183. EditorGUIUtility.labelWidth = AnimancerGUI.CalculateLabelWidth(label.text);
  184. EditorGUI.BeginChangeCheck();
  185. Target._Mask = (AvatarMask)EditorGUI.ObjectField(maskRect, label, Target._Mask, typeof(AvatarMask), false);
  186. if (EditorGUI.EndChangeCheck())
  187. Target.SetMask(Target._Mask);
  188. }
  189. EditorGUI.indentLevel = indentLevel;
  190. EditorGUIUtility.labelWidth = labelWidth;
  191. }
  192. /************************************************************************************************************************/
  193. private void DoStatesGUI()
  194. {
  195. if (AnimancerPlayableDrawer.HideInactiveStates)
  196. {
  197. DoStatesGUI("Active States", ActiveStates);
  198. }
  199. else if (AnimancerPlayableDrawer.SeparateActiveFromInactiveStates)
  200. {
  201. DoStatesGUI("Active States", ActiveStates);
  202. DoStatesGUI("Inactive States", InactiveStates);
  203. }
  204. else
  205. {
  206. DoStatesGUI("States", ActiveStates);
  207. }
  208. if (Target.Index == 0 &&
  209. Target.Weight != 0 &&
  210. !Target.IsAdditive &&
  211. !Mathf.Approximately(Target.GetTotalWeight(), 1))
  212. {
  213. EditorGUILayout.HelpBox(
  214. "The total Weight of all states in this layer does not equal 1, which will likely give undesirable results." +
  215. " Click here for more information.",
  216. MessageType.Warning);
  217. if (AnimancerGUI.TryUseClickEventInLastRect())
  218. EditorUtility.OpenWithDefaultApp(Strings.DocsURLs.Fading);
  219. }
  220. }
  221. /************************************************************************************************************************/
  222. /// <summary>Draws all `states` in the given list.</summary>
  223. private void DoStatesGUI(string label, List<AnimancerState> states)
  224. {
  225. var area = AnimancerGUI.LayoutSingleLineRect();
  226. const string Label = "Weight";
  227. var width = AnimancerGUI.CalculateLabelWidth(Label);
  228. GUI.Label(AnimancerGUI.StealFromRight(ref area, width), Label);
  229. EditorGUI.LabelField(area, label, states.Count.ToString());
  230. EditorGUI.indentLevel++;
  231. for (int i = 0; i < states.Count; i++)
  232. {
  233. DoStateGUI(states[i]);
  234. }
  235. EditorGUI.indentLevel--;
  236. }
  237. /************************************************************************************************************************/
  238. /// <summary>Cached Inspectors that have already been created for states.</summary>
  239. private readonly Dictionary<AnimancerState, IAnimancerNodeDrawer>
  240. StateInspectors = new Dictionary<AnimancerState, IAnimancerNodeDrawer>();
  241. /// <summary>Draws the Inspector for the given `state`.</summary>
  242. private void DoStateGUI(AnimancerState state)
  243. {
  244. if (!StateInspectors.TryGetValue(state, out var inspector))
  245. {
  246. inspector = state.CreateDrawer();
  247. StateInspectors.Add(state, inspector);
  248. }
  249. inspector.DoGUI();
  250. DoChildStatesGUI(state);
  251. }
  252. /************************************************************************************************************************/
  253. /// <summary>Draws all child states of the `state`.</summary>
  254. private void DoChildStatesGUI(AnimancerState state)
  255. {
  256. EditorGUI.indentLevel++;
  257. foreach (var child in state)
  258. {
  259. if (child == null)
  260. continue;
  261. DoStateGUI(child);
  262. }
  263. EditorGUI.indentLevel--;
  264. }
  265. /************************************************************************************************************************/
  266. /// <inheritdoc/>
  267. protected override void DoHeaderGUI()
  268. {
  269. if (AnimancerPlayableDrawer.HideSingleLayerHeader &&
  270. Target.Root.Layers.Count == 1 &&
  271. Target.Weight == 1 &&
  272. Target.TargetWeight == 1 &&
  273. Target.Speed == 1 &&
  274. !Target.IsAdditive &&
  275. Target._Mask == null)
  276. return;
  277. base.DoHeaderGUI();
  278. }
  279. /************************************************************************************************************************/
  280. /// <inheritdoc/>
  281. public override void DoGUI()
  282. {
  283. if (!Target.IsValid)
  284. return;
  285. base.DoGUI();
  286. var area = GUILayoutUtility.GetLastRect();
  287. HandleDragAndDropAnimations(area, Target.Root.Component, Target.Index);
  288. }
  289. /// <summary>
  290. /// If <see cref="AnimationClip"/>s or <see cref="IAnimationClipSource"/>s are dropped inside the `dropArea`,
  291. /// this method creates a new state in the `target` for each animation.
  292. /// </summary>
  293. public static void HandleDragAndDropAnimations(Rect dropArea, IAnimancerComponent target, int layerIndex)
  294. {
  295. if (target == null)
  296. return;
  297. AnimancerGUI.HandleDragAndDropAnimations(dropArea, (clip) =>
  298. {
  299. target.Playable.Layers[layerIndex].GetOrCreateState(clip);
  300. });
  301. }
  302. /************************************************************************************************************************/
  303. #region Context Menu
  304. /************************************************************************************************************************/
  305. /// <inheritdoc/>
  306. protected override void PopulateContextMenu(GenericMenu menu)
  307. {
  308. menu.AddDisabledItem(new GUIContent($"{DetailsPrefix}{nameof(Target.CurrentState)}: {Target.CurrentState}"));
  309. menu.AddDisabledItem(new GUIContent($"{DetailsPrefix}{nameof(Target.CommandCount)}: {Target.CommandCount}"));
  310. menu.AddFunction("Stop",
  311. HasAnyStates((state) => state.IsPlaying || state.Weight != 0),
  312. () => Target.Stop());
  313. AnimancerEditorUtilities.AddFadeFunction(menu, "Fade In",
  314. Target.Index > 0 && Target.Weight != 1, Target,
  315. (duration) => Target.StartFade(1, duration));
  316. AnimancerEditorUtilities.AddFadeFunction(menu, "Fade Out",
  317. Target.Index > 0 && Target.Weight != 0, Target,
  318. (duration) => Target.StartFade(0, duration));
  319. AnimancerEditorUtilities.AddContextMenuIK(menu, Target);
  320. menu.AddSeparator("");
  321. menu.AddFunction("Destroy States",
  322. ActiveStates.Count > 0 || InactiveStates.Count > 0,
  323. () => Target.DestroyStates());
  324. AnimancerPlayableDrawer.AddRootFunctions(menu, Target.Root);
  325. menu.AddSeparator("");
  326. AnimancerPlayableDrawer.AddDisplayOptions(menu);
  327. AnimancerEditorUtilities.AddDocumentationLink(menu, "Layer Documentation", Strings.DocsURLs.Layers);
  328. menu.ShowAsContext();
  329. }
  330. /************************************************************************************************************************/
  331. private bool HasAnyStates(Func<AnimancerState, bool> condition)
  332. {
  333. foreach (var state in Target)
  334. {
  335. if (condition(state))
  336. return true;
  337. }
  338. return false;
  339. }
  340. /************************************************************************************************************************/
  341. #endregion
  342. /************************************************************************************************************************/
  343. }
  344. }
  345. #endif