TransitionLibrarySelectionPreview.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR && UNITY_IMGUI
  3. using Animancer.Editor.Previews;
  4. using Animancer.TransitionLibraries;
  5. using System;
  6. using UnityEditor;
  7. using UnityEngine;
  8. using static Animancer.Editor.AnimancerGUI;
  9. using Object = UnityEngine.Object;
  10. namespace Animancer.Editor.TransitionLibraries
  11. {
  12. /// <summary>[Editor-Only] Custom preview for <see cref="TransitionLibrarySelection"/>.</summary>
  13. /// <remarks>Parts of this class are based on Unity's <see cref="MeshPreview"/>.</remarks>
  14. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.TransitionLibraries/TransitionLibrarySelectionPreview
  15. [CustomPreview(typeof(TransitionLibrarySelection))]
  16. public class TransitionLibrarySelectionPreview : ObjectPreview
  17. {
  18. /************************************************************************************************************************/
  19. [SerializeField] private AnimancerPreviewRenderer _PreviewRenderer;
  20. [SerializeField] private TransitionPreviewPlayer _PreviewPlayer;
  21. [NonSerialized] private TransitionLibrarySelection _Target;
  22. [NonSerialized] private int _TargetVersion = -1;
  23. [NonSerialized] private readonly TransitionLibrarySelectionPreviewSpeed Speed = new();
  24. /************************************************************************************************************************/
  25. /// <inheritdoc/>
  26. public override void Initialize(Object[] targets)
  27. {
  28. _PreviewRenderer ??= new();
  29. _PreviewPlayer ??= new();
  30. if (targets.Length == 1)
  31. {
  32. _Target = targets[0] as TransitionLibrarySelection;
  33. if (_Target != null)
  34. {
  35. _TargetVersion = _Target.Version - 1;
  36. if (_Target.Window != null)
  37. _PreviewRenderer.PreviewObject.TrySelectBestModel(_Target.Window.Data);
  38. CheckTarget();
  39. }
  40. }
  41. base.Initialize(targets);
  42. }
  43. /************************************************************************************************************************/
  44. /// <inheritdoc/>
  45. public override void Cleanup()
  46. {
  47. base.Cleanup();
  48. _PreviewPlayer?.Dispose();
  49. _PreviewPlayer = null;
  50. _PreviewRenderer?.Dispose();
  51. _PreviewRenderer = null;
  52. }
  53. /************************************************************************************************************************/
  54. /// <summary>Handles changes to the target object.</summary>
  55. private void CheckTarget()
  56. {
  57. if (_TargetVersion == _Target.Version)
  58. return;
  59. _TargetVersion = _Target.Version;
  60. _PreviewPlayer.IsPlaying = false;
  61. switch (_Target.Type)
  62. {
  63. case TransitionLibrarySelection.SelectionType.FromTransition:
  64. _PreviewPlayer.FromTransition = _Target.FromTransition;
  65. _PreviewPlayer.ToTransition = null;
  66. break;
  67. case TransitionLibrarySelection.SelectionType.ToTransition:
  68. _PreviewPlayer.FromTransition = null;
  69. _PreviewPlayer.ToTransition = _Target.ToTransition;
  70. break;
  71. case TransitionLibrarySelection.SelectionType.Modifier:
  72. _PreviewPlayer.FromTransition = _Target.FromTransition;
  73. _PreviewPlayer.ToTransition = _Target.ToTransition;
  74. break;
  75. }
  76. }
  77. /************************************************************************************************************************/
  78. /// <summary>Updates the settings of the <see cref="TransitionPreviewPlayer"/>.</summary>
  79. private void UpdatePlayerSettings()
  80. {
  81. _PreviewPlayer.Graph = _PreviewRenderer.PreviewObject.Graph;
  82. _PreviewPlayer.FadeDuration = _Target.FadeDuration;
  83. _PreviewPlayer.Speed = Speed.Speed;
  84. _PreviewPlayer.RecalculateTimeBounds();
  85. }
  86. /************************************************************************************************************************/
  87. private static readonly GUIContent
  88. Title = new("Preview");
  89. /// <inheritdoc/>
  90. public override GUIContent GetPreviewTitle()
  91. => Title;
  92. /************************************************************************************************************************/
  93. /// <inheritdoc/>
  94. public override bool HasPreviewGUI()
  95. => _Target != null
  96. && _Target.Type switch
  97. {
  98. TransitionLibrarySelection.SelectionType.FromTransition or
  99. TransitionLibrarySelection.SelectionType.ToTransition or
  100. TransitionLibrarySelection.SelectionType.Modifier
  101. => true,
  102. _ => false,
  103. };
  104. /************************************************************************************************************************/
  105. #region Header Settings
  106. /************************************************************************************************************************/
  107. private static GUIStyle _ToolbarButtonStyle;
  108. /// <inheritdoc/>
  109. public override void OnPreviewSettings()
  110. {
  111. CheckTarget();
  112. _ToolbarButtonStyle ??= new(EditorStyles.toolbarButton)
  113. {
  114. padding = new(),
  115. };
  116. var area = GUILayoutUtility.GetRect(LineHeight * 1.5f, LineHeight);
  117. DoPlayPauseToggle(area, _ToolbarButtonStyle);
  118. area = GUILayoutUtility.GetRect(LineHeight * 2f, LineHeight);
  119. Speed.DoToggleGUI(area, _ToolbarButtonStyle);
  120. }
  121. /************************************************************************************************************************/
  122. /// <summary>Draws a toggle to play and pause the preview.</summary>
  123. private void DoPlayPauseToggle(Rect area, GUIStyle style)
  124. {
  125. if (TryUseClickEvent(area, 1) || TryUseClickEvent(area, 2))
  126. _PreviewPlayer.CurrentTime = _PreviewPlayer.MinTime;
  127. _PreviewPlayer.IsPlaying = AnimancerGUI.DoPlayPauseToggle(
  128. area,
  129. _PreviewPlayer.IsPlaying,
  130. style,
  131. "Left Click = Play/Pause\nRight Click = Reset Time");
  132. }
  133. /************************************************************************************************************************/
  134. #endregion
  135. /************************************************************************************************************************/
  136. /// <inheritdoc/>
  137. public override void OnInteractivePreviewGUI(Rect area, GUIStyle background)
  138. {
  139. if (_Target == null)
  140. return;
  141. CheckTarget();
  142. UpdatePlayerSettings();
  143. DoSettingsGUI(ref area);
  144. DoTimelineGUI(ref area);
  145. _PreviewRenderer.DoGUI(area, background);
  146. AnimancerPreviewObjectGUI.HandleDragAndDrop(area, _PreviewRenderer.PreviewObject);
  147. }
  148. /************************************************************************************************************************/
  149. /// <summary>Draws settings for modifying the preview.</summary>
  150. private void DoSettingsGUI(ref Rect area)
  151. {
  152. if (!Speed.IsOn)
  153. return;
  154. area.yMin += StandardSpacing;
  155. Speed.DoSpeedSlider(ref area, EditorStyles.toolbar);
  156. var preview = _PreviewRenderer.PreviewObject;
  157. var height = AnimancerPreviewObjectGUI.CalculateHeight(preview);
  158. var settingsArea = StealFromTop(ref area, height, StandardSpacing);
  159. settingsArea = settingsArea.Expand(-StandardSpacing, 0);
  160. GUI.Label(settingsArea, GUIContent.none, EditorStyles.toolbar);
  161. AnimancerPreviewObjectGUI.DoModelGUI(settingsArea, preview);
  162. }
  163. /************************************************************************************************************************/
  164. #region Timeline
  165. /************************************************************************************************************************/
  166. /// <summary>Draws the preview timeline.</summary>
  167. private void DoTimelineGUI(ref Rect area)
  168. {
  169. var timelineArea = StealFromTop(ref area, EditorStyles.toolbar.fixedHeight, StandardSpacing);
  170. EditorGUI.DrawRect(timelineArea, Grey(0.25f, 0.3f));
  171. EditorGUI.DrawRect(new(timelineArea.x, timelineArea.yMax - 1, timelineArea.width, 1), Grey(0, 0.5f));
  172. DoFadeDurationSliderGUI(timelineArea);
  173. DoTimeSliderGUI(timelineArea);
  174. }
  175. /************************************************************************************************************************/
  176. private static readonly int SliderHash = "Slider".GetHashCode();
  177. /************************************************************************************************************************/
  178. /// <summary>Draws the fade duration slider.</summary>
  179. private void DoFadeDurationSliderGUI(Rect area)
  180. {
  181. if (!CalculateFadeBounds(area, out var startFadeX, out var endFadeX))
  182. return;
  183. switch (_Target.Type)
  184. {
  185. default:
  186. return;
  187. case TransitionLibrarySelection.SelectionType.FromTransition:
  188. case TransitionLibrarySelection.SelectionType.ToTransition:
  189. case TransitionLibrarySelection.SelectionType.Modifier:
  190. break;
  191. }
  192. var sliderArea = area;
  193. sliderArea.width = LineHeight * 0.5f;
  194. sliderArea.x = endFadeX - sliderArea.width * 0.5f;
  195. var control = new GUIControl(sliderArea, SliderHash);
  196. switch (control.EventType)
  197. {
  198. case EventType.MouseDown:
  199. if (control.TryUseMouseDown())
  200. _PreviewPlayer.IsPlaying = false;
  201. break;
  202. case EventType.MouseUp:
  203. control.TryUseMouseUp();
  204. break;
  205. case EventType.MouseDrag:
  206. if (control.TryUseHotControl())
  207. {
  208. var x = Math.Max(startFadeX, control.Event.mousePosition.x);
  209. var normalizedTime = area.InverseLerpUnclampedX(x);
  210. var normalizedStartFade = area.InverseLerpUnclampedX(startFadeX);
  211. _PreviewPlayer.NormalizedTime = normalizedTime;
  212. var fadeDuration =
  213. _PreviewPlayer.LerpTimeUnclamped(normalizedTime) -
  214. _PreviewPlayer.LerpTimeUnclamped(normalizedStartFade);
  215. var selected = _Target.Selected;
  216. if (selected is TransitionModifierDefinition modifier)
  217. {
  218. _Target.Window.RecordUndo()
  219. .SetModifier(modifier.WithFadeDuration(fadeDuration));
  220. }
  221. else if (selected is TransitionAssetBase transitionAsset)
  222. {
  223. if (fadeDuration < 0)
  224. fadeDuration = 0;
  225. using var serializedObject = new SerializedObject(transitionAsset);
  226. var property = serializedObject.FindProperty(TransitionAssetBase.TransitionField);
  227. property = property.FindPropertyRelative("_" + nameof(ITransition.FadeDuration));
  228. property.floatValue = fadeDuration;
  229. serializedObject.ApplyModifiedProperties();
  230. }
  231. _Target.Window.Repaint();
  232. }
  233. break;
  234. case EventType.Repaint:
  235. var color = AnimancerStateDrawerColors.FadeLineColor;
  236. var showCursor = GUIUtility.hotControl == 0 || GUIUtility.hotControl == control.ID;
  237. if (showCursor)
  238. EditorGUIUtility.AddCursorRect(sliderArea, MouseCursor.ResizeHorizontal);
  239. if (!showCursor || !sliderArea.Contains(control.Event.mousePosition))
  240. color.a *= 0.5f;
  241. EditorGUI.DrawRect(
  242. new(endFadeX, sliderArea.y, 1, sliderArea.height - 1),
  243. color);
  244. break;
  245. }
  246. }
  247. /************************************************************************************************************************/
  248. /// <summary>Draws the preview time slider.</summary>
  249. private void DoTimeSliderGUI(Rect area)
  250. {
  251. var control = new GUIControl(area, SliderHash);
  252. switch (control.EventType)
  253. {
  254. case EventType.MouseDown:
  255. if (control.TryUseMouseDown())
  256. {
  257. _ForceClampTime = true;
  258. _DidWrapTime = false;
  259. HandleDragTime(area, control.Event);
  260. _ForceClampTime = control.Event.control;
  261. if (!_ForceClampTime)
  262. EditorGUIUtility.SetWantsMouseJumping(1);
  263. _PreviewPlayer.IsPlaying = control.Event.clickCount > 1;
  264. }
  265. break;
  266. case EventType.MouseUp:
  267. if (control.TryUseMouseUp())
  268. EditorGUIUtility.SetWantsMouseJumping(0);
  269. break;
  270. case EventType.MouseDrag:
  271. if (control.TryUseHotControl())
  272. HandleDragTime(area, control.Event);
  273. break;
  274. case EventType.Repaint:
  275. BeginTriangles(AnimancerStateDrawerColors.FadeLineColor);
  276. if (CalculateFadeBounds(area, out var startFadeX, out var endFadeX))
  277. {
  278. // Fade.
  279. DrawLineBatched(
  280. new(startFadeX, area.yMin + 1),
  281. new(endFadeX, area.yMax - 1),
  282. 1);
  283. // To.
  284. if (endFadeX < area.xMax)
  285. DrawLineBatched(
  286. new(endFadeX, area.yMax - 1),
  287. new(area.xMax, area.yMax - 1),
  288. 1);
  289. }
  290. // From.
  291. if (area.xMin < startFadeX)
  292. DrawLineBatched(
  293. new(area.xMin, area.yMin + 1),
  294. new(startFadeX, area.yMin + 1),
  295. 1);
  296. var color = _PreviewPlayer.IsPlaying
  297. ? AnimancerStateDrawerColors.PlayingBarColor
  298. : AnimancerStateDrawerColors.PausedBarColor;
  299. color.a = 1;
  300. var timeX = area.LerpUnclampedX(_PreviewPlayer.NormalizedTime);
  301. GL.Color(color);
  302. DrawLineBatched(new(timeX, area.yMin), new(timeX, area.yMax), 2);
  303. EndTriangles();
  304. DoTransitionLabels(area);
  305. break;
  306. }
  307. }
  308. /************************************************************************************************************************/
  309. private bool _ForceClampTime;
  310. private bool _DidWrapTime;
  311. /// <summary>Draws handles drag events to control the preview time.</summary>
  312. private void HandleDragTime(Rect area, Event currentEvent)
  313. {
  314. if (_ForceClampTime)
  315. {
  316. _PreviewPlayer.NormalizedTime = area.InverseLerpUnclampedX(currentEvent.mousePosition.x);
  317. return;
  318. }
  319. var delta = currentEvent.delta.x;
  320. var normalizedTime = _PreviewPlayer.NormalizedTime;
  321. if (normalizedTime == 0 && !_DidWrapTime && delta > 0)
  322. {
  323. var x = currentEvent.mousePosition.x;
  324. if (area.xMin > x || area.xMax < x)
  325. return;
  326. }
  327. normalizedTime += delta / area.width;
  328. if (normalizedTime >= 0 || _DidWrapTime)
  329. {
  330. if (normalizedTime > 1)
  331. _DidWrapTime = true;
  332. normalizedTime = AnimancerUtilities.Wrap01(normalizedTime);
  333. }
  334. else
  335. {
  336. normalizedTime = 0;
  337. }
  338. _PreviewPlayer.NormalizedTime = normalizedTime;
  339. }
  340. /************************************************************************************************************************/
  341. /// <summary>Calculates the start and end pixels of the fade.</summary>
  342. private bool CalculateFadeBounds(
  343. Rect area,
  344. out float startFadeX,
  345. out float endFadeX)
  346. {
  347. var fadeDuration = _Target.FadeDuration;
  348. if (!float.IsNaN(fadeDuration))
  349. {
  350. startFadeX = area.LerpUnclampedX(_PreviewPlayer.InverseLerpTimeUnclamped(0));
  351. endFadeX = area.LerpUnclampedX(_PreviewPlayer.InverseLerpTimeUnclamped(fadeDuration));
  352. if (_Target.FromTransition.IsValid())
  353. {
  354. if (!_Target.ToTransition.IsValid())
  355. {
  356. endFadeX -= startFadeX;
  357. startFadeX = area.xMin;
  358. }
  359. return true;
  360. }
  361. else
  362. {
  363. if (_Target.ToTransition.IsValid())
  364. {
  365. return true;
  366. }
  367. }
  368. }
  369. startFadeX = area.LerpUnclampedX(_PreviewPlayer.InverseLerpTimeUnclamped(0));
  370. endFadeX = startFadeX;
  371. return false;
  372. }
  373. /************************************************************************************************************************/
  374. /// <summary>Draws labels for the selected transitions.</summary>
  375. private void DoTransitionLabels(Rect area)
  376. {
  377. area.xMin += 1;
  378. area.xMax -= 2;
  379. var mid = area.width * 0.5f;
  380. var leftArea = area;
  381. var rightArea = area;
  382. var fromTransition = _Target.FromTransition;
  383. var toTransition = _Target.ToTransition;
  384. var hasFrom = fromTransition.IsValid();
  385. var hasTo = toTransition.IsValid();
  386. if (hasFrom && hasTo)
  387. {
  388. leftArea.width = mid - StandardSpacing * 0.5f;
  389. rightArea.x = area.xMax - leftArea.width;
  390. rightArea.width = leftArea.width;
  391. }
  392. if (hasFrom)
  393. GUI.Label(leftArea, _Target.FromTransition.GetCachedName());
  394. if (hasTo)
  395. GUI.Label(rightArea, _Target.ToTransition.GetCachedName(), RightLabelStyle);
  396. }
  397. /************************************************************************************************************************/
  398. #endregion
  399. /************************************************************************************************************************/
  400. }
  401. }
  402. #endif