LocationAnimationsEditor.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEditor;
  5. using UnityEngine;
  6. namespace TheraBytes.BetterUi.Editor
  7. {
  8. [CustomEditor(typeof(LocationAnimations))]
  9. public class LocationAnimationsEditor : UnityEditor.Editor
  10. {
  11. const string ANY = "[ Any Location ]";
  12. const string NONE = "[ None ]";
  13. static readonly GUIContent pushContent = new GUIContent("↑", "Push current location data to Rect Transform.");
  14. static readonly GUIContent pullContent = new GUIContent("↓", "Pull Rect Transform to current location data.");
  15. SerializedProperty useRelativeProp, locationListProp, transitionListProp, startLocProp, startupAniProp, initCallbackProp;
  16. bool locationListExpanded = true;
  17. bool transitionListExpanded = true;
  18. bool initialStateExpanded = true;
  19. HashSet<string> expandedAnimations = new HashSet<string>();
  20. HashSet<string> expandedLocations = new HashSet<string>();
  21. LocationAnimations locAni;
  22. float lastTime;
  23. protected virtual void OnEnable()
  24. {
  25. locAni = target as LocationAnimations;
  26. useRelativeProp = serializedObject.FindProperty("useRelativeLocations");
  27. locationListProp = serializedObject.FindProperty("locations");
  28. transitionListProp = serializedObject.FindProperty("animations");
  29. startLocProp = serializedObject.FindProperty("startLocation");
  30. startupAniProp = serializedObject.FindProperty("startUpAnimation");
  31. initCallbackProp = serializedObject.FindProperty("actionOnInit");
  32. lastTime = (float)EditorApplication.timeSinceStartup;
  33. EditorApplication.update += UpdateAnimation;
  34. }
  35. protected virtual void OnDisable()
  36. {
  37. EditorApplication.update -= UpdateAnimation;
  38. }
  39. private void UpdateAnimation()
  40. {
  41. if (EditorApplication.isPlaying || EditorApplication.isPaused)
  42. return;
  43. float now = (float)EditorApplication.timeSinceStartup;
  44. float delta = now - lastTime;
  45. lastTime = now;
  46. locAni.UpdateCurrentAnimation(delta);
  47. }
  48. public override void OnInspectorGUI()
  49. {
  50. // LOCATIONS
  51. if (BoldFoldout("Locations", ref locationListExpanded))
  52. {
  53. for (int i = 0; i < locationListProp.arraySize; i++)
  54. {
  55. SerializedProperty prop = locationListProp.GetArrayElementAtIndex(i);
  56. SerializedProperty nameProp = prop.FindPropertyRelative("name");
  57. SerializedProperty fallbackProp = prop.FindPropertyRelative("transformFallback");
  58. SerializedProperty configsProp = prop.FindPropertyRelative("transformConfigs");
  59. EditorGUILayout.BeginVertical("box");
  60. string name = nameProp.stringValue;
  61. bool expanded = expandedLocations.Contains(name);
  62. if (DrawNameAndDelete(nameProp, locationListProp, LocationNameChanged, ref i, () =>
  63. {
  64. if (GUILayout.Button(EditorGUIUtility.IconContent(expanded ? "d_ViewToolOrbit On" : "d_ViewToolOrbit"),
  65. EditorStyles.label, GUILayout.Width(25), GUILayout.Height(EditorGUIUtility.singleLineHeight)))
  66. {
  67. expanded = !expanded;
  68. if (expanded)
  69. expandedLocations.Add(name);
  70. else
  71. expandedLocations.Remove(name);
  72. }
  73. if (GUILayout.Button(pullContent, GUILayout.Width(25)))
  74. {
  75. Undo.RecordObject(locAni, "pull from transform");
  76. var loc = locAni.GetLocation(name);
  77. PullFromTransform(loc);
  78. }
  79. if (GUILayout.Button(pushContent, GUILayout.Width(25)))
  80. {
  81. Undo.RecordObject(locAni.RectTransform, "push to transform");
  82. var loc = locAni.GetLocation(name);
  83. PushToTransform(loc);
  84. }
  85. }))
  86. {
  87. continue;
  88. }
  89. if (expanded)
  90. {
  91. if (locAni.UseRelativeLocations)
  92. {
  93. RectTransformDataDrawer.OverwritePushPullMethods(
  94. push: (rt, data) => RectTransformData.Combine(data, locAni.ReferenceLocation).PushToTransform(rt),
  95. pull: (rt, data) => data.PullFromData(RectTransformData.Separate(new RectTransformData(rt), locAni.ReferenceLocation)));
  96. }
  97. ScreenConfigConnectionHelper.DrawGui(nameProp.stringValue, configsProp, ref fallbackProp,
  98. drawContent: null,
  99. newElementInitCallback: (n, obj) =>
  100. {
  101. var euler = obj.FindPropertyRelative("saveRotationAsEuler");
  102. euler.boolValue = true;
  103. });
  104. RectTransformDataDrawer.OverwritePushPullMethods(null, null);
  105. }
  106. EditorGUILayout.EndVertical();
  107. }
  108. EditorGUILayout.BeginHorizontal();
  109. // Use Relative Locations
  110. bool preUseRelative = useRelativeProp.boolValue;
  111. EditorGUILayout.PropertyField(useRelativeProp);
  112. bool postUseRelative = useRelativeProp.boolValue;
  113. if (preUseRelative != postUseRelative)
  114. {
  115. ConvertLocationSpace(postUseRelative);
  116. }
  117. // "Add Location" button
  118. var newObj = DrawAddButton("Add Location", "Location", locationListProp);
  119. if (newObj != null)
  120. {
  121. var rtd = newObj.FindPropertyRelative("transformFallback");
  122. var euler = rtd.FindPropertyRelative("saveRotationAsEuler");
  123. euler.boolValue = true;
  124. serializedObject.ApplyModifiedPropertiesWithoutUndo();
  125. }
  126. EditorGUILayout.EndHorizontal();
  127. }
  128. // ANIMATIONS
  129. List<string> options = locAni.Locations.Select(o => o.Name).ToList();
  130. string[] toOptions = options.ToArray();
  131. options.Insert(0, ANY);
  132. string[] fromOptions = options.ToArray();
  133. if (BoldFoldout("Animations", ref transitionListExpanded))
  134. {
  135. for (int i = 0; i < transitionListProp.arraySize; i++)
  136. {
  137. SerializedProperty prop = transitionListProp.GetArrayElementAtIndex(i);
  138. SerializedProperty nameProp = prop.FindPropertyRelative("name");
  139. SerializedProperty fromProp = prop.FindPropertyRelative("from");
  140. SerializedProperty toProp = prop.FindPropertyRelative("to");
  141. EditorGUILayout.BeginVertical("box");
  142. if (DrawNameAndDelete(nameProp, transitionListProp, TransitionNameChanged, ref i,
  143. () =>
  144. {
  145. bool isAnimating = locAni.RunningAnimation != null && locAni.RunningAnimation.Animation.Name == nameProp.stringValue;
  146. if (GUILayout.Button((isAnimating) ? "■" : "►", GUILayout.Width(20)))
  147. {
  148. if (isAnimating)
  149. {
  150. locAni.StopCurrentAnimation();
  151. }
  152. else
  153. {
  154. locAni.StartAnimation(nameProp.stringValue);
  155. }
  156. }
  157. }))
  158. {
  159. continue;
  160. }
  161. EditorGUILayout.Space();
  162. int fromSelection = Array.IndexOf<string>(fromOptions, fromProp.stringValue);
  163. if (fromSelection < 0) // any
  164. fromSelection = 0;
  165. int newFromSelection = EditorGUILayout.Popup("From", fromSelection, fromOptions);
  166. if (fromSelection != newFromSelection)
  167. {
  168. string val = fromOptions[newFromSelection];
  169. fromProp.stringValue = (val != ANY) ? val : "";
  170. }
  171. int toSelection = Array.IndexOf<string>(toOptions, toProp.stringValue);
  172. int newToSelection = EditorGUILayout.Popup("To", toSelection, toOptions);
  173. if (toSelection != newToSelection)
  174. {
  175. toProp.stringValue = toOptions[newToSelection];
  176. }
  177. EditorGUI.indentLevel++;
  178. bool actionsFoldout = EditorGUILayout.Foldout(expandedAnimations.Contains(nameProp.stringValue), "Advanced");
  179. if (actionsFoldout)
  180. {
  181. SerializedProperty curveProp = prop.FindPropertyRelative("curve");
  182. SerializedProperty timeScaleProp = prop.FindPropertyRelative("timeScale");
  183. SerializedProperty eulerProp = prop.FindPropertyRelative("animateWithEulerRotation");
  184. SerializedProperty actionBeforeProp = prop.FindPropertyRelative("actionBeforeStart");
  185. SerializedProperty actionUpdateProp = prop.FindPropertyRelative("actionOnUpdating");
  186. SerializedProperty actionAfterProp = prop.FindPropertyRelative("actionAfterFinish");
  187. curveProp.animationCurveValue = EditorGUILayout.CurveField("Curve", curveProp.animationCurveValue);
  188. float speedBefore = timeScaleProp.floatValue;
  189. EditorGUILayout.PropertyField(timeScaleProp);
  190. float speedAfter = timeScaleProp.floatValue;
  191. if (speedBefore != speedAfter && locAni.RunningAnimation != null)
  192. {
  193. locAni.RunningAnimation.TimeScale = speedAfter;
  194. }
  195. string[] rotationOptions = { "Quaternion", "Euler" };
  196. int prevIdx = (eulerProp.boolValue) ? 1 : 0;
  197. int newIdx = EditorGUILayout.Popup("Rotation Mode", prevIdx, rotationOptions);
  198. if (prevIdx != newIdx)
  199. {
  200. eulerProp.boolValue = (newIdx == 1);
  201. }
  202. EditorGUILayout.PropertyField(actionBeforeProp);
  203. EditorGUILayout.PropertyField(actionUpdateProp);
  204. EditorGUILayout.PropertyField(actionAfterProp);
  205. expandedAnimations.Add(nameProp.stringValue);
  206. }
  207. else
  208. {
  209. expandedAnimations.Remove(nameProp.stringValue);
  210. }
  211. EditorGUI.indentLevel--;
  212. serializedObject.ApplyModifiedProperties();
  213. EditorGUILayout.EndVertical();
  214. }
  215. var newAni = DrawAddButton("Add Animation", "Animation", transitionListProp);
  216. if (newAni != null)
  217. {
  218. var timeScaleProp = newAni.FindPropertyRelative("timeScale");
  219. timeScaleProp.floatValue = 1;
  220. serializedObject.ApplyModifiedPropertiesWithoutUndo();
  221. }
  222. }
  223. // INITIAL STATE
  224. if (BoldFoldout("Initial State", ref initialStateExpanded))
  225. {
  226. // Start Location
  227. int startLocSelection = Array.IndexOf<string>(fromOptions, startLocProp.stringValue);
  228. if (startLocSelection < 0) // any
  229. startLocSelection = 0;
  230. int newFromSelection = EditorGUILayout.Popup("Start Location", startLocSelection, fromOptions);
  231. if (startLocSelection != newFromSelection)
  232. {
  233. string val = fromOptions[newFromSelection];
  234. startLocProp.stringValue = (val != ANY) ? val : "";
  235. }
  236. // Start Animation
  237. List<string> opt = locAni.Animations.Select(o => o.Name).ToList();
  238. opt.Insert(0, NONE);
  239. string[] startupOptions = opt.ToArray();
  240. int sel = Array.IndexOf<string>(startupOptions, startupAniProp.stringValue);
  241. if (sel < 0) // none
  242. sel = 0;
  243. int newSel = EditorGUILayout.Popup("Startup Animation", sel, startupOptions);
  244. if (sel != newSel)
  245. {
  246. string val = startupOptions[newSel];
  247. startupAniProp.stringValue = (val != NONE) ? val : "";
  248. serializedObject.ApplyModifiedProperties();
  249. }
  250. // Init Callback
  251. EditorGUILayout.PropertyField(initCallbackProp);
  252. serializedObject.ApplyModifiedProperties();
  253. }
  254. }
  255. private void ConvertLocationSpace(bool toRelativeSpace)
  256. {
  257. foreach (var loc in locAni.Locations)
  258. {
  259. var rtd = (toRelativeSpace)
  260. ? RectTransformData.Separate(loc.CurrentTransformData, locAni.ReferenceLocation)
  261. : RectTransformData.Combine(loc.CurrentTransformData, locAni.ReferenceLocation);
  262. loc.CurrentTransformData.PullFromData(rtd);
  263. }
  264. locAni.UseRelativeLocations = toRelativeSpace;
  265. serializedObject.Update();
  266. serializedObject.ApplyModifiedPropertiesWithoutUndo();
  267. }
  268. private void PushToTransform(LocationAnimations.LocationData loc)
  269. {
  270. if (locAni.UseRelativeLocations)
  271. {
  272. var rtd = RectTransformData.Combine(loc.CurrentTransformData, locAni.ReferenceLocation);
  273. rtd.PushToTransform(locAni.RectTransform);
  274. }
  275. else
  276. {
  277. loc.CurrentTransformData.PushToTransform(locAni.RectTransform);
  278. }
  279. }
  280. private void PullFromTransform(LocationAnimations.LocationData loc)
  281. {
  282. if (locAni.UseRelativeLocations)
  283. {
  284. var rtd = RectTransformData.Separate(new RectTransformData(locAni.RectTransform), locAni.ReferenceLocation);
  285. loc.CurrentTransformData.PullFromData(rtd);
  286. }
  287. else
  288. {
  289. loc.CurrentTransformData.PullFromTransform(locAni.RectTransform);
  290. }
  291. }
  292. private void LocationNameChanged(int index, string before, string after)
  293. {
  294. if (locAni.Locations.Any(o => o.Name == before))
  295. {
  296. // the name is still valid.
  297. return;
  298. }
  299. foreach (var tr in locAni.Animations)
  300. {
  301. if (tr.From == before)
  302. {
  303. tr.From = after;
  304. }
  305. if (tr.To == before)
  306. {
  307. tr.To = after;
  308. }
  309. }
  310. serializedObject.ApplyModifiedProperties();
  311. }
  312. private void TransitionNameChanged(int index, string before, string after)
  313. {
  314. if (locAni.Animations.Any(o => o.Name == before))
  315. {
  316. // the name is still valid.
  317. return;
  318. }
  319. if (locAni.StartUpAnimation == before)
  320. {
  321. locAni.StartUpAnimation = after;
  322. }
  323. serializedObject.ApplyModifiedProperties();
  324. }
  325. bool BoldFoldout(string text, ref bool expanded)
  326. {
  327. string prefix = (expanded) ? "▼" : "►";
  328. if (GUILayout.Button(string.Format("{0} {1}", prefix, text), EditorStyles.boldLabel))
  329. {
  330. expanded = !expanded;
  331. }
  332. return expanded;
  333. }
  334. SerializedProperty DrawAddButton(string text, string namePrefix, SerializedProperty prop)
  335. {
  336. SerializedProperty result = null;
  337. EditorGUILayout.BeginHorizontal();
  338. GUILayout.FlexibleSpace();
  339. if (GUILayout.Button(text, GUILayout.Width(150)))
  340. {
  341. prop.InsertArrayElementAtIndex(prop.arraySize);
  342. result = prop.GetArrayElementAtIndex(prop.arraySize - 1);
  343. SerializedProperty nameProp = result.FindPropertyRelative("name");
  344. if (nameProp != null)
  345. {
  346. nameProp.stringValue = string.Format("{0} {1}", namePrefix, prop.arraySize);
  347. }
  348. serializedObject.ApplyModifiedPropertiesWithoutUndo();
  349. }
  350. EditorGUILayout.EndHorizontal();
  351. return result;
  352. }
  353. bool DrawNameAndDelete(SerializedProperty nameProp, SerializedProperty listProp, Action<int, string, string> nameChanged, ref int idx, Action drawNextToText = null)
  354. {
  355. EditorGUILayout.BeginHorizontal();
  356. string nameBefore = nameProp.stringValue;
  357. EditorGUILayout.PropertyField(nameProp, GUIContent.none);
  358. string nameAfter = nameProp.stringValue;
  359. if (nameBefore != nameAfter && nameChanged != null)
  360. {
  361. int index = idx;
  362. serializedObject.ApplyModifiedProperties();
  363. nameChanged(index, nameBefore, nameAfter);
  364. }
  365. if (drawNextToText != null)
  366. {
  367. drawNextToText();
  368. }
  369. GUILayout.FlexibleSpace();
  370. if (GUILayout.Button("x", GUILayout.Width(20)))
  371. {
  372. if (EditorUtility.DisplayDialog("Delete Item", string.Format("Do you really want to delete the item '{0}'?", nameProp.stringValue), "Yes", "No"))
  373. {
  374. listProp.DeleteArrayElementAtIndex(idx);
  375. idx--;
  376. serializedObject.ApplyModifiedPropertiesWithoutUndo();
  377. EditorGUILayout.EndHorizontal();
  378. return true;
  379. }
  380. }
  381. EditorGUILayout.EndHorizontal();
  382. return false;
  383. }
  384. }
  385. }