Tool.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using UnityEditor;
  7. using UnityEditor.AnimatedValues;
  8. using UnityEngine;
  9. using Object = UnityEngine.Object;
  10. namespace Animancer.Editor.Tools
  11. {
  12. partial class AnimancerToolsWindow
  13. {
  14. /// <summary>[Editor-Only] [Pro-Only] Base class for tools in the <see cref="AnimancerToolsWindow"/>.</summary>
  15. /// <remarks>
  16. /// <strong>Documentation:</strong>
  17. /// <see href="https://kybernetik.com.au/animancer/docs/manual/tools">
  18. /// Animancer Tools</see>
  19. /// </remarks>
  20. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Tools/Tool
  21. ///
  22. [Serializable]
  23. public abstract class Tool : IComparable<Tool>
  24. {
  25. /************************************************************************************************************************/
  26. private AnimBool _FullAnimator;
  27. private AnimBool _BodyAnimator;
  28. private int _Index;
  29. /************************************************************************************************************************/
  30. /// <summary>Is this tool currently visible?</summary>
  31. public bool IsVisible => Instance._CurrentTool == _Index || Instance._CurrentTool < 0;
  32. /************************************************************************************************************************/
  33. /// <summary>Is the body of this tool currently visible?</summary>
  34. public bool IsExpanded
  35. {
  36. get { return Instance._CurrentTool == _Index; }
  37. set
  38. {
  39. if (value)
  40. Instance._CurrentTool = _Index;
  41. else if (IsExpanded)
  42. Instance._CurrentTool = -1;
  43. }
  44. }
  45. /************************************************************************************************************************/
  46. /// <summary>Lower numbers display first.</summary>
  47. public abstract int DisplayOrder { get; }
  48. /// <summary>Compares the <see cref="DisplayOrder"/> to put lower numbers first.</summary>
  49. public int CompareTo(Tool other)
  50. => DisplayOrder.CompareTo(other.DisplayOrder);
  51. /************************************************************************************************************************/
  52. /// <summary>The display name of this tool.</summary>
  53. public abstract string Name { get; }
  54. /// <summary>The usage instructions to display at the top of this tool.</summary>
  55. public abstract string Instructions { get; }
  56. /// <summary>The URL for the help button in the header to open.</summary>
  57. public virtual string HelpURL => Strings.DocsURLs.AnimancerTools;
  58. /// <summary>Called whenever the <see cref="Selection"/> changes.</summary>
  59. public virtual void OnSelectionChanged() { }
  60. /************************************************************************************************************************/
  61. /// <summary>Called by <see cref="AnimancerToolsWindow.OnEnable"/>.</summary>
  62. public virtual void OnEnable(int index)
  63. {
  64. _Index = index;
  65. _FullAnimator = new(IsVisible);
  66. _BodyAnimator = new(IsExpanded);
  67. }
  68. /// <summary>Called by <see cref="AnimancerToolsWindow.OnDisable"/>.</summary>
  69. public virtual void OnDisable() { }
  70. /************************************************************************************************************************/
  71. /// <summary>Draws the GUI for this tool.</summary>
  72. public virtual void DoGUI()
  73. {
  74. var enabled = GUI.enabled;
  75. _FullAnimator.target = IsVisible;
  76. if (EditorGUILayout.BeginFadeGroup(_FullAnimator.faded))
  77. {
  78. GUILayout.BeginVertical(EditorStyles.helpBox);
  79. DoHeaderGUI();
  80. _BodyAnimator.target = IsExpanded;
  81. if (EditorGUILayout.BeginFadeGroup(_BodyAnimator.faded))
  82. {
  83. var instructions = Instructions;
  84. if (!string.IsNullOrEmpty(instructions))
  85. EditorGUILayout.HelpBox(instructions, MessageType.Info);
  86. DoBodyGUI();
  87. }
  88. EditorGUILayout.EndFadeGroup();
  89. GUILayout.EndVertical();
  90. }
  91. EditorGUILayout.EndFadeGroup();
  92. if (_FullAnimator.isAnimating || _BodyAnimator.isAnimating)
  93. Repaint();
  94. GUI.enabled = enabled;
  95. }
  96. /************************************************************************************************************************/
  97. /// <summary>
  98. /// Draws the Header GUI for this tool which is displayed regardless of whether it is expanded or not.
  99. /// </summary>
  100. public virtual void DoHeaderGUI()
  101. {
  102. var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.BeforeAndAfter);
  103. var click = GUI.Button(area, Name, EditorStyles.boldLabel);
  104. area.xMin = area.xMax - area.height;
  105. GUI.DrawTexture(area, HelpIcon);
  106. if (click)
  107. {
  108. if (area.Contains(Event.current.mousePosition))
  109. {
  110. Application.OpenURL(HelpURL);
  111. return;
  112. }
  113. else
  114. {
  115. IsExpanded = !IsExpanded;
  116. }
  117. }
  118. }
  119. /************************************************************************************************************************/
  120. /// <summary>Draws the Body GUI for this tool which is only displayed while it is expanded.</summary>
  121. public abstract void DoBodyGUI();
  122. /************************************************************************************************************************/
  123. /// <summary>Asks the user where they want to save a modified asset, calls `modify` on it, and saves it.</summary>
  124. public static bool SaveModifiedAsset<T>(string saveTitle, string saveMessage,
  125. T obj, Action<T> modify) where T : Object
  126. {
  127. var originalPath = AssetDatabase.GetAssetPath(obj);
  128. var extension = Path.GetExtension(originalPath);
  129. if (extension[0] == '.')
  130. extension = extension[1..];
  131. var directory = Path.GetDirectoryName(originalPath);
  132. var newName = Path.GetFileNameWithoutExtension(AssetDatabase.GenerateUniqueAssetPath(originalPath));
  133. var savePath = EditorUtility.SaveFilePanelInProject(saveTitle, newName, extension, saveMessage, directory);
  134. if (string.IsNullOrEmpty(savePath))
  135. return false;
  136. if (originalPath != savePath)
  137. {
  138. obj = Instantiate(obj);
  139. AssetDatabase.CreateAsset(obj, savePath);
  140. }
  141. modify(obj);
  142. AssetDatabase.SaveAssets();
  143. return true;
  144. }
  145. /************************************************************************************************************************/
  146. private static Texture _HelpIcon;
  147. /// <summary>The help icon image used in the tool header.</summary>
  148. public static Texture HelpIcon
  149. {
  150. get
  151. {
  152. if (_HelpIcon == null)
  153. _HelpIcon = AnimancerIcons.Load("_Help");
  154. return _HelpIcon;
  155. }
  156. }
  157. /************************************************************************************************************************/
  158. /// <summary>Adds any objects dropped in the `area` to the `list`.</summary>
  159. protected void HandleDragAndDropIntoList<T>(
  160. Rect area,
  161. IList<T> list,
  162. bool overwrite)
  163. where T : Object
  164. {
  165. var dropIndex = 0;
  166. // No easy way to avoid this closure.
  167. AnimancerGUI.Handle<T>((obj, isDrop) =>
  168. {
  169. if (!isDrop)
  170. return true;
  171. if (overwrite)
  172. {
  173. RecordUndo();
  174. if (dropIndex < list.Count)
  175. {
  176. list[dropIndex++] = obj;
  177. }
  178. else
  179. {
  180. list.Add(obj);
  181. }
  182. }
  183. else
  184. {
  185. list.Add(obj);
  186. }
  187. return true;
  188. }, area);
  189. }
  190. /************************************************************************************************************************/
  191. }
  192. }
  193. }
  194. #endif