TransitionModifierTableGUI.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using UnityEditor;
  5. using UnityEngine;
  6. using static Animancer.Editor.AnimancerGUI;
  7. using static Animancer.Editor.TransitionLibraries.TransitionLibrarySelection;
  8. namespace Animancer.Editor.TransitionLibraries
  9. {
  10. /// <summary>[Editor-Only]
  11. /// A <see cref="TableGUI"/> for editing
  12. /// <see cref="Animancer.TransitionLibraries.TransitionLibraryDefinition.Modifiers"/>.
  13. /// </summary>
  14. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionModifierTableGUI
  15. [Serializable]
  16. public class TransitionModifierTableGUI : TableGUI
  17. {
  18. /************************************************************************************************************************/
  19. [NonSerialized] private TransitionLibraryWindow _Window;
  20. [NonSerialized] private Vector2Int _SelectedCell;
  21. /************************************************************************************************************************/
  22. /// <summary>Creates a new <see cref="TransitionModifierTableGUI"/>.</summary>
  23. public TransitionModifierTableGUI()
  24. {
  25. base.DoCellGUI = DoCellGUI;
  26. CalculateWidestLabel = CalculateWidestTransitionLabel;
  27. MinCellSize = new(LineHeight * 2, LineHeight);
  28. MaxCellSize = new(LineHeight * 4, LineHeight);
  29. }
  30. /************************************************************************************************************************/
  31. /// <summary>Draws the table GUI.</summary>
  32. public void DoGUI(
  33. Rect area,
  34. TransitionLibraryWindow window)
  35. {
  36. _Window = window;
  37. _SelectedCell = RecalculateSelectedCell(window.Selection);
  38. var transitions = window.Data.Transitions;
  39. DoTableGUI(area, transitions.Length, transitions.Length);
  40. }
  41. /************************************************************************************************************************/
  42. /// <summary>Calculates the table coordinates of the `selection`.</summary>
  43. private Vector2Int RecalculateSelectedCell(TransitionLibrarySelection selection)
  44. {
  45. if (selection.Validate())
  46. {
  47. switch (selection.Type)
  48. {
  49. case SelectionType.FromTransition:
  50. case SelectionType.ToTransition:
  51. case SelectionType.Modifier:
  52. var cell = new Vector2Int(selection.ToIndex, selection.FromIndex);
  53. if (cell.x < 0)
  54. cell.x = int.MinValue;
  55. if (cell.y < 0)
  56. cell.y = int.MinValue;
  57. return cell;
  58. }
  59. }
  60. return new(int.MinValue, int.MinValue);
  61. }
  62. /************************************************************************************************************************/
  63. /// <summary>Draws a table cell.</summary>
  64. private new void DoCellGUI(Rect area, int column, int row)
  65. {
  66. var invertHover = false;
  67. if (column < 0)
  68. {
  69. if (row < 0)
  70. DoCornerGUI(area);
  71. else
  72. DoLabelGUI(
  73. area,
  74. row,
  75. RightLabelStyle,
  76. SelectionType.FromTransition);
  77. }
  78. else if (row < 0)
  79. {
  80. DoLabelGUI(
  81. area,
  82. column,
  83. EditorStyles.label,
  84. SelectionType.ToTransition);
  85. invertHover = true;
  86. }
  87. else
  88. {
  89. DoFadeDurationGUI(area, _Window, row, column, "");
  90. }
  91. DrawHighlightGUI(area, column, row, invertHover);
  92. }
  93. /************************************************************************************************************************/
  94. /// <summary>Draws the header corner.</summary>
  95. private void DoCornerGUI(Rect area)
  96. {
  97. area.xMin += StandardSpacing;
  98. var fromArea = area;
  99. fromArea.y += area.height - LineHeight;
  100. fromArea.height = LineHeight;
  101. var toArea = fromArea;
  102. toArea.y -= toArea.height - Padding;
  103. var removeArea = toArea;
  104. removeArea.y -= removeArea.height - Padding;
  105. var createArea = removeArea;
  106. createArea.y -= createArea.height - Padding;
  107. fromArea.width -= VerticalScrollBar.fixedWidth + Padding;
  108. var style = RightLabelStyle;
  109. var fontStyle = style.fontStyle;
  110. style.fontStyle = FontStyle.Bold;
  111. GUI.Label(fromArea, "From", style);
  112. GUI.Label(toArea, "To", style);
  113. style.fontStyle = fontStyle;
  114. DoCreateButtonGUI(createArea);
  115. DoDeleteButtonGUI(removeArea);
  116. }
  117. /************************************************************************************************************************/
  118. /// <summary>Draws a button to create a new transition.</summary>
  119. private void DoCreateButtonGUI(Rect area)
  120. {
  121. if (GUI.Button(area, "Create Transition"))
  122. TransitionLibraryOperations.CreateTransition(_Window);
  123. }
  124. /************************************************************************************************************************/
  125. /// <summary>Draws a button to remove the selected transition.</summary>
  126. private void DoDeleteButtonGUI(Rect area)
  127. {
  128. TransitionAssetBase transition = null;
  129. int index = -1;
  130. var selection = _Window.Selection;
  131. switch (selection.Type)
  132. {
  133. case SelectionType.FromTransition:
  134. transition = selection.FromTransition;
  135. index = selection.FromIndex;
  136. break;
  137. case SelectionType.ToTransition:
  138. transition = selection.ToTransition;
  139. index = selection.ToIndex;
  140. break;
  141. }
  142. using (new EditorGUI.DisabledScope(index < 0 || index >= _Window.Data.Transitions.Length))
  143. if (GUI.Button(area, "Remove Transition"))
  144. TransitionLibraryOperations.AskHowToDeleteTransition(transition, index, _Window);
  145. }
  146. /************************************************************************************************************************/
  147. /// <summary>Draws a row or column label.</summary>
  148. private void DoLabelGUI(
  149. Rect area,
  150. int index,
  151. GUIStyle style,
  152. SelectionType selectionType)
  153. {
  154. if (!_Window.Data.Transitions.TryGet(index, out var transition))
  155. return;
  156. HandleTransitionLabelInput(
  157. ref area,
  158. _Window,
  159. transition,
  160. index,
  161. selectionType,
  162. CalculateTargetTransitionIndex);
  163. GUI.Label(area, GetTransitionName(transition), style);
  164. }
  165. /************************************************************************************************************************/
  166. /// <summary>Returns the name of the `transition` with a special message for <c>null</c>.</summary>
  167. public static string GetTransitionName(TransitionAssetBase transition)
  168. => transition != null
  169. ? transition.GetCachedName()
  170. : "<Missing Transition>";
  171. /************************************************************************************************************************/
  172. private static readonly int LabelHint = "Label".GetHashCode();
  173. [NonSerialized] private static bool _IsLabelDrag;
  174. /// <summary>Handles input events on transition labels.</summary>
  175. public static void HandleTransitionLabelInput(
  176. ref Rect area,
  177. TransitionLibraryWindow window,
  178. TransitionAssetBase transition,
  179. int index,
  180. SelectionType selectionType,
  181. Func<Rect, int, Event, int> calculateTargetTransitionIndex)
  182. {
  183. var control = new GUIControl(area, LabelHint);
  184. switch (control.EventType)
  185. {
  186. case EventType.MouseDown:
  187. if (control.Event.button == 0 &&
  188. control.TryUseMouseDown())
  189. {
  190. if (control.Event.clickCount == 2)
  191. EditorGUIUtility.PingObject(transition);
  192. else
  193. window.Selection.Select(window, transition, index, selectionType);
  194. _IsLabelDrag = false;
  195. }
  196. break;
  197. case EventType.MouseUp:
  198. if (control.TryUseMouseUp() && _IsLabelDrag)
  199. {
  200. var target = calculateTargetTransitionIndex(area, index, control.Event);
  201. TransitionLibrarySort.MoveTransition(window, index, target);
  202. window.Selection.Select(window, transition, index, selectionType);
  203. }
  204. break;
  205. case EventType.MouseDrag:
  206. if (control.TryUseHotControl())
  207. _IsLabelDrag = true;
  208. break;
  209. }
  210. if (GUIUtility.hotControl == control.ID && _IsLabelDrag)
  211. {
  212. RepaintEverything();
  213. area.y = control.Event.mousePosition.y - area.height * 0.5f;
  214. }
  215. }
  216. /************************************************************************************************************************/
  217. /// <summary>Calculates the transition index for a drag and drop operation.</summary>
  218. private static int CalculateTargetTransitionIndex(
  219. Rect area,
  220. int index,
  221. Event currentEvent)
  222. {
  223. var distance = currentEvent.mousePosition.y - area.y;
  224. var offset = Mathf.FloorToInt(distance / area.height);
  225. return index + offset;
  226. }
  227. /************************************************************************************************************************/
  228. private static GUIStyle _FadeDurationStyle;
  229. /// <summary>Draws the fade duration for a particular transition combination.</summary>
  230. public static void DoFadeDurationGUI(
  231. Rect area,
  232. TransitionLibraryWindow window,
  233. int from,
  234. int to,
  235. string label)
  236. {
  237. _FadeDurationStyle ??= new(EditorStyles.numberField)
  238. {
  239. alignment = TextAnchor.MiddleLeft,
  240. };
  241. var previousHotControl = GUIUtility.hotControl;
  242. var hasModifier = window.Data.TryGetModifier(from, to, out var modifier);
  243. var labelStyle = EditorStyles.label.fontStyle;
  244. try
  245. {
  246. if (hasModifier)
  247. {
  248. EditorStyles.label.fontStyle = FontStyle.Bold;
  249. _FadeDurationStyle.fontStyle = FontStyle.Bold;
  250. _FadeDurationStyle.fontSize = EditorStyles.numberField.fontSize;
  251. }
  252. else
  253. {
  254. _FadeDurationStyle.fontStyle = FontStyle.Normal;
  255. _FadeDurationStyle.fontSize = EditorStyles.numberField.fontSize * 4 / 5;
  256. }
  257. EditorGUI.BeginChangeCheck();
  258. // This is basically a float field,
  259. // but anything that fails to parse will clear the field instead of setting it to 0.
  260. var text = modifier.FadeDuration.ToStringCached();
  261. text = EditorGUI.TextField(area, label, text, _FadeDurationStyle);
  262. if (EditorGUI.EndChangeCheck())
  263. {
  264. if (!float.TryParse(text, out var fadeDuration))
  265. fadeDuration = float.NaN;
  266. window.RecordUndo()
  267. .SetModifier(modifier.WithFadeDuration(fadeDuration));
  268. hasModifier = true;
  269. RepaintEverything();
  270. }
  271. }
  272. finally
  273. {
  274. EditorStyles.label.fontStyle = labelStyle;
  275. }
  276. if (previousHotControl != GUIUtility.hotControl)
  277. {
  278. window.Selection.Select(
  279. window,
  280. modifier,
  281. modifier.FromIndex,
  282. SelectionType.Modifier);
  283. }
  284. }
  285. /************************************************************************************************************************/
  286. /// <summary>Draws the selection and hover highlights for a particular cell.</summary>
  287. private void DrawHighlightGUI(Rect area, int column, int row, bool invertHover)
  288. {
  289. if (_Window.Highlighter.EventType != EventType.Repaint)
  290. return;
  291. var selected =
  292. _SelectedCell.x == column ||
  293. _SelectedCell.y == row;
  294. var hover = false;
  295. if (_Window.Highlighter.IsMouseOver)
  296. {
  297. if (invertHover)
  298. (row, column) = (column, row);
  299. var mousePosition = Event.current.mousePosition;
  300. if ((column >= 0 && IsInlineWithX(area, mousePosition.x)) ||
  301. (row >= 0 && IsInlineWithY(area, mousePosition.y)))
  302. {
  303. hover = true;
  304. }
  305. }
  306. _Window.Highlighter.DrawHighlightGUI(area, selected, hover);
  307. }
  308. /************************************************************************************************************************/
  309. /// <summary>Is `x` inside the `area`.</summary>
  310. private static bool IsInlineWithX(Rect area, float x)
  311. => area.xMin <= x
  312. && area.xMax > x;
  313. /// <summary>Is `y` inside the `area`.</summary>
  314. private static bool IsInlineWithY(Rect area, float y)
  315. => area.yMin <= y
  316. && area.yMax > y;
  317. /************************************************************************************************************************/
  318. /// <summary>Calculates the largest width of all transition labels.</summary>
  319. private float CalculateWidestTransitionLabel()
  320. {
  321. var widest = LineHeight * 2;
  322. var transitions = _Window.Data.Transitions;
  323. for (int i = 0; i < transitions.Length; i++)
  324. {
  325. var transition = transitions[i];
  326. if (transition == null)
  327. continue;
  328. var label = transition.GetCachedName();
  329. var width = CalculateLabelWidth(label);
  330. if (widest < width)
  331. widest = width;
  332. }
  333. return widest;
  334. }
  335. /************************************************************************************************************************/
  336. }
  337. }
  338. #endif