// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik // #if UNITY_EDITOR using System.Collections.Generic; using UnityEditor; using UnityEditorInternal; using UnityEngine; using Object = UnityEngine.Object; namespace Animancer.Editor { /// [Editor-Only] A custom Inspector for s. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/NamedAnimancerComponentEditor /// [CustomEditor(typeof(NamedAnimancerComponent), true), CanEditMultipleObjects] public class NamedAnimancerComponentEditor : AnimancerComponentEditor { /************************************************************************************************************************/ /// [Editor-Only] /// Draws any custom GUI for the `property`. The return value indicates whether the GUI should replace the /// regular call to or not. /// protected override bool DoOverridePropertyGUI(string path, SerializedProperty property, GUIContent label) { switch (path) { case "_PlayAutomatically": if (ShouldShowAnimationFields()) DoDefaultAnimationField(property); return true; case "_Animations": if (ShouldShowAnimationFields()) { DoAnimationsField(property); } return true; default: return base.DoOverridePropertyGUI(path, property, label); } } /************************************************************************************************************************/ /// /// The and /// fields are only used on startup, so we don't need to show /// them in Play Mode after the object is already enabled. /// private bool ShouldShowAnimationFields() { if (!EditorApplication.isPlaying) return true; for (int i = 0; i < Targets.Length; i++) { if (!Targets[i].IsPlayableInitialized) return true; } return false; } /************************************************************************************************************************/ private void DoDefaultAnimationField(SerializedProperty playAutomatically) { var area = AnimancerGUI.LayoutSingleLineRect(); var playAutomaticallyWidth = EditorGUIUtility.labelWidth + AnimancerGUI.ToggleWidth; var playAutomaticallyArea = AnimancerGUI.StealFromLeft(ref area, playAutomaticallyWidth); using (ObjectPool.Disposable.AcquireContent(out var label, playAutomatically)) EditorGUI.PropertyField(playAutomaticallyArea, playAutomatically, label); SerializedProperty firstElement; AnimationClip clip; var animations = serializedObject.FindProperty("_Animations"); if (animations.arraySize > 0) { firstElement = animations.GetArrayElementAtIndex(0); clip = (AnimationClip)firstElement.objectReferenceValue; EditorGUI.BeginProperty(area, null, firstElement); } else { firstElement = null; clip = null; EditorGUI.BeginProperty(area, null, animations); } EditorGUI.BeginChangeCheck(); var indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; clip = (AnimationClip)EditorGUI.ObjectField(area, GUIContent.none, clip, typeof(AnimationClip), true); EditorGUI.indentLevel = indentLevel; if (EditorGUI.EndChangeCheck()) { if (clip != null) { if (firstElement == null) { animations.arraySize = 1; firstElement = animations.GetArrayElementAtIndex(0); } firstElement.objectReferenceValue = clip; } else { if (firstElement == null || animations.arraySize == 1) animations.arraySize = 0; else firstElement.objectReferenceValue = clip; } } EditorGUI.EndProperty(); } /************************************************************************************************************************/ private ReorderableList _Animations; private static int _RemoveAnimationIndex; private void DoAnimationsField(SerializedProperty property) { GUILayout.Space(AnimancerGUI.StandardSpacing - 1); if (_Animations == null) { _Animations = new ReorderableList(property.serializedObject, property.Copy()) { drawHeaderCallback = DrawAnimationsHeader, drawElementCallback = DrawAnimationElement, elementHeight = AnimancerGUI.LineHeight, onRemoveCallback = RemoveSelectedElement, }; } _RemoveAnimationIndex = -1; GUILayout.BeginVertical(); _Animations.DoLayoutList(); GUILayout.EndVertical(); if (_RemoveAnimationIndex >= 0) { property.DeleteArrayElementAtIndex(_RemoveAnimationIndex); } AnimancerGUI.HandleDragAndDropAnimations(GUILayoutUtility.GetLastRect(), (clip) => { var index = property.arraySize; property.arraySize = index + 1; var element = property.GetArrayElementAtIndex(index); element.objectReferenceValue = clip; property.serializedObject.ApplyModifiedProperties(); }); } /************************************************************************************************************************/ private SerializedProperty _AnimationsArraySize; private void DrawAnimationsHeader(Rect area) { var labelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth -= 6; area.width += 5; var property = _Animations.serializedProperty; using (ObjectPool.Disposable.AcquireContent(out var label, property)) { label = EditorGUI.BeginProperty(area, label, property); if (_AnimationsArraySize == null) { _AnimationsArraySize = property.Copy(); _AnimationsArraySize.Next(true); _AnimationsArraySize.Next(true); } EditorGUI.PropertyField(area, _AnimationsArraySize, label); EditorGUI.EndProperty(); } EditorGUIUtility.labelWidth = labelWidth; } /************************************************************************************************************************/ private static readonly HashSet PreviousAnimations = new HashSet(); private void DrawAnimationElement(Rect area, int index, bool isActive, bool isFocused) { if (index == 0) PreviousAnimations.Clear(); var labelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth -= 20; var element = _Animations.serializedProperty.GetArrayElementAtIndex(index); var color = GUI.color; var animation = element.objectReferenceValue; if (animation == null || PreviousAnimations.Contains(animation)) GUI.color = AnimancerGUI.WarningFieldColor; else PreviousAnimations.Add(animation); EditorGUI.BeginChangeCheck(); EditorGUI.ObjectField(area, element, GUIContent.none); if (EditorGUI.EndChangeCheck() && element.objectReferenceValue == null) _RemoveAnimationIndex = index; GUI.color = color; EditorGUIUtility.labelWidth = labelWidth; } /************************************************************************************************************************/ private static void RemoveSelectedElement(ReorderableList list) { var property = list.serializedProperty; var element = property.GetArrayElementAtIndex(list.index); // Deleting a non-null element sets it to null, so we make sure it's null to actually remove it. if (element.objectReferenceValue != null) element.objectReferenceValue = null; property.DeleteArrayElementAtIndex(list.index); if (list.index >= property.arraySize - 1) list.index = property.arraySize - 1; } /************************************************************************************************************************/ } } #endif