NamedAnimancerComponentEditor.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System.Collections.Generic;
  4. using UnityEditor;
  5. using UnityEditorInternal;
  6. using UnityEngine;
  7. using Object = UnityEngine.Object;
  8. namespace Animancer.Editor
  9. {
  10. /// <summary>[Editor-Only] A custom Inspector for <see cref="NamedAnimancerComponent"/>s.</summary>
  11. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/NamedAnimancerComponentEditor
  12. ///
  13. [CustomEditor(typeof(NamedAnimancerComponent), true), CanEditMultipleObjects]
  14. public class NamedAnimancerComponentEditor : AnimancerComponentEditor
  15. {
  16. /************************************************************************************************************************/
  17. /// <inheritdoc/>
  18. protected override bool DoOverridePropertyGUI(string path, SerializedProperty property, GUIContent label)
  19. {
  20. switch (path)
  21. {
  22. case "_PlayAutomatically":
  23. if (ShouldShowAnimationFields())
  24. DoDefaultAnimationField(property);
  25. return true;
  26. case "_Animations":
  27. if (ShouldShowAnimationFields())
  28. DoAnimationsField(property);
  29. return true;
  30. default:
  31. return base.DoOverridePropertyGUI(path, property, label);
  32. }
  33. }
  34. /************************************************************************************************************************/
  35. /// <summary>
  36. /// The <see cref="NamedAnimancerComponent.PlayAutomatically"/> and
  37. /// <see cref="NamedAnimancerComponent.Animations"/> fields are only used on startup, so we don't need to show
  38. /// them in Play Mode after the object is already enabled.
  39. /// </summary>
  40. private bool ShouldShowAnimationFields()
  41. {
  42. if (!EditorApplication.isPlayingOrWillChangePlaymode)
  43. return true;
  44. for (int i = 0; i < Targets.Length; i++)
  45. if (!Targets[i].IsGraphInitialized)
  46. return true;
  47. return false;
  48. }
  49. /************************************************************************************************************************/
  50. private void DoDefaultAnimationField(SerializedProperty playAutomatically)
  51. {
  52. var area = AnimancerGUI.LayoutSingleLineRect();
  53. var playAutomaticallyWidth = EditorGUIUtility.labelWidth + AnimancerGUI.ToggleWidth;
  54. var playAutomaticallyArea = AnimancerGUI.StealFromLeft(ref area, playAutomaticallyWidth);
  55. using (var label = PooledGUIContent.Acquire(playAutomatically))
  56. EditorGUI.PropertyField(playAutomaticallyArea, playAutomatically, label);
  57. SerializedProperty firstElement;
  58. AnimationClip clip;
  59. var animations = serializedObject.FindProperty("_Animations");
  60. if (animations.arraySize > 0)
  61. {
  62. firstElement = animations.GetArrayElementAtIndex(0);
  63. clip = (AnimationClip)firstElement.objectReferenceValue;
  64. EditorGUI.BeginProperty(area, null, firstElement);
  65. }
  66. else
  67. {
  68. firstElement = null;
  69. clip = null;
  70. EditorGUI.BeginProperty(area, null, animations);
  71. }
  72. EditorGUI.BeginChangeCheck();
  73. var indentLevel = EditorGUI.indentLevel;
  74. EditorGUI.indentLevel = 0;
  75. clip = AnimancerGUI.DoObjectFieldGUI(area, GUIContent.none, clip, true);
  76. EditorGUI.indentLevel = indentLevel;
  77. if (EditorGUI.EndChangeCheck())
  78. {
  79. if (clip != null)
  80. {
  81. if (firstElement == null)
  82. {
  83. animations.arraySize = 1;
  84. firstElement = animations.GetArrayElementAtIndex(0);
  85. }
  86. firstElement.objectReferenceValue = clip;
  87. }
  88. else
  89. {
  90. if (firstElement == null || animations.arraySize == 1)
  91. animations.arraySize = 0;
  92. else
  93. firstElement.objectReferenceValue = clip;
  94. }
  95. }
  96. EditorGUI.EndProperty();
  97. }
  98. /************************************************************************************************************************/
  99. private ReorderableList _Animations;
  100. private static int _RemoveAnimationIndex;
  101. private void DoAnimationsField(SerializedProperty property)
  102. {
  103. GUILayout.Space(AnimancerGUI.StandardSpacing - 1);
  104. _Animations ??= new(property.serializedObject, property.Copy())
  105. {
  106. drawHeaderCallback = DrawAnimationsHeader,
  107. drawElementCallback = DrawAnimationElement,
  108. elementHeight = AnimancerGUI.LineHeight,
  109. onRemoveCallback = RemoveSelectedElement,
  110. };
  111. _RemoveAnimationIndex = -1;
  112. GUILayout.BeginVertical();
  113. _Animations.DoLayoutList();
  114. GUILayout.EndVertical();
  115. if (_RemoveAnimationIndex >= 0)
  116. property.DeleteArrayElementAtIndex(_RemoveAnimationIndex);
  117. HandleDragAndDropToAddAnimations(GUILayoutUtility.GetLastRect(), property);
  118. }
  119. /************************************************************************************************************************/
  120. private SerializedProperty _AnimationsArraySize;
  121. private void DrawAnimationsHeader(Rect area)
  122. {
  123. var labelWidth = EditorGUIUtility.labelWidth;
  124. EditorGUIUtility.labelWidth -= 6;
  125. area.width += 5;
  126. var property = _Animations.serializedProperty;
  127. using (var label = PooledGUIContent.Acquire(property))
  128. {
  129. var propertyLabel = EditorGUI.BeginProperty(area, label, property);
  130. if (_AnimationsArraySize == null)
  131. {
  132. _AnimationsArraySize = property.Copy();
  133. _AnimationsArraySize.Next(true);
  134. _AnimationsArraySize.Next(true);
  135. }
  136. EditorGUI.PropertyField(area, _AnimationsArraySize, propertyLabel);
  137. EditorGUI.EndProperty();
  138. }
  139. EditorGUIUtility.labelWidth = labelWidth;
  140. }
  141. /************************************************************************************************************************/
  142. private static readonly HashSet<Object>
  143. PreviousAnimations = new();
  144. private void DrawAnimationElement(Rect area, int index, bool isActive, bool isFocused)
  145. {
  146. if (index == 0)
  147. PreviousAnimations.Clear();
  148. var labelWidth = EditorGUIUtility.labelWidth;
  149. EditorGUIUtility.labelWidth -= 20;
  150. var element = _Animations.serializedProperty.GetArrayElementAtIndex(index);
  151. var color = GUI.color;
  152. var animation = element.objectReferenceValue;
  153. if (animation == null || PreviousAnimations.Contains(animation))
  154. GUI.color = AnimancerGUI.WarningFieldColor;
  155. else
  156. PreviousAnimations.Add(animation);
  157. EditorGUI.BeginChangeCheck();
  158. EditorGUI.ObjectField(area, element, GUIContent.none);
  159. if (EditorGUI.EndChangeCheck() && element.objectReferenceValue == null)
  160. _RemoveAnimationIndex = index;
  161. GUI.color = color;
  162. EditorGUIUtility.labelWidth = labelWidth;
  163. }
  164. /************************************************************************************************************************/
  165. private static void RemoveSelectedElement(ReorderableList list)
  166. {
  167. var property = list.serializedProperty;
  168. var element = property.GetArrayElementAtIndex(list.index);
  169. // Deleting a non-null element sets it to null, so we make sure it's null to actually remove it.
  170. if (element.objectReferenceValue != null)
  171. element.objectReferenceValue = null;
  172. property.DeleteArrayElementAtIndex(list.index);
  173. if (list.index >= property.arraySize - 1)
  174. list.index = property.arraySize - 1;
  175. }
  176. /************************************************************************************************************************/
  177. private static DragAndDropHandler<object> _DropToAddAnimations;
  178. private static SerializedProperty _DropToAddAnimationsProperty;
  179. private static void HandleDragAndDropToAddAnimations(Rect area, SerializedProperty property)
  180. {
  181. _DropToAddAnimationsProperty = property;
  182. _DropToAddAnimations ??= (obj, isDrop) =>
  183. {
  184. using (ListPool<AnimationClip>.Instance.Acquire(out var clips))
  185. {
  186. clips.GatherFromSource(obj);
  187. var anyValid = false;
  188. for (int i = 0; i < clips.Count; i++)
  189. {
  190. var clip = clips[i];
  191. if (clip.legacy)
  192. continue;
  193. if (!isDrop)
  194. return true;
  195. anyValid = true;
  196. var targetProperty = _DropToAddAnimationsProperty;
  197. var index = targetProperty.arraySize;
  198. targetProperty.arraySize = index + 1;
  199. var element = targetProperty.GetArrayElementAtIndex(index);
  200. element.objectReferenceValue = clip;
  201. targetProperty.serializedObject.ApplyModifiedProperties();
  202. }
  203. return anyValid;
  204. }
  205. };
  206. _DropToAddAnimations.Handle(area);
  207. }
  208. /************************************************************************************************************************/
  209. }
  210. }
  211. #endif