123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- #if UNITY_EDITOR
- using System;
- using System.Collections.Generic;
- using UnityEditor;
- using UnityEngine;
- namespace Animancer.Editor
- {
- /// <summary>[Editor-Only] Persistent settings used by Animancer.</summary>
- /// <remarks>
- /// This asset automatically creates itself when first accessed.
- /// <para></para>
- /// The default location is <em>Packages/com.kybernetik.animancer/Code/Editor</em>, but you can freely move it
- /// (and the whole Animancer folder) anywhere in your project.
- /// <para></para>
- /// These settings can also be accessed via the Settings in the <see cref="Tools.AnimancerToolsWindow"/>
- /// (<c>Window/Animation/Animancer Tools</c>).
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerSettings
- ///
- [AnimancerHelpUrl(typeof(AnimancerSettings))]
- public class AnimancerSettings : ScriptableObject
- {
- /************************************************************************************************************************/
- private static AnimancerSettings _Instance;
- /// <summary>
- /// Loads an existing <see cref="AnimancerSettings"/> if there is one anywhere in your project.
- /// Otherwise, creates a new one and saves it in the Assets folder.
- /// </summary>
- public static AnimancerSettings Instance
- {
- get
- {
- if (_Instance != null)
- return _Instance;
- _Instance = AnimancerEditorUtilities.FindAssetOfType<AnimancerSettings>();
- if (_Instance != null)
- return _Instance;
- _Instance = CreateInstance<AnimancerSettings>();
- _Instance.name = "Animancer Settings";
- _Instance.hideFlags = HideFlags.DontSaveInBuild;
- var path = $"Assets/{_Instance.name}.asset";
- path = AssetDatabase.GenerateUniqueAssetPath(path);
- AssetDatabase.CreateAsset(_Instance, path);
- return _Instance;
- }
- }
- /************************************************************************************************************************/
- /// <summary>Finds an existing instance of this asset anywhere in the project.</summary>
- [InitializeOnLoadMethod]
- private static void FindExistingInstance()
- {
- if (_Instance == null)
- _Instance = AnimancerEditorUtilities.FindAssetOfType<AnimancerSettings>();
- }
- /************************************************************************************************************************/
- private SerializedObject _SerializedObject;
- /// <summary>The <see cref="SerializedProperty"/> representing the <see cref="Instance"/>.</summary>
- public static SerializedObject SerializedObject
- => Instance._SerializedObject ?? (Instance._SerializedObject = new(Instance));
- /************************************************************************************************************************/
- private readonly List<Dictionary<string, SerializedProperty>>
- SerializedProperties = new();
- private static SerializedProperty GetSerializedProperty(int index, string propertyPath)
- {
- while (index >= Instance.SerializedProperties.Count)
- Instance.SerializedProperties.Add(null);
- var properties = Instance.SerializedProperties[index];
- properties ??= Instance.SerializedProperties[index] = new();
- if (!properties.TryGetValue(propertyPath, out var property))
- {
- property = SerializedObject.FindProperty(propertyPath);
- properties.Add(propertyPath, property);
- }
- return property;
- }
- /// <summary>Returns a <see cref="SerializedProperty"/> relative to the data at the specified `index`.</summary>
- public static SerializedProperty GetSerializedProperty(
- int index,
- ref string basePropertyPath,
- string propertyPath)
- {
- if (string.IsNullOrEmpty(basePropertyPath))
- basePropertyPath =
- $"{nameof(_Data)}{Serialization.ArrayDataPrefix}{index}{Serialization.ArrayDataSuffix}";
- if (string.IsNullOrEmpty(propertyPath))
- propertyPath = basePropertyPath;
- else
- propertyPath = $"{basePropertyPath}.{propertyPath}";
- return GetSerializedProperty(index, propertyPath);
- }
- /************************************************************************************************************************/
- [SerializeReference]
- private List<AnimancerSettingsGroup> _Data;
- /// <summary>Returns a stored item of the specified type or creates a new one if necessary.</summary>
- public static T GetOrCreateData<T>()
- where T : AnimancerSettingsGroup, new()
- {
- ref var data = ref Instance._Data;
- data ??= new();
- var index = AnimancerEditorUtilities.IndexOfType(Instance._Data, typeof(T));
- if (index >= 0)
- return (T)data[index];
- var newT = new T();
- newT.SetDataIndex(data.Count);
- data.Add(newT);
- SetDirty();
- return newT;
- }
- /************************************************************************************************************************/
- /// <summary>Calls <see cref="EditorUtility.SetDirty"/> on the <see cref="Instance"/>.</summary>
- public static new void SetDirty()
- => EditorUtility.SetDirty(_Instance);
- /************************************************************************************************************************/
- /// <summary>
- /// Ensures that there is an instance of each class derived from <see cref="AnimancerSettingsGroup"/>.
- /// </summary>
- protected virtual void OnEnable()
- {
- AnimancerEditorUtilities.InstantiateDerivedTypes(ref _Data);
- for (int i = 0; i < _Data.Count; i++)
- _Data[i].SetDataIndex(i);
- }
- /************************************************************************************************************************/
- /// <summary>A custom Inspector for <see cref="AnimancerSettings"/>.</summary>
- [CustomEditor(typeof(AnimancerSettings), true), CanEditMultipleObjects]
- public class Editor : UnityEditor.Editor
- {
- /************************************************************************************************************************/
- [NonSerialized]
- private SerializedProperty _Data;
- /************************************************************************************************************************/
- /// <summary>Called when this object is first loaded.</summary>
- protected virtual void OnEnable()
- {
- _Data = serializedObject.FindProperty(nameof(AnimancerSettings._Data));
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override void OnInspectorGUI()
- {
- DoInfoGUI();
- DoOptionalWarningsGUI();
- serializedObject.Update();
- var count = _Data.arraySize;
- for (int i = 0; i < count; i++)
- DoDataGUI(_Data.GetArrayElementAtIndex(i), i);
- serializedObject.ApplyModifiedProperties();
- }
- /************************************************************************************************************************/
- /// <summary>
- /// If <c>true</c>, the next <see cref="OnInspectorGUI"/> will skip drawing the info panel.
- /// </summary>
- public static bool HideNextInfo { get; set; }
- private void DoInfoGUI()
- {
- if (HideNextInfo)
- {
- HideNextInfo = false;
- }
- else
- {
- EditorGUILayout.HelpBox(
- "Feel free to move this asset anywhere in your project." +
- "\n\nIt should generally not be in the Animancer folder" +
- " so that if you ever update Animancer you can delete that folder" +
- " without losing these settings." +
- "\n\nIf this asset is deleted, it will be automatically recreated" +
- " with default values when something needs it.",
- MessageType.Info);
- }
- }
- /************************************************************************************************************************/
- private void DoDataGUI(SerializedProperty property, int index)
- {
- if (property.managedReferenceValue is AnimancerSettingsGroup value)
- {
- DoHeading(value.DisplayName);
- var first = true;
- var depth = property.depth;
- while (property.NextVisible(first) && property.depth > depth)
- {
- first = false;
- EditorGUILayout.PropertyField(property, true);
- }
- }
- else
- {
- EditorGUILayout.BeginHorizontal();
- DoHeading("Missing Type");
- if (GUILayout.Button("X", AnimancerGUI.MiniButtonStyle))
- {
- var count = _Data.arraySize;
- _Data.DeleteArrayElementAtIndex(index);
- if (count == _Data.arraySize)
- _Data.DeleteArrayElementAtIndex(index);
- serializedObject.ApplyModifiedProperties();
- GUIUtility.ExitGUI();
- }
- EditorGUILayout.EndHorizontal();
- }
- }
- /************************************************************************************************************************/
- private void DoOptionalWarningsGUI()
- {
- DoHeading("Optional Warnings");
- EditorGUILayout.BeginHorizontal();
- using (var label = PooledGUIContent.Acquire("Disabled Warnings"))
- {
- EditorGUI.BeginChangeCheck();
- var value = EditorGUILayout.EnumFlagsField(label, Validate.PermanentlyDisabledWarnings);
- if (EditorGUI.EndChangeCheck())
- Validate.PermanentlyDisabledWarnings = (OptionalWarning)value;
- }
- if (GUILayout.Button("Help", EditorStyles.miniButton, AnimancerGUI.DontExpandWidth))
- Application.OpenURL(Strings.DocsURLs.OptionalWarning);
- EditorGUILayout.EndHorizontal();
- }
- /************************************************************************************************************************/
- private static GUIStyle _HeadingStyle;
- /// <summary>Draws a heading label.</summary>
- public static void DoHeading(string text)
- => GUILayout.Label(text, _HeadingStyle ??= new(EditorStyles.largeLabel)
- {
- fontSize = 18,
- });
- /************************************************************************************************************************/
- /// <summary>Creates the Project Settings page.</summary>
- [SettingsProvider]
- public static SettingsProvider CreateSettingsProvider()
- {
- UnityEditor.Editor editor = null;
- return new("Project/" + Strings.ProductName, SettingsScope.Project)
- {
- keywords = new HashSet<string>() { Strings.ProductName },
- guiHandler = searchContext =>
- {
- if (editor == null)
- editor = CreateEditor(Instance);
- HideNextInfo = true;
- editor.OnInspectorGUI();
- },
- };
- }
- /************************************************************************************************************************/
- }
- /************************************************************************************************************************/
- }
- }
- #endif
|