// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 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 { /************************************************************************************************************************/ /// 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.isPlayingOrWillChangePlaymode) return true; for (int i = 0; i < Targets.Length; i++) if (!Targets[i].IsGraphInitialized) 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 (var label = PooledGUIContent.Acquire(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 = AnimancerGUI.DoObjectFieldGUI(area, GUIContent.none, clip, 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); _Animations ??= new(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); HandleDragAndDropToAddAnimations(GUILayoutUtility.GetLastRect(), property); } /************************************************************************************************************************/ private SerializedProperty _AnimationsArraySize; private void DrawAnimationsHeader(Rect area) { var labelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth -= 6; area.width += 5; var property = _Animations.serializedProperty; using (var label = PooledGUIContent.Acquire(property)) { var propertyLabel = EditorGUI.BeginProperty(area, label, property); if (_AnimationsArraySize == null) { _AnimationsArraySize = property.Copy(); _AnimationsArraySize.Next(true); _AnimationsArraySize.Next(true); } EditorGUI.PropertyField(area, _AnimationsArraySize, propertyLabel); EditorGUI.EndProperty(); } EditorGUIUtility.labelWidth = labelWidth; } /************************************************************************************************************************/ private static readonly HashSet PreviousAnimations = new(); 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; } /************************************************************************************************************************/ private static DragAndDropHandler _DropToAddAnimations; private static SerializedProperty _DropToAddAnimationsProperty; private static void HandleDragAndDropToAddAnimations(Rect area, SerializedProperty property) { _DropToAddAnimationsProperty = property; _DropToAddAnimations ??= (obj, isDrop) => { using (ListPool.Instance.Acquire(out var clips)) { clips.GatherFromSource(obj); var anyValid = false; for (int i = 0; i < clips.Count; i++) { var clip = clips[i]; if (clip.legacy) continue; if (!isDrop) return true; anyValid = true; var targetProperty = _DropToAddAnimationsProperty; var index = targetProperty.arraySize; targetProperty.arraySize = index + 1; var element = targetProperty.GetArrayElementAtIndex(index); element.objectReferenceValue = clip; targetProperty.serializedObject.ApplyModifiedProperties(); } return anyValid; } }; _DropToAddAnimations.Handle(area); } /************************************************************************************************************************/ } } #endif