SerializableEventSequenceDrawer.cs 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR && UNITY_IMGUI
  3. #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
  4. using Animancer.Editor.Previews;
  5. using Animancer.Units;
  6. using Animancer.Units.Editor;
  7. using System;
  8. using System.Collections.Generic;
  9. using UnityEditor;
  10. using UnityEditor.AnimatedValues;
  11. using UnityEngine;
  12. using UnityEngine.Events;
  13. using static Animancer.Editor.AnimancerGUI;
  14. using Object = UnityEngine.Object;
  15. using SerializableSequence = Animancer.AnimancerEvent.Sequence.Serializable;
  16. namespace Animancer.Editor
  17. {
  18. /// <summary>[Editor-Only] Draws the Inspector GUI for a <see cref="SerializableSequence"/>.</summary>
  19. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/SerializableEventSequenceDrawer
  20. [CustomPropertyDrawer(typeof(SerializableSequence), true)]
  21. public class SerializableEventSequenceDrawer : PropertyDrawer
  22. {
  23. /************************************************************************************************************************/
  24. /// <summary><see cref="RepaintEverything"/></summary>
  25. public static UnityAction Repaint = RepaintEverything;
  26. private readonly Dictionary<string, List<AnimBool>>
  27. EventVisibility = new();
  28. private AnimBool GetVisibility(Context context, int index)
  29. {
  30. var path = context.Property.propertyPath;
  31. if (!EventVisibility.TryGetValue(path, out var list))
  32. EventVisibility.Add(path, list = new());
  33. while (list.Count <= index)
  34. {
  35. var visible = context.Property.isExpanded || context.SelectedEvent == index;
  36. list.Add(new(visible, Repaint));
  37. }
  38. return list[index];
  39. }
  40. /************************************************************************************************************************/
  41. /// <summary>
  42. /// Calculates the number of vertical pixels the `property` will occupy when it is drawn.
  43. /// </summary>
  44. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
  45. {
  46. if (property.hasMultipleDifferentValues)
  47. return LineHeight;
  48. using var context = Context.Get(property);
  49. var height = LineHeight;
  50. var count = Math.Max(1, context.Times.Count);
  51. for (int i = 0; i < count; i++)
  52. {
  53. height += CalculateEventHeight(context, i) * GetVisibility(context, i).faded;
  54. }
  55. var events = context.Sequence?.InitializedEvents;
  56. if (events != null)
  57. height += EventSequenceDrawer.Get(events).CalculateHeight(events) + StandardSpacing;
  58. return height;
  59. }
  60. /************************************************************************************************************************/
  61. private float CalculateEventHeight(Context context, int index)
  62. {
  63. // Name.
  64. var height = index < context.Times.Count - 1
  65. ? LineHeight + StandardSpacing
  66. : 0;// End Events don't have a Name.
  67. // Time.
  68. height += AnimationTimeAttributeDrawer.GetPropertyHeight(null, null) + StandardSpacing;
  69. // Callback.
  70. if (!SerializableEventSequenceDrawerSettings.HideEventCallbacks || context.Callbacks.Count > 0)
  71. {
  72. height += index < context.Callbacks.Count
  73. ? EditorGUI.GetPropertyHeight(context.Callbacks.GetElement(index), null, false)
  74. : DummyInvokableDrawer.Height;
  75. height += StandardSpacing;
  76. }
  77. return height;
  78. }
  79. /************************************************************************************************************************/
  80. /// <summary>Draws the GUI for the `property`.</summary>
  81. public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
  82. {
  83. var warnings = OptionalWarning.ProOnly.DisableTemporarily();
  84. using var context = Context.Get(property);
  85. DoHeaderGUI(ref area, label, context);
  86. if (property.hasMultipleDifferentValues)
  87. return;
  88. EditorGUI.indentLevel++;
  89. DoAllEventsGUI(ref area, context);
  90. EditorGUI.indentLevel--;
  91. var sequence = context.Sequence?.InitializedEvents;
  92. if (sequence != null)
  93. {
  94. using (var content = PooledGUIContent.Acquire("Runtime Events",
  95. $"The runtime {nameof(AnimancerEvent)}.{nameof(AnimancerEvent.Sequence)}" +
  96. $" created from the serialized data above"))
  97. {
  98. EventSequenceDrawer.Get(sequence).DoGUI(ref area, sequence, content);
  99. }
  100. }
  101. warnings.Enable();
  102. }
  103. /************************************************************************************************************************/
  104. private void DoHeaderGUI(ref Rect area, GUIContent label, Context context)
  105. {
  106. if (!EditorGUIUtility.hierarchyMode)
  107. EditorGUI.indentLevel--;
  108. area.height = LineHeight;
  109. var headerArea = area;
  110. NextVerticalArea(ref area);
  111. label = EditorGUI.BeginProperty(headerArea, label, context.Property);
  112. if (!context.Property.hasMultipleDifferentValues)
  113. {
  114. var addEventArea = StealFromRight(ref headerArea, headerArea.height, StandardSpacing);
  115. DoAddRemoveEventButtonGUI(addEventArea, context);
  116. }
  117. if (context.TransitionContext.Transition != null)
  118. {
  119. EditorGUI.EndProperty();
  120. TimelineGUI.DoGUI(headerArea, context, out var addEventNormalizedTime);
  121. if (!float.IsNaN(addEventNormalizedTime))
  122. {
  123. AddEvent(context, addEventNormalizedTime);
  124. }
  125. }
  126. else
  127. {
  128. string summary;
  129. if (context.Times.Count == 0)
  130. {
  131. summary = "[0] End Time 1";
  132. }
  133. else
  134. {
  135. var index = context.Times.Count - 1;
  136. var endTime = context.Times.GetElement(index).floatValue;
  137. summary = $"[{index}] End Time {endTime:G3}";
  138. }
  139. using (var content = PooledGUIContent.Acquire(summary))
  140. EditorGUI.LabelField(headerArea, label, content);
  141. EditorGUI.EndProperty();
  142. }
  143. EditorGUI.BeginChangeCheck();
  144. context.Property.isExpanded =
  145. EditorGUI.Foldout(headerArea, context.Property.isExpanded, GUIContent.none, true);
  146. if (EditorGUI.EndChangeCheck())
  147. context.SelectedEvent = -1;
  148. if (!EditorGUIUtility.hierarchyMode)
  149. EditorGUI.indentLevel++;
  150. }
  151. /************************************************************************************************************************/
  152. private static readonly int EventTimeHash = "EventTime".GetHashCode();
  153. private static int _HotControlAdjustRoot;
  154. private static int _SelectedEventToHotControl;
  155. private void DoAllEventsGUI(ref Rect area, Context context)
  156. {
  157. var currentEvent = Event.current;
  158. var originalEventType = currentEvent.type;
  159. if (originalEventType == EventType.Used)
  160. return;
  161. var rootControlID = GUIUtility.GetControlID(EventTimeHash - 1, FocusType.Passive);
  162. var eventCount = Mathf.Max(1, context.Times.Count);
  163. for (int i = 0; i < eventCount; i++)
  164. {
  165. var controlID = GUIUtility.GetControlID(EventTimeHash + i, FocusType.Passive);
  166. if (rootControlID == _HotControlAdjustRoot &&
  167. _SelectedEventToHotControl > 0 &&
  168. i == context.SelectedEvent)
  169. {
  170. GUIUtility.hotControl = GUIUtility.keyboardControl = controlID + _SelectedEventToHotControl;
  171. _SelectedEventToHotControl = 0;
  172. _HotControlAdjustRoot = -1;
  173. }
  174. DoEventGUI(ref area, context, i, false);
  175. if (currentEvent.type == EventType.Used && originalEventType == EventType.MouseUp)
  176. {
  177. context.SelectedEvent = i;
  178. if (SortEvents(context))
  179. {
  180. _SelectedEventToHotControl = GUIUtility.keyboardControl - controlID;
  181. _HotControlAdjustRoot = rootControlID;
  182. Deselect();
  183. }
  184. GUIUtility.ExitGUI();
  185. }
  186. }
  187. }
  188. /************************************************************************************************************************/
  189. /// <summary>Draws the GUI fields for the event at the specified `index`.</summary>
  190. public void DoEventGUI(ref Rect area, Context context, int index, bool autoSort)
  191. {
  192. GetEventLabels(
  193. index,
  194. context,
  195. out var nameLabel,
  196. out var timeLabel,
  197. out var callbackLabel,
  198. out var defaultTime,
  199. out var isEndEvent);
  200. var y = area.y;
  201. var visibility = GetVisibility(context, index);
  202. visibility.target = context.Property.isExpanded || context.SelectedEvent == index;
  203. var x = area.xMin;
  204. area.xMin = 0;
  205. area.height = CalculateEventHeight(context, index) * visibility.faded;
  206. var offset = GuiOffset;
  207. GuiOffset += area.position;
  208. TypeSelectionButton.BeginDelayingLinkLines();
  209. try
  210. {
  211. GUI.BeginGroup(area, GUIStyle.none);
  212. if (visibility.faded > 0)
  213. {
  214. area.xMin = x;
  215. area.y = 0;
  216. DoNameGUI(ref area, context, index, nameLabel);
  217. DoTimeGUI(ref area, context, index, autoSort, timeLabel, defaultTime, isEndEvent);
  218. DoCallbackGUI(ref area, context, index, callbackLabel);
  219. area.y = area.y * visibility.faded + y;
  220. area.height *= visibility.faded;
  221. }
  222. GUI.EndGroup();
  223. }
  224. finally
  225. {
  226. GuiOffset = offset;
  227. TypeSelectionButton.EndDelayingLinkLines();
  228. }
  229. area.xMin = x;
  230. }
  231. /************************************************************************************************************************/
  232. /// <summary>Draws the time field for the event at the specified `index`.</summary>
  233. public static void DoNameGUI(
  234. ref Rect area,
  235. Context context,
  236. int index,
  237. string nameLabel)
  238. {
  239. if (nameLabel == null)
  240. return;
  241. EditorGUI.BeginChangeCheck();
  242. area.height = LineHeight;
  243. var fieldArea = area;
  244. NextVerticalArea(ref area);
  245. using (var label = PooledGUIContent.Acquire(nameLabel,
  246. "An optional name which can be used to identify the event in code." +
  247. " Leaving all names blank is recommended if you aren't using them."))
  248. {
  249. fieldArea = EditorGUI.PrefixLabel(fieldArea, label);
  250. }
  251. var indentLevel = EditorGUI.indentLevel;
  252. EditorGUI.indentLevel = 0;
  253. var nameProperty = index < context.Names.Count
  254. ? context.Names.GetElement(index)
  255. : null;
  256. var name = nameProperty?.objectReferenceValue;
  257. DoNameWarningGUI(ref fieldArea, context, name);
  258. var exitGUI = false;
  259. if (nameProperty != null)
  260. {
  261. EditorGUI.PropertyField(fieldArea, nameProperty, GUIContent.none);
  262. }
  263. else
  264. {
  265. EditorGUI.BeginProperty(fieldArea, GUIContent.none, context.Names.Property);
  266. EditorGUI.BeginChangeCheck();
  267. name = StringAssetDrawer.DrawGUI(fieldArea, GUIContent.none, null, out exitGUI);
  268. if (EditorGUI.EndChangeCheck() && name != null)
  269. {
  270. // Expand up to the new name.
  271. // If we need to expand more than one slot, make sure all the new ones are null.
  272. context.Names.Count++;
  273. if (context.Names.Count < index + 1)
  274. {
  275. var nextProperty = context.Names.GetElement(context.Names.Count - 1);
  276. nextProperty.objectReferenceValue = null;
  277. context.Names.Count = index + 1;
  278. }
  279. // Get and assign the new property.
  280. nameProperty = context.Names.GetElement(index);
  281. nameProperty.objectReferenceValue = name;
  282. }
  283. if (!exitGUI)
  284. EditorGUI.EndProperty();
  285. }
  286. EditorGUI.indentLevel = indentLevel;
  287. if (EditorGUI.EndChangeCheck())
  288. {
  289. var events = context.Sequence?.InitializedEvents;
  290. events?.SetName(index, name as StringAsset);
  291. }
  292. if (exitGUI)
  293. {
  294. context.Names.Property.serializedObject.ApplyModifiedProperties();
  295. GUIUtility.ExitGUI();
  296. }
  297. }
  298. /************************************************************************************************************************/
  299. private static void DoNameWarningGUI(ref Rect area, Context context, Object name)
  300. {
  301. var property = context.TransitionContext.Property;
  302. var attribute = AttributeCache<EventNamesAttribute>.FindAttribute(property);
  303. if (attribute == null || !attribute.HasNames)
  304. return;
  305. var icon = name == null || Array.IndexOf(attribute.Names, (StringReference)name.name) >= 0
  306. ? AnimancerIcons.Info
  307. : AnimancerIcons.Warning;
  308. var warningArea = StealFromLeft(ref area, area.height, StandardSpacing);
  309. var tooltip = attribute.NamesToString("Expected Names:");
  310. using (var content = PooledGUIContent.Acquire("", tooltip))
  311. {
  312. content.image = icon;
  313. GUI.Label(warningArea, content);
  314. content.image = null;
  315. }
  316. }
  317. /************************************************************************************************************************/
  318. private static readonly AnimationTimeAttributeDrawer
  319. AnimationTimeAttributeDrawer = new();
  320. static SerializableEventSequenceDrawer()
  321. => AnimationTimeAttributeDrawer.Initialize(
  322. new AnimationTimeAttribute(AnimationTimeAttribute.Units.Normalized));
  323. private static float _PreviousTime = float.NaN;
  324. /// <summary>Draws the time field for the event at the specified `index`.</summary>
  325. public static void DoTimeGUI(
  326. ref Rect area,
  327. Context context,
  328. int index,
  329. bool autoSort,
  330. string timeLabel,
  331. float defaultTime,
  332. bool isEndEvent)
  333. {
  334. EditorGUI.BeginChangeCheck();
  335. area.height = AnimationTimeAttributeDrawer.GetPropertyHeight(null, null);
  336. var timeArea = area;
  337. NextVerticalArea(ref area);
  338. float normalizedTime;
  339. using (var label = PooledGUIContent.Acquire(timeLabel,
  340. isEndEvent ? Strings.Tooltips.EndTime : Strings.Tooltips.CallbackTime))
  341. {
  342. var length = context.TransitionContext.Transition != null
  343. ? context.TransitionContext.MaximumDuration
  344. : float.NaN;
  345. if (index < context.Times.Count)
  346. {
  347. var timeProperty = context.Times.GetElement(index);
  348. if (timeProperty == null)// Multi-selection screwed up the property retrieval.
  349. {
  350. EditorGUI.BeginChangeCheck();
  351. var propertyLabel = EditorGUI.BeginProperty(timeArea, label, context.Times.Property);
  352. if (isEndEvent)
  353. AnimationTimeAttributeDrawer.NextDefaultValue = defaultTime;
  354. normalizedTime = float.NaN;
  355. AnimationTimeAttributeDrawer.OnGUI(timeArea, propertyLabel, ref normalizedTime);
  356. EditorGUI.EndProperty();
  357. if (EditorGUI.EndChangeCheck())
  358. {
  359. context.Times.Count = context.Times.Count;
  360. timeProperty = context.Times.GetElement(index);
  361. timeProperty.floatValue = normalizedTime;
  362. SyncEventTimeChange(context, index, normalizedTime);
  363. }
  364. }
  365. else// Event time property was correctly retrieved.
  366. {
  367. var wasEditingTextField = EditorGUIUtility.editingTextField;
  368. if (!wasEditingTextField)
  369. _PreviousTime = float.NaN;
  370. EditorGUI.BeginChangeCheck();
  371. var propertyLabel = EditorGUI.BeginProperty(timeArea, label, timeProperty);
  372. if (isEndEvent)
  373. AnimationTimeAttributeDrawer.NextDefaultValue = defaultTime;
  374. normalizedTime = timeProperty.floatValue;
  375. AnimationTimeAttributeDrawer.OnGUI(timeArea, propertyLabel, ref normalizedTime);
  376. EditorGUI.EndProperty();
  377. if (TryUseClickEvent(timeArea, 2))
  378. normalizedTime = float.NaN;
  379. var isEditingTextField = EditorGUIUtility.editingTextField;
  380. if (EditorGUI.EndChangeCheck() || (wasEditingTextField && !isEditingTextField))
  381. {
  382. if (float.IsNaN(normalizedTime))
  383. {
  384. RemoveEvent(context, index);
  385. Deselect();
  386. }
  387. else if (isEndEvent)
  388. {
  389. timeProperty.floatValue = normalizedTime;
  390. SyncEventTimeChange(context, index, normalizedTime);
  391. }
  392. else if (!autoSort && isEditingTextField)
  393. {
  394. _PreviousTime = normalizedTime;
  395. }
  396. else
  397. {
  398. if (!float.IsNaN(_PreviousTime))
  399. {
  400. if (Event.current.keyCode != KeyCode.Escape)
  401. {
  402. normalizedTime = _PreviousTime;
  403. Deselect();
  404. }
  405. _PreviousTime = float.NaN;
  406. }
  407. WrapEventTime(context, ref normalizedTime);
  408. timeProperty.floatValue = normalizedTime;
  409. SyncEventTimeChange(context, index, normalizedTime);
  410. if (autoSort)
  411. SortEvents(context);
  412. }
  413. GUI.changed = true;
  414. }
  415. }
  416. }
  417. else// Dummy End Event (when there are no event times).
  418. {
  419. AnimancerUtilities.Assert(index == 0, "Dummy end event index != 0");
  420. EditorGUI.BeginChangeCheck();
  421. EditorGUI.BeginProperty(timeArea, GUIContent.none, context.Times.Property);
  422. AnimationTimeAttributeDrawer.NextDefaultValue = defaultTime;
  423. normalizedTime = float.NaN;
  424. AnimationTimeAttributeDrawer.OnGUI(timeArea, label, ref normalizedTime);
  425. EditorGUI.EndProperty();
  426. if (EditorGUI.EndChangeCheck() && !float.IsNaN(normalizedTime))
  427. {
  428. context.Times.Count = 1;
  429. var timeProperty = context.Times.GetElement(0);
  430. timeProperty.floatValue = normalizedTime;
  431. SyncEventTimeChange(context, 0, normalizedTime);
  432. }
  433. }
  434. }
  435. if (EditorGUI.EndChangeCheck())
  436. {
  437. var eventType = Event.current.type;
  438. if (eventType == EventType.Layout)
  439. return;
  440. if (eventType == EventType.Used)
  441. {
  442. normalizedTime = UnitsAttributeDrawer.GetDisplayValue(normalizedTime, defaultTime);
  443. TransitionPreviewWindow.PreviewNormalizedTime = normalizedTime;
  444. }
  445. GUIUtility.ExitGUI();
  446. }
  447. }
  448. /// <summary>Draws the time field for the event at the specified `index`.</summary>
  449. public static void DoTimeGUI(ref Rect area, Context context, int index, bool autoSort)
  450. {
  451. GetEventLabels(
  452. index,
  453. context,
  454. out var _,
  455. out var timeLabel,
  456. out var _,
  457. out var defaultTime,
  458. out var isEndEvent);
  459. DoTimeGUI(
  460. ref area,
  461. context,
  462. index,
  463. autoSort,
  464. timeLabel,
  465. defaultTime,
  466. isEndEvent);
  467. }
  468. /************************************************************************************************************************/
  469. /// <summary>Updates the <see cref="SerializableSequence.Events"/> to accomodate a changed event time.</summary>
  470. public static void SyncEventTimeChange(Context context, int index, float normalizedTime)
  471. {
  472. var events = context.Sequence?.InitializedEvents;
  473. if (events == null)
  474. return;
  475. if (index == events.Count)// End Event.
  476. {
  477. events.NormalizedEndTime = normalizedTime;
  478. }
  479. else// Regular Event.
  480. {
  481. events.SetNormalizedTime(index, normalizedTime);
  482. }
  483. }
  484. /************************************************************************************************************************/
  485. /// <summary>Draws the GUI fields for the event at the specified `index`.</summary>
  486. public static void DoCallbackGUI(
  487. ref Rect area,
  488. Context context,
  489. int index,
  490. string callbackLabel)
  491. {
  492. if (SerializableEventSequenceDrawerSettings.HideEventCallbacks && context.Callbacks.Count == 0)
  493. return;
  494. EditorGUI.BeginChangeCheck();
  495. using (var label = PooledGUIContent.Acquire(callbackLabel))
  496. {
  497. if (index < context.Callbacks.Count)
  498. {
  499. var callback = context.Callbacks.GetElement(index);
  500. area.height = EditorGUI.GetPropertyHeight(callback, false);
  501. EditorGUI.PropertyField(area, callback, label, false);
  502. }
  503. else if (DummyInvokableDrawer.DoGUI(ref area, label, context.Callbacks.Property, out var callback))
  504. {
  505. try
  506. {
  507. SerializableSequence.DisableCompactArrays = true;
  508. if (index >= context.Times.Count)
  509. {
  510. context.Times.Property.InsertArrayElementAtIndex(index);
  511. context.Times.Count++;
  512. context.Times.GetElement(index).floatValue = float.NaN;
  513. context.Times.Property.serializedObject.ApplyModifiedProperties();
  514. }
  515. context.Callbacks.Property.ForEachTarget(callbacksProperty =>
  516. {
  517. var accessor = callbacksProperty.GetAccessor();
  518. var oldCallbacks = (Array)accessor.GetValue(callbacksProperty.serializedObject.targetObject);
  519. Array newCallbacks;
  520. if (oldCallbacks == null)
  521. {
  522. var elementType = accessor.GetFieldElementType(callbacksProperty);
  523. newCallbacks = Array.CreateInstance(elementType, 1);
  524. }
  525. else
  526. {
  527. var elementType = oldCallbacks.GetType().GetElementType();
  528. newCallbacks = Array.CreateInstance(elementType, index + 1);
  529. Array.Copy(oldCallbacks, newCallbacks, oldCallbacks.Length);
  530. }
  531. newCallbacks.SetValue(callback, index);
  532. accessor.SetValue(callbacksProperty, newCallbacks);
  533. });
  534. context.Callbacks.Property.OnPropertyChanged();
  535. context.Callbacks.Property.GetArrayElementAtIndex(index).isExpanded = true;
  536. context.Callbacks.Refresh();
  537. }
  538. finally
  539. {
  540. SerializableSequence.DisableCompactArrays = false;
  541. }
  542. }
  543. }
  544. if (EditorGUI.EndChangeCheck())
  545. {
  546. if (index < context.Callbacks.Count)
  547. {
  548. var events = context.Sequence?.InitializedEvents;
  549. if (events != null)
  550. {
  551. var animancerEvent = index < events.Count
  552. ? events[index]
  553. : events.EndEvent;
  554. if (AnimancerEvent.IsNullOrDummy(animancerEvent.callback))
  555. {
  556. context.Callbacks.Property.serializedObject.ApplyModifiedProperties();
  557. var property = context.Callbacks.GetElement(index);
  558. var callback = property.GetValue();
  559. var invoke = SerializableSequence.GetInvoke(callback as IInvokable);
  560. if (index < events.Count)
  561. events.SetCallback(index, invoke);
  562. else
  563. events.OnEnd = invoke;
  564. }
  565. }
  566. }
  567. }
  568. NextVerticalArea(ref area);
  569. }
  570. /************************************************************************************************************************/
  571. private static ConversionCache<int, string>
  572. _NameLabelCache,
  573. _TimeLabelCache,
  574. _CallbackLabelCache;
  575. private static void GetEventLabels(
  576. int index,
  577. Context context,
  578. out string nameLabel,
  579. out string timeLabel,
  580. out string callbackLabel,
  581. out float defaultTime,
  582. out bool isEndEvent)
  583. {
  584. if (index >= context.Times.Count - 1)
  585. {
  586. nameLabel = null;
  587. timeLabel = "End Time";
  588. callbackLabel = "End Callback";
  589. defaultTime = AnimancerEvent.Sequence.GetDefaultNormalizedEndTime(
  590. context.TransitionContext.Transition?.Speed ?? 1);
  591. isEndEvent = true;
  592. }
  593. else
  594. {
  595. if (_NameLabelCache == null)
  596. {
  597. _NameLabelCache = new((i) => $"Event {i} Name");
  598. _TimeLabelCache = new((i) => $"Event {i} Time");
  599. _CallbackLabelCache = new((i) => $"Event {i} Callback");
  600. }
  601. nameLabel = _NameLabelCache.Convert(index);
  602. timeLabel = _TimeLabelCache.Convert(index);
  603. callbackLabel = _CallbackLabelCache.Convert(index);
  604. defaultTime = 0;
  605. isEndEvent = false;
  606. }
  607. }
  608. /************************************************************************************************************************/
  609. private static void WrapEventTime(Context context, ref float normalizedTime)
  610. {
  611. var transition = context.TransitionContext.Transition;
  612. if (transition != null && transition.IsLooping)
  613. {
  614. if (normalizedTime == 0)
  615. return;
  616. else if (normalizedTime % 1 == 0)
  617. normalizedTime = AnimancerEvent.AlmostOne;
  618. else
  619. normalizedTime = AnimancerUtilities.Wrap01(normalizedTime);
  620. }
  621. }
  622. /************************************************************************************************************************/
  623. #region Event Modification
  624. /************************************************************************************************************************/
  625. private static GUIStyle _AddEventStyle;
  626. private static GUIContent _AddEventContent;
  627. /// <summary>Draws a button to add a new event or remove the selected one.</summary>
  628. public void DoAddRemoveEventButtonGUI(Rect area, Context context)
  629. {
  630. if (ShowAddButton(context))
  631. {
  632. AnimancerIcons.IconContent(ref _AddEventContent, "Animation.AddEvent", Strings.ProOnlyTag + "Add event");
  633. _AddEventStyle ??= new(EditorStyles.miniButton)
  634. {
  635. fixedHeight = 0,
  636. padding = new(-1, 1, 0, 0),
  637. };
  638. if (GUI.Button(area, _AddEventContent, _AddEventStyle))
  639. {
  640. // If the target is currently being previewed, add the event at the currently selected time.
  641. var state = TransitionPreviewWindow.GetCurrentState();
  642. var normalizedTime = state != null ? state.NormalizedTime : float.NaN;
  643. AddEvent(context, normalizedTime);
  644. }
  645. }
  646. else
  647. {
  648. if (GUI.Button(area, AnimancerIcons.ClearIcon("Remove selected event"), NoPaddingButtonStyle))
  649. {
  650. RemoveEvent(context, context.SelectedEvent);
  651. }
  652. }
  653. }
  654. /************************************************************************************************************************/
  655. private static bool ShowAddButton(Context context)
  656. {
  657. // Nothing selected = Add.
  658. if (context.SelectedEvent < 0)
  659. return true;
  660. // No times means no events exist = Add.
  661. if (context.Times.Count == 0)
  662. return true;
  663. // Regular event selected = Remove.
  664. if (context.SelectedEvent < context.Times.Count - 1)
  665. return false;
  666. // End has non-default time = Remove.
  667. if (!float.IsNaN(context.Times.GetElement(context.SelectedEvent).floatValue))
  668. return false;
  669. // End has non-empty callback = Remove.
  670. // If the end callback was empty, the array would have been compacted.
  671. if (context.Callbacks.Count == context.Times.Count)
  672. return false;
  673. // End has empty callback = Add.
  674. return true;
  675. }
  676. /************************************************************************************************************************/
  677. /// <summary>Adds an event to the sequence represented by the given `context`.</summary>
  678. public static void AddEvent(Context context, float normalizedTime)
  679. {
  680. // If the time is NaN, add it halfway between the last event and the end.
  681. if (context.Times.Count == 0)
  682. {
  683. // Having any events means we need the end time too.
  684. context.Times.Count = 2;
  685. context.Times.GetElement(1).floatValue = float.NaN;
  686. if (float.IsNaN(normalizedTime))
  687. normalizedTime = 0.5f;
  688. }
  689. else
  690. {
  691. context.Times.Property.InsertArrayElementAtIndex(context.Times.Count - 1);
  692. context.Times.Count++;
  693. if (float.IsNaN(normalizedTime))
  694. {
  695. var transition = context.TransitionContext.Transition;
  696. var previousTime = context.Times.Count >= 3
  697. ? context.Times.GetElement(context.Times.Count - 3).floatValue
  698. : AnimancerEvent.Sequence.GetDefaultNormalizedStartTime(transition.Speed);
  699. var endTime = context.Times.GetElement(context.Times.Count - 1).floatValue;
  700. if (float.IsNaN(endTime))
  701. endTime = AnimancerEvent.Sequence.GetDefaultNormalizedEndTime(transition.Speed);
  702. normalizedTime = previousTime < endTime
  703. ? (previousTime + endTime) * 0.5f
  704. : previousTime;
  705. }
  706. }
  707. WrapEventTime(context, ref normalizedTime);
  708. var newEvent = context.Times.Count - 2;
  709. context.Times.GetElement(newEvent).floatValue = normalizedTime;
  710. context.SelectedEvent = newEvent;
  711. if (context.Callbacks.Count > newEvent)
  712. {
  713. context.Callbacks.Property.InsertArrayElementAtIndex(newEvent);
  714. context.Callbacks.Property.serializedObject.ApplyModifiedProperties();
  715. // Make sure the callback starts empty rather than copying an existing value.
  716. var callback = context.Callbacks.GetElement(newEvent);
  717. callback.SetValue(null);
  718. context.Callbacks.Property.OnPropertyChanged();
  719. }
  720. // Update the runtime sequence accordingly.
  721. var events = context.Sequence?.InitializedEvents;
  722. events?.Add(normalizedTime, AnimancerEvent.DummyCallback);
  723. OptionalWarning.UselessEvent.Disable();
  724. if (Event.current != null)
  725. {
  726. GUI.changed = true;
  727. GUIUtility.ExitGUI();
  728. }
  729. }
  730. /************************************************************************************************************************/
  731. /// <summary>Removes the event at the specified `index`.</summary>
  732. public static void RemoveEvent(Context context, int index)
  733. {
  734. // If it's an End Event, set it to NaN.
  735. if (index >= context.Times.Count - 1)
  736. {
  737. context.Times.GetElement(index).floatValue = float.NaN;
  738. if (context.Callbacks.Count > index)
  739. context.Callbacks.Count--;
  740. Deselect();
  741. // Update the runtime sequence accordingly.
  742. var events = context.Sequence?.InitializedEvents;
  743. if (events != null)
  744. {
  745. events.EndEvent = new(float.NaN, null);
  746. }
  747. }
  748. else// Otherwise remove it.
  749. {
  750. context.Times.Property.DeleteArrayElementAtIndex(index);
  751. context.Times.Count--;
  752. // Update the runtime sequence accordingly.
  753. var events = context.Sequence?.InitializedEvents;
  754. events?.Remove(index);
  755. if (index < context.Names.Count)
  756. {
  757. context.Names.Property.DeleteArrayElementAtIndex(index);
  758. context.Names.Count--;
  759. }
  760. if (index < context.Callbacks.Count)
  761. {
  762. context.Callbacks.Property.DeleteArrayElementAtIndex(index);
  763. context.Callbacks.Count--;
  764. }
  765. }
  766. }
  767. /************************************************************************************************************************/
  768. /// <summary>Sorts the events in the `context` according to their times.</summary>
  769. private static bool SortEvents(Context context)
  770. {
  771. if (context.Times.Count <= 2)
  772. return false;
  773. // The serializable sequence sorts itself in ISerializationCallbackReceiver.OnBeforeSerialize.
  774. var selectedEvent = context.SelectedEvent;
  775. var sorted = context.Property.serializedObject.ApplyModifiedProperties();
  776. if (!sorted)
  777. return false;
  778. context.Property.serializedObject.Update();
  779. context.Times.Refresh();
  780. context.Names.Refresh();
  781. context.Callbacks.Refresh();
  782. return context.SelectedEvent != selectedEvent;
  783. }
  784. /************************************************************************************************************************/
  785. #endregion
  786. /************************************************************************************************************************/
  787. #region OnBeforeSerialize
  788. /************************************************************************************************************************/
  789. [InitializeOnLoadMethod]
  790. private static void InitializeOnBeforeSerialize()
  791. => SerializableSequence.OnBeforeSerialize += OnBeforeSerialize;
  792. private static void OnBeforeSerialize(SerializableSequence sequence)
  793. {
  794. var warnings = OptionalWarning.ProOnly.DisableTemporarily();
  795. var normalizedTimes = sequence.NormalizedTimes;
  796. warnings.Enable();
  797. if (normalizedTimes == null ||
  798. normalizedTimes.Length <= 2)
  799. {
  800. sequence.CompactArrays();
  801. return;
  802. }
  803. var eventContext = Context.Current;
  804. var selectedEvent = eventContext?.Property != null
  805. ? eventContext.SelectedEvent
  806. : -1;
  807. var timeCount = normalizedTimes.Length - 1;
  808. var previousTime = normalizedTimes[0];
  809. // Bubble Sort based on the normalized times.
  810. for (int i = 1; i < timeCount; i++)
  811. {
  812. var time = normalizedTimes[i];
  813. if (time >= previousTime)
  814. {
  815. previousTime = time;
  816. continue;
  817. }
  818. normalizedTimes.Swap(i, i - 1);
  819. DynamicSwap(ref sequence.Callbacks, i);
  820. DynamicSwap(ref sequence.Names, i);
  821. if (selectedEvent == i)
  822. selectedEvent = i - 1;
  823. else if (selectedEvent == i - 1)
  824. selectedEvent = i;
  825. if (i == 1)
  826. {
  827. i = 0;
  828. previousTime = float.NegativeInfinity;
  829. }
  830. else
  831. {
  832. i -= 2;
  833. previousTime = normalizedTimes[i];
  834. }
  835. }
  836. // If the current animation is looping, clamp all times within the 0-1 range.
  837. var transitionContext = TransitionDrawer.Context;
  838. if (transitionContext.Transition != null &&
  839. transitionContext.Transition.IsLooping)
  840. {
  841. for (int i = normalizedTimes.Length - 1; i >= 0; i--)
  842. {
  843. var time = normalizedTimes[i];
  844. if (time < 0)
  845. normalizedTimes[i] = 0;
  846. else if (time > AnimancerEvent.AlmostOne)
  847. normalizedTimes[i] = AnimancerEvent.AlmostOne;
  848. }
  849. }
  850. // If the selected event was moved adjust the selection.
  851. if (eventContext?.Property != null && eventContext.SelectedEvent != selectedEvent)
  852. {
  853. eventContext.SelectedEvent = selectedEvent;
  854. TransitionPreviewWindow.PreviewNormalizedTime = normalizedTimes[selectedEvent];
  855. }
  856. sequence.CompactArrays();
  857. }
  858. /************************************************************************************************************************/
  859. /// <summary>
  860. /// Swaps <c>array[index]</c> with <c>array[index - 1]</c>
  861. /// while accounting for the possibility of the `index` being beyond the bounds of the `array`.
  862. /// </summary>
  863. private static void DynamicSwap<T>(ref T[] array, int index)
  864. {
  865. var count = array != null ? array.Length : 0;
  866. if (index == count)
  867. Array.Resize(ref array, ++count);
  868. if (index < count)
  869. array.Swap(index, index - 1);
  870. }
  871. /************************************************************************************************************************/
  872. #endregion
  873. /************************************************************************************************************************/
  874. #region Context
  875. /************************************************************************************************************************/
  876. /// <summary>Details of an <see cref="AnimancerEvent.Sequence.Serializable"/>.</summary>
  877. public class Context : IDisposable
  878. {
  879. /************************************************************************************************************************/
  880. /// <summary>The main property representing the <see cref="Sequence"/> field.</summary>
  881. public SerializedProperty Property { get; private set; }
  882. private SerializableSequence _Sequence;
  883. /// <summary>Underlying value of the <see cref="Property"/>.</summary>
  884. public SerializableSequence Sequence
  885. {
  886. get
  887. {
  888. if (_Sequence == null && Property.serializedObject.targetObjects.Length == 1)
  889. _Sequence = Property.GetValue<SerializableSequence>();
  890. return _Sequence;
  891. }
  892. }
  893. /// <summary>The property representing the <see cref="SerializableSequence.NormalizedTimes"/> backing field.</summary>
  894. public readonly SerializedArrayProperty Times = new();
  895. /// <summary>The property representing the <see cref="SerializableSequence.Names"/> backing field.</summary>
  896. public readonly SerializedArrayProperty Names = new();
  897. /// <summary>The property representing the <see cref="SerializableSequence.Callbacks"/> backing field.</summary>
  898. public readonly SerializedArrayProperty Callbacks = new();
  899. /************************************************************************************************************************/
  900. private int _SelectedEvent;
  901. /// <summary>The index of the currently selected event.</summary>
  902. public int SelectedEvent
  903. {
  904. get => _SelectedEvent;
  905. set
  906. {
  907. if (Times != null && value >= 0 && (value < Times.Count || Times.Count == 0))
  908. {
  909. float normalizedTime;
  910. if (Times.Count > 0)
  911. {
  912. normalizedTime = Times.GetElement(value).floatValue;
  913. }
  914. else
  915. {
  916. var transition = TransitionContext.Transition;
  917. var speed = transition != null ? transition.Speed : 1;
  918. normalizedTime = AnimancerEvent.Sequence.GetDefaultNormalizedEndTime(speed);
  919. }
  920. TransitionPreviewWindow.PreviewNormalizedTime = normalizedTime;
  921. }
  922. if (_SelectedEvent == value &&
  923. Callbacks != null)
  924. return;
  925. _SelectedEvent = value;
  926. TemporarySettings.SetSelectedEvent(Callbacks.Property, value);
  927. }
  928. }
  929. /************************************************************************************************************************/
  930. /// <summary>The stack of active contexts.</summary>
  931. private static readonly List<Context> Stack = new();
  932. /// <summary>The number of active items in the <see cref="Stack"/>.</summary>
  933. private static int _ActiveIndex = -1;
  934. /// <summary>The currently active instance.</summary>
  935. public static Context Current { get; private set; }
  936. /************************************************************************************************************************/
  937. /// <summary>Adds a new <see cref="Context"/> representing the `property` to the stack and returns it.</summary>
  938. public static Context Get(SerializedProperty property)
  939. {
  940. _ActiveIndex++;
  941. if (_ActiveIndex >= Stack.Count)
  942. {
  943. Current = new();
  944. Stack.Add(Current);
  945. }
  946. else
  947. {
  948. Current = Stack[_ActiveIndex];
  949. }
  950. Current.Initialize(property);
  951. EditorGUI.BeginChangeCheck();
  952. return Current;
  953. }
  954. /// <summary>Sets this <see cref="Context"/> as the <see cref="Current"/> and returns it.</summary>
  955. public Context SetAsCurrent()
  956. {
  957. Current = this;
  958. EditorGUI.BeginChangeCheck();
  959. return this;
  960. }
  961. /************************************************************************************************************************/
  962. private void Initialize(SerializedProperty property)
  963. {
  964. if (Property == property)
  965. return;
  966. Property = property;
  967. _Sequence = null;
  968. Times.Property = property.FindPropertyRelative(SerializableSequence.NormalizedTimesField);
  969. Names.Property = property.FindPropertyRelative(SerializableSequence.NamesField);
  970. Callbacks.Property = property.FindPropertyRelative(SerializableSequence.CallbacksField);
  971. if (Names.Count > Times.Count)
  972. Names.Count = Times.Count;
  973. if (Callbacks.Count > Times.Count)
  974. Callbacks.Count = Times.Count;
  975. _SelectedEvent = TemporarySettings.GetSelectedEvent(Callbacks.Property);
  976. _SelectedEvent = Mathf.Min(_SelectedEvent, Mathf.Max(Times.Count - 1, 0));
  977. }
  978. /************************************************************************************************************************/
  979. /// <summary>[<see cref="IDisposable"/>] Calls <see cref="SerializedObject.ApplyModifiedProperties"/>.</summary>
  980. public void Dispose()
  981. {
  982. if (this == Stack[_ActiveIndex])
  983. _ActiveIndex--;
  984. Stack.TryGet(_ActiveIndex, out var current);
  985. Current = current;
  986. if (EditorGUI.EndChangeCheck())
  987. Property.serializedObject.ApplyModifiedProperties();
  988. Property = null;
  989. _Sequence = null;
  990. }
  991. /************************************************************************************************************************/
  992. /// <summary>Shorthand for <see cref="TransitionDrawer.Context"/>.</summary>
  993. public TransitionDrawer.DrawerContext TransitionContext
  994. => TransitionDrawer.Context;
  995. /************************************************************************************************************************/
  996. /// <summary>Creates a copy of this <see cref="Context"/>.</summary>
  997. public Context Copy()
  998. {
  999. var copy = new Context
  1000. {
  1001. Property = Property,
  1002. _SelectedEvent = _SelectedEvent,
  1003. };
  1004. copy.Times.Property = Times.Property;
  1005. copy.Names.Property = Names.Property;
  1006. copy.Callbacks.Property = Callbacks.Property;
  1007. return copy;
  1008. }
  1009. /************************************************************************************************************************/
  1010. }
  1011. /************************************************************************************************************************/
  1012. #endregion
  1013. /************************************************************************************************************************/
  1014. }
  1015. /************************************************************************************************************************/
  1016. #region Settings
  1017. /************************************************************************************************************************/
  1018. /// <summary>[Editor-Only] Settings for <see cref="SerializableEventSequenceDrawer"/>.</summary>
  1019. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/SerializableEventSequenceDrawerSettings
  1020. [Serializable, InternalSerializableType]
  1021. public class SerializableEventSequenceDrawerSettings : AnimancerSettingsGroup
  1022. {
  1023. /************************************************************************************************************************/
  1024. /// <inheritdoc/>
  1025. public override string DisplayName
  1026. => "Animancer Events";
  1027. /// <inheritdoc/>
  1028. public override int Index
  1029. => 4;
  1030. /************************************************************************************************************************/
  1031. [SerializeField]
  1032. [Tooltip("Should Animancer Event Callbacks be hidden in the Inspector?")]
  1033. private bool _HideEventCallbacks;
  1034. /// <summary>Should Animancer Event Callbacks be hidden in the Inspector?</summary>
  1035. public static bool HideEventCallbacks
  1036. => AnimancerSettingsGroup<SerializableEventSequenceDrawerSettings>.Instance._HideEventCallbacks;
  1037. /************************************************************************************************************************/
  1038. }
  1039. /************************************************************************************************************************/
  1040. #endregion
  1041. /************************************************************************************************************************/
  1042. }
  1043. #endif