TransitionLibraryWindow.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using Animancer.TransitionLibraries;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using UnityEditor;
  8. using UnityEditor.Callbacks;
  9. using UnityEngine;
  10. using static Animancer.Editor.AnimancerGUI;
  11. using Object = UnityEngine.Object;
  12. namespace Animancer.Editor.TransitionLibraries
  13. {
  14. /// <summary>[Editor-Only]
  15. /// An <see cref="EditorWindow"/> for configuring <see cref="TransitionLibraryAsset"/>.
  16. /// </summary>
  17. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibraryWindow
  18. public class TransitionLibraryWindow :
  19. SerializedDataEditorWindow<TransitionLibraryAsset, TransitionLibraryDefinition>
  20. {
  21. /************************************************************************************************************************/
  22. /// <summary>Opens a window for the `library`.</summary>
  23. public static TransitionLibraryWindow Open(TransitionLibraryAsset library)
  24. => Open<TransitionLibraryWindow>(library, true, typeof(SceneView));
  25. /************************************************************************************************************************/
  26. /// <summary>
  27. /// Double clicking a <see cref="TransitionLibraryAsset"/>
  28. /// opens it in the <see cref="TransitionLibraryWindow"/>.
  29. /// </summary>
  30. [OnOpenAsset]
  31. private static bool OnOpenAsset(int instanceID, int line)
  32. {
  33. var library = EditorUtility.InstanceIDToObject(instanceID) as TransitionLibraryAsset;
  34. if (library == null)
  35. return false;
  36. Open(library);
  37. return true;
  38. }
  39. /************************************************************************************************************************/
  40. /// <summary>The current window instance.</summary>
  41. public static TransitionLibraryWindow Instance { get; private set; }
  42. /// <summary>Is a window currently showing the `library`.</summary>
  43. public static bool IsShowing(Object library)
  44. => Instance != null
  45. && Instance.SourceObject == library;
  46. /************************************************************************************************************************/
  47. /// <inheritdoc/>
  48. public override TransitionLibraryDefinition SourceData
  49. {
  50. get => SourceObject.Definition;
  51. set => SourceObject.Definition = value;
  52. }
  53. /************************************************************************************************************************/
  54. [SerializeField]
  55. private TransitionLibrarySelection _Selection;
  56. /// <summary>Manages the objects which can be selected within a library.</summary>
  57. public TransitionLibrarySelection Selection
  58. => AnimancerEditorUtilities.FindOrCreate(ref _Selection);
  59. /************************************************************************************************************************/
  60. [SerializeReference]
  61. private List<TransitionLibraryWindowPage> _Pages;
  62. [SerializeField]
  63. private int _CurrentPage;
  64. /// <summary>The currently selected page.</summary>
  65. public TransitionLibraryWindowPage CurrentPage
  66. {
  67. get
  68. {
  69. _CurrentPage = Mathf.Clamp(_CurrentPage, 0, _Pages.Count - 1);
  70. return _Pages[_CurrentPage];
  71. }
  72. }
  73. /************************************************************************************************************************/
  74. /// <summary>Object highlight manager.</summary>
  75. public readonly TransitionLibraryWindowHighlighter
  76. Highlighter = new();
  77. /************************************************************************************************************************/
  78. /// <summary>Called when an object is selected.</summary>
  79. private void OnSelectionChange()
  80. {
  81. if (_Selection != null)
  82. _Selection.OnSelectionChange();
  83. var library = UnityEditor.Selection.activeObject as TransitionLibraryAsset;
  84. if (library != null && library != SourceObject)
  85. SetAndCaptureSource(library);
  86. }
  87. /************************************************************************************************************************/
  88. /// <inheritdoc/>
  89. protected override void OnEnable()
  90. {
  91. base.OnEnable();
  92. Instance = this;
  93. wantsMouseMove = true;
  94. // MainStageView, CanvasGroup Icon, GridLayoutGroup Icon.
  95. titleContent = EditorGUIUtility.IconContent("CanvasGroup Icon");
  96. titleContent.text = "Transition Library";
  97. AnimancerEditorUtilities.InstantiateDerivedTypes(ref _Pages);
  98. for (int i = 0; i < _Pages.Count; i++)
  99. _Pages[i].Window = this;
  100. OnSelectionChange();
  101. }
  102. /************************************************************************************************************************/
  103. /// <inheritdoc/>
  104. protected override void OnDisable()
  105. {
  106. base.OnDisable();
  107. if (Instance == this)
  108. Instance = null;
  109. }
  110. /************************************************************************************************************************/
  111. /// <inheritdoc/>
  112. protected override void OnDestroy()
  113. {
  114. base.OnDestroy();
  115. DestroyImmediate(_Selection);
  116. }
  117. /************************************************************************************************************************/
  118. /// <summary>Draws the GUI of this window.</summary>
  119. protected virtual void OnGUI()
  120. {
  121. if (SourceObject == null)
  122. {
  123. GUILayout.Label("No Transition Library has been selected");
  124. return;
  125. }
  126. DoHeaderGUI();
  127. DoBodyGUI();
  128. }
  129. /************************************************************************************************************************/
  130. /// <inheritdoc/>
  131. protected override void CaptureData()
  132. {
  133. base.CaptureData();
  134. Data.SortAliases();
  135. }
  136. /************************************************************************************************************************/
  137. /// <inheritdoc/>
  138. public override void Apply()
  139. {
  140. base.Apply();
  141. for (int i = 0; i < Data.Transitions.Length; i++)
  142. {
  143. var transition = Data.Transitions[i];
  144. if (EditorUtility.IsPersistent(transition))
  145. continue;
  146. AssetDatabase.AddObjectToAsset(transition, SourceObject);
  147. }
  148. }
  149. /************************************************************************************************************************/
  150. private static ButtonGroupStyles _ApplyRevertStyles;
  151. /// <summary>Draws the header GUI.</summary>
  152. private void DoHeaderGUI()
  153. {
  154. if (_ApplyRevertStyles.left == null)
  155. _ApplyRevertStyles = new(
  156. EditorStyles.toolbarButton,
  157. EditorStyles.toolbarButton,
  158. EditorStyles.toolbarButton);
  159. GUILayout.BeginHorizontal();
  160. var style = EditorStyles.toolbar;
  161. var applyRevertWidth = CalculateApplyRevertWidth(_ApplyRevertStyles) - StandardSpacing - 1;
  162. var area = GUILayoutUtility.GetRect(position.width, style.fixedHeight);
  163. var currentEvent = Event.current;
  164. if (currentEvent.type == EventType.Repaint)
  165. style.Draw(area, false, false, false, false);
  166. var pageArea = StealFromLeft(ref area, PageSelectionWidth);
  167. var applyRevertArea = StealFromRight(ref area, applyRevertWidth);
  168. var pathArea = area;
  169. DoPageSelectionDropdown(pageArea);
  170. DoAssetPathButton(pathArea, currentEvent);
  171. DoApplyRevertGUI(applyRevertArea, _ApplyRevertStyles);
  172. GUILayout.EndHorizontal();
  173. }
  174. /************************************************************************************************************************/
  175. [NonSerialized]
  176. private float _PageSelectionWidth;
  177. private float PageSelectionWidth
  178. {
  179. get
  180. {
  181. if (_PageSelectionWidth == 0)
  182. {
  183. for (int i = 0; i < _Pages.Count; i++)
  184. {
  185. _PageSelectionWidth = Math.Max(
  186. _PageSelectionWidth,
  187. EditorStyles.toolbarDropDown.CalculateWidth(_Pages[i].DisplayName));
  188. }
  189. }
  190. return _PageSelectionWidth;
  191. }
  192. }
  193. /************************************************************************************************************************/
  194. /// <summary>Draws a dropdown button for selecting the <see cref="CurrentPage"/>.</summary>
  195. private void DoPageSelectionDropdown(Rect area)
  196. {
  197. using (var label = PooledGUIContent.Acquire(CurrentPage.DisplayName, CurrentPage.HelpTooltip))
  198. if (!EditorGUI.DropdownButton(area, label, FocusType.Passive, EditorStyles.toolbarDropDown))
  199. return;
  200. var menu = new GenericMenu();
  201. for (int i = 0; i < _Pages.Count; i++)
  202. {
  203. var index = i;
  204. var page = _Pages[index];
  205. menu.AddItem(
  206. new(page.DisplayName),
  207. _CurrentPage == index,
  208. () => _CurrentPage = index);
  209. }
  210. menu.AddSeparator("");
  211. menu.AddItem(
  212. new("Documentation"),
  213. false,
  214. () => Application.OpenURL(Strings.DocsURLs.TransitionLibraries));
  215. menu.ShowAsContext();
  216. }
  217. /************************************************************************************************************************/
  218. private static GUIStyle _AssetPathStyle;
  219. private readonly GUIContent AssetPath = new();
  220. /// <summary>Draws the asset path of the target library and selects it if clicked.</summary>
  221. private void DoAssetPathButton(Rect area, Event currentEvent)
  222. {
  223. _AssetPathStyle ??= new(EditorStyles.toolbarButton)
  224. {
  225. richText = true,
  226. alignment = TextAnchor.MiddleRight,
  227. fontStyle = FontStyle.Italic,
  228. fontSize = (int)(EditorStyles.toolbarButton.fontSize * 0.8f),
  229. };
  230. if (currentEvent.type == EventType.Repaint)
  231. {
  232. var assetPath = AssetDatabase.GetAssetPath(SourceObject);
  233. if (string.IsNullOrEmpty(assetPath))
  234. {
  235. AssetPath.text = "The target Transition Library isn't saved as an asset.";
  236. AssetPath.tooltip = null;
  237. }
  238. else if (AssetPath.tooltip != assetPath)
  239. {
  240. AssetPath.tooltip = assetPath;
  241. var directory = Path.GetDirectoryName(assetPath).Replace('\\', '/');
  242. var file = Path.GetFileNameWithoutExtension(assetPath);
  243. assetPath = $"{directory}/<b>{file}</b>";
  244. AssetPath.text = assetPath;
  245. }
  246. }
  247. if (GUI.Button(area, AssetPath, _AssetPathStyle))
  248. {
  249. if (Selection.Selected != (object)SourceObject)
  250. Selection.Select(this, SourceObject, -1, TransitionLibrarySelection.SelectionType.Library);
  251. else
  252. EditorGUIUtility.PingObject(SourceObject);
  253. }
  254. }
  255. /************************************************************************************************************************/
  256. /// <summary>Draws the <see cref="CurrentPage"/>.</summary>
  257. private void DoBodyGUI()
  258. {
  259. GUILayout.FlexibleSpace();
  260. var area = GUILayoutUtility.GetLastRect();
  261. area.width = position.width;
  262. EditorGUI.DrawRect(area, Grey(0.2f, 0.5f));
  263. if (_Pages.Count > 0)
  264. {
  265. Highlighter.BeginGUI(area);
  266. CurrentPage?.OnGUI(area);
  267. Highlighter.EndGUI(this);
  268. }
  269. }
  270. /************************************************************************************************************************/
  271. }
  272. }
  273. #endif