ReadMe.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
  3. #if UNITY_EDITOR
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using UnityEditor;
  8. using UnityEngine;
  9. namespace Animancer.Editor
  10. {
  11. /// <summary>[Editor-Only] A welcome screen for <see cref="Animancer"/>.</summary>
  12. // [CreateAssetMenu(menuName = Strings.MenuPrefix + "Read Me", order = Strings.AssetMenuOrder)]
  13. [HelpURL(Strings.DocsURLs.APIDocumentation + "." + nameof(Animancer.Editor) + "/" + nameof(ReadMe))]
  14. public class ReadMe : ScriptableObject
  15. {
  16. /************************************************************************************************************************/
  17. #region Fields and Properties
  18. /************************************************************************************************************************/
  19. /// <summary>The release ID of the current version.</summary>
  20. /// <example><list type="bullet">
  21. /// <item>[ 1] = v1.0: 2018-05-02.</item>
  22. /// <item>[ 2] = v1.1: 2018-05-29.</item>
  23. /// <item>[ 3] = v1.2: 2018-08-14.</item>
  24. /// <item>[ 4] = v1.3: 2018-09-12.</item>
  25. /// <item>[ 5] = v2.0: 2018-10-08.</item>
  26. /// <item>[ 6] = v3.0: 2019-05-27.</item>
  27. /// <item>[ 7] = v3.1: 2019-08-12.</item>
  28. /// <item>[ 8] = v4.0: 2020-01-28.</item>
  29. /// <item>[ 9] = v4.1: 2020-02-21.</item>
  30. /// <item>[10] = v4.2: 2020-03-02.</item>
  31. /// <item>[11] = v4.3: 2020-03-13.</item>
  32. /// <item>[12] = v4.4: 2020-03-27.</item>
  33. /// <item>[13] = v5.0: 2020-07-17.</item>
  34. /// <item>[14] = v5.1: 2020-07-27.</item>
  35. /// <item>[15] = v5.2: 2020-09-16.</item>
  36. /// <item>[16] = v5.3: 2020-10-06.</item>
  37. /// <item>[17] = v6.0: 2020-12-04.</item>
  38. /// <item>[18] = v6.1: 2021-04-13.</item>
  39. /// <item>[19] = v7.0: 2021-07-29.</item>
  40. /// <item>[20] = v7.1: 2021-08-13.</item>
  41. /// <item>[21] = v7.2: 2021-10-17.</item>
  42. /// <item>[22] = v7.3: 2022-07-03.</item>
  43. /// </list></example>
  44. protected virtual int ReleaseNumber => 22;
  45. /// <summary>The display name of this product version.</summary>
  46. protected virtual string VersionName => "v7.3";
  47. /// <summary>The URL for the change log of this Animancer version.</summary>
  48. protected virtual string ChangeLogURL => Strings.DocsURLs.ChangeLogPrefix + "v7-3";
  49. /// <summary>The key used to save the release number.</summary>
  50. protected virtual string ReleaseNumberPrefKey => nameof(Animancer) + "." + nameof(ReleaseNumber);
  51. /// <summary>The name of this product.</summary>
  52. protected virtual string ProductName => Strings.ProductName + " Pro";
  53. /// <summary>The URL for the documentation.</summary>
  54. protected virtual string DocumentationURL => Strings.DocsURLs.Documentation;
  55. /// <summary>The URL for the example documentation.</summary>
  56. protected virtual string ExampleURL => Strings.DocsURLs.Examples;
  57. /// <summary>The URL for the Unity Forum thread.</summary>
  58. protected virtual string ForumURL => Strings.DocsURLs.Forum;
  59. /// <summary>The URL for the Github Issues page.</summary>
  60. protected virtual string IssuesURL => Strings.DocsURLs.Issues;
  61. /// <summary>The developer email address.</summary>
  62. protected virtual string DeveloperEmail => Strings.DocsURLs.DeveloperEmail;
  63. /************************************************************************************************************************/
  64. /// <summary>
  65. /// The <see cref="ReadMe"/> file name ends with the <see cref="VersionName"/> to detect if the user imported
  66. /// this version without deleting a previous version.
  67. /// <para></para>
  68. /// When Unity's package importer sees an existing file with the same GUID as one in the package, it will
  69. /// overwrite that file but not move or rename it if the name has changed. So it will leave the file there with
  70. /// the old version name.
  71. /// </summary>
  72. private bool HasCorrectName => name.EndsWith(VersionName);
  73. /************************************************************************************************************************/
  74. [SerializeField]
  75. private DefaultAsset _ExamplesFolder;
  76. /************************************************************************************************************************/
  77. #endregion
  78. /************************************************************************************************************************/
  79. #region Show On Startup
  80. /************************************************************************************************************************/
  81. [SerializeField]
  82. private bool _DontShowOnStartup;
  83. /// <summary>Should the system be prevented from automatically selecting this asset on startup?</summary>
  84. public bool DontShowOnStartup => _DontShowOnStartup && HasCorrectName;
  85. /************************************************************************************************************************/
  86. /// <summary>Automatically selects a <see cref="ReadMe"/> on startup.</summary>
  87. [InitializeOnLoadMethod]
  88. private static void ShowReadMe()
  89. {
  90. EditorApplication.delayCall += () =>
  91. {
  92. var asset = FindReadMe();
  93. if (asset != null)// Delay the call again to ensure that the Project window actually shows the selection.
  94. EditorApplication.delayCall += () =>
  95. Selection.activeObject = asset;
  96. };
  97. }
  98. /************************************************************************************************************************/
  99. /// <summary>
  100. /// Finds the most recently modified <see cref="ReadMe"/> asset with <see cref="DontShowOnStartup"/> disabled.
  101. /// </summary>
  102. private static ReadMe FindReadMe()
  103. {
  104. DateTime latestWriteTime = default;
  105. ReadMe latestReadMe = null;
  106. string latestGUID = null;
  107. var guids = AssetDatabase.FindAssets($"t:{nameof(ReadMe)}");
  108. for (int i = 0; i < guids.Length; i++)
  109. {
  110. var guid = guids[i];
  111. if (SessionState.GetBool(guid, false))
  112. continue;
  113. var assetPath = AssetDatabase.GUIDToAssetPath(guid);
  114. var asset = AssetDatabase.LoadAssetAtPath<ReadMe>(assetPath);
  115. if (asset != null && !asset.DontShowOnStartup)
  116. {
  117. var writeTime = File.GetLastWriteTimeUtc(assetPath);
  118. if (latestWriteTime < writeTime)
  119. {
  120. latestWriteTime = writeTime;
  121. latestReadMe = asset;
  122. latestGUID = guid;
  123. }
  124. }
  125. }
  126. if (latestGUID != null)
  127. SessionState.SetBool(latestGUID, true);
  128. return latestReadMe;
  129. }
  130. /************************************************************************************************************************/
  131. #endregion
  132. /************************************************************************************************************************/
  133. #region Custom Editor
  134. /************************************************************************************************************************/
  135. /// <summary>[Editor-Only] A custom Inspector for <see cref="ReadMe"/>.</summary>
  136. [CustomEditor(typeof(ReadMe), editorForChildClasses: true)]
  137. public class Editor : UnityEditor.Editor
  138. {
  139. /************************************************************************************************************************/
  140. [NonSerialized] private ReadMe _Target;
  141. [NonSerialized] private Texture2D _Icon;
  142. [NonSerialized] private int _PreviousVersion;
  143. [NonSerialized] private string _ExamplesDirectory;
  144. [NonSerialized] private List<ExampleGroup> _Examples;
  145. [NonSerialized] private string _Title;
  146. [NonSerialized] private string _EmailLink;
  147. [NonSerialized] private SerializedProperty _DontShowOnStartupProperty;
  148. /************************************************************************************************************************/
  149. /// <summary>Don't use any margins.</summary>
  150. public override bool UseDefaultMargins() => false;
  151. /************************************************************************************************************************/
  152. protected virtual void OnEnable()
  153. {
  154. _Target = (ReadMe)target;
  155. _Icon = AssetPreview.GetMiniThumbnail(target);
  156. var key = _Target.ReleaseNumberPrefKey;
  157. _PreviousVersion = PlayerPrefs.GetInt(key, -1);
  158. if (_PreviousVersion < 0)
  159. _PreviousVersion = EditorPrefs.GetInt(key, -1);// Animancer v2.0 used EditorPrefs.
  160. _Examples = ExampleGroup.Gather(_Target._ExamplesFolder, out _ExamplesDirectory);
  161. _Title = $"{_Target.ProductName}\n{_Target.VersionName}";
  162. _EmailLink = $"mailto:{_Target.DeveloperEmail}?subject={_Target.ProductName.Replace(" ", "%20")}";
  163. _DontShowOnStartupProperty = serializedObject.FindProperty(nameof(_DontShowOnStartup));
  164. }
  165. /************************************************************************************************************************/
  166. protected override void OnHeaderGUI()
  167. {
  168. GUILayout.BeginHorizontal(Styles.TitleArea);
  169. {
  170. using (ObjectPool.Disposable.AcquireContent(out var label, _Title, null, false))
  171. {
  172. var iconWidth = Styles.Title.CalcHeight(label, EditorGUIUtility.currentViewWidth);
  173. GUILayout.Label(_Icon, GUILayout.Width(iconWidth), GUILayout.Height(iconWidth));
  174. GUILayout.Label(label, Styles.Title);
  175. }
  176. }
  177. GUILayout.EndHorizontal();
  178. }
  179. /************************************************************************************************************************/
  180. public override void OnInspectorGUI()
  181. {
  182. serializedObject.Update();
  183. DoSpace();
  184. DoWarnings();
  185. DoShowOnStartup();
  186. DoSpace();
  187. DoIntroductionBlock();
  188. DoSpace();
  189. DoExampleBlock();
  190. DoSpace();
  191. DoSupportBlock();
  192. DoSpace();
  193. DoShowOnStartup();
  194. serializedObject.ApplyModifiedProperties();
  195. }
  196. /************************************************************************************************************************/
  197. protected static void DoSpace() => GUILayout.Space(AnimancerGUI.LineHeight * 0.2f);
  198. /************************************************************************************************************************/
  199. private void DoShowOnStartup()
  200. {
  201. var area = AnimancerGUI.LayoutSingleLineRect();
  202. area.xMin += AnimancerGUI.LineHeight * 0.2f;
  203. using (ObjectPool.Disposable.AcquireContent(out var content, _DontShowOnStartupProperty, false))
  204. {
  205. var label = EditorGUI.BeginProperty(area, content, _DontShowOnStartupProperty);
  206. EditorGUI.BeginChangeCheck();
  207. var value = _DontShowOnStartupProperty.boolValue;
  208. value = GUI.Toggle(area, value, label);
  209. if (EditorGUI.EndChangeCheck())
  210. {
  211. _DontShowOnStartupProperty.boolValue = value;
  212. if (value)
  213. PlayerPrefs.SetInt(_Target.ReleaseNumberPrefKey, _Target.ReleaseNumber);
  214. }
  215. EditorGUI.EndProperty();
  216. }
  217. }
  218. /************************************************************************************************************************/
  219. private void DoWarnings()
  220. {
  221. MessageType messageType;
  222. if (!_Target.HasCorrectName)
  223. {
  224. messageType = MessageType.Error;
  225. }
  226. else if (_PreviousVersion >= 0 && _PreviousVersion < _Target.ReleaseNumber)
  227. {
  228. messageType = MessageType.Warning;
  229. }
  230. else return;
  231. // Upgraded from any older version.
  232. DoSpace();
  233. var directory = AssetDatabase.GetAssetPath(_Target);
  234. if (string.IsNullOrEmpty(directory))
  235. return;
  236. directory = Path.GetDirectoryName(directory);
  237. var productName = _Target.ProductName;
  238. string versionWarning;
  239. if (messageType == MessageType.Error)
  240. {
  241. versionWarning = $"You must fully delete any old version of {productName} before importing a new version." +
  242. $"\n1. Check the Upgrade Guide in the Change Log." +
  243. $"\n2. Click here to delete '{directory}'." +
  244. $"\n3. Import {productName} again.";
  245. }
  246. else
  247. {
  248. versionWarning = $"You must fully delete any old version of {productName} before importing a new version." +
  249. $"\n1. Ignore this message if you have already deleted the old version." +
  250. $"\n2. Check the Upgrade Guide in the Change Log." +
  251. $"\n3. Click here to delete '{directory}'." +
  252. $"\n4. Import {productName} again.";
  253. }
  254. EditorGUILayout.HelpBox(versionWarning, messageType);
  255. CheckDeleteDirectory(directory);
  256. DoSpace();
  257. }
  258. /************************************************************************************************************************/
  259. /// <summary>Asks if the user wants to delete the `directory` and does so if they confirm.</summary>
  260. private void CheckDeleteDirectory(string directory)
  261. {
  262. if (!AnimancerGUI.TryUseClickEventInLastRect())
  263. return;
  264. var name = _Target.ProductName;
  265. if (!AssetDatabase.IsValidFolder(directory))
  266. {
  267. Debug.Log($"{directory} doesn't exist." +
  268. $" You must have moved {name} somewhere else so you will need to delete it manually.", this);
  269. return;
  270. }
  271. if (!EditorUtility.DisplayDialog($"Delete {name}? ",
  272. $"Would you like to delete {directory}?\n\nYou will then need to reimport {name} manually.",
  273. "Delete", "Cancel"))
  274. return;
  275. AssetDatabase.DeleteAsset(directory);
  276. }
  277. /************************************************************************************************************************/
  278. protected virtual void DoIntroductionBlock()
  279. {
  280. GUILayout.BeginVertical(Styles.Block);
  281. DoHeadingLink("Documentation", null, _Target.DocumentationURL);
  282. DoSpace();
  283. DoHeadingLink("Change Log", null, _Target.ChangeLogURL);
  284. GUILayout.EndVertical();
  285. }
  286. /************************************************************************************************************************/
  287. protected virtual void DoExampleBlock()
  288. {
  289. GUILayout.BeginVertical(Styles.Block);
  290. DoHeadingLink("Examples", null, _Target.ExampleURL);
  291. if (_Target._ExamplesFolder != null)
  292. {
  293. EditorGUILayout.ObjectField(_ExamplesDirectory, _Target._ExamplesFolder, typeof(SceneAsset), false);
  294. ExampleGroup.DoExampleGUI(_Examples);
  295. }
  296. DoExtraExamples();
  297. GUILayout.EndVertical();
  298. }
  299. protected virtual void DoExtraExamples()
  300. {
  301. DoHeadingLink("Platformer Game Kit", null, "https://kybernetik.com.au/platformer", null, GUI.skin.label.fontSize);
  302. }
  303. /************************************************************************************************************************/
  304. protected virtual void DoSupportBlock()
  305. {
  306. GUILayout.BeginVertical(Styles.Block);
  307. DoHeadingLink("Forum",
  308. "for general discussions, feedback, and news",
  309. _Target.ForumURL);
  310. DoSpace();
  311. DoHeadingLink("Issues",
  312. "for questions, suggestions, and bug reports",
  313. _Target.IssuesURL);
  314. DoSpace();
  315. DoHeadingLink("Email",
  316. "for anything private",
  317. _EmailLink, _Target.DeveloperEmail);
  318. GUILayout.EndVertical();
  319. }
  320. /************************************************************************************************************************/
  321. protected void DoHeadingLink(string heading, string description, string url, string displayURL = null, int fontSize = 22)
  322. {
  323. // Heading.
  324. var area = DoLinkButton(heading, url, url == null ? Styles.HeaderLabel : Styles.HeaderLink, fontSize);
  325. // Description.
  326. area.y += AnimancerGUI.StandardSpacing;
  327. var urlHeight = Styles.URL.fontSize + Styles.URL.margin.vertical;
  328. area.height -= urlHeight;
  329. if (description != null)
  330. GUI.Label(area, description, Styles.Description);
  331. // URL.
  332. area.y += area.height;
  333. area.height = urlHeight;
  334. if (displayURL == null)
  335. displayURL = url;
  336. if (displayURL != null)
  337. {
  338. using (ObjectPool.Disposable.AcquireContent(out var label,
  339. displayURL, "Click to copy this link to the clipboard", false))
  340. {
  341. if (GUI.Button(area, label, Styles.URL))
  342. {
  343. GUIUtility.systemCopyBuffer = displayURL;
  344. Debug.Log($"Copied '{displayURL}' to the clipboard.", this);
  345. }
  346. EditorGUIUtility.AddCursorRect(area, MouseCursor.Text);
  347. }
  348. }
  349. }
  350. /************************************************************************************************************************/
  351. protected Rect DoLinkButton(string text, string url, GUIStyle style, int fontSize = 22)
  352. {
  353. using (ObjectPool.Disposable.AcquireContent(out var label, text, url, false))
  354. {
  355. style.fontSize = fontSize;
  356. var size = style.CalcSize(label);
  357. var area = GUILayoutUtility.GetRect(0, size.y);
  358. var linkArea = AnimancerGUI.StealFromLeft(ref area, size.x);
  359. if (url == null)
  360. {
  361. GUI.Label(linkArea, label, style);
  362. }
  363. else
  364. {
  365. if (GUI.Button(linkArea, label, style))
  366. Application.OpenURL(url);
  367. EditorGUIUtility.AddCursorRect(linkArea, MouseCursor.Link);
  368. DrawLine(
  369. new Vector2(linkArea.xMin, linkArea.yMax),
  370. new Vector2(linkArea.xMax, linkArea.yMax),
  371. style.normal.textColor);
  372. }
  373. return area;
  374. }
  375. }
  376. /************************************************************************************************************************/
  377. /// <summary>Draws a line between the `start` and `end` using the `color`.</summary>
  378. public static void DrawLine(Vector2 start, Vector2 end, Color color)
  379. {
  380. var previousColor = Handles.color;
  381. Handles.BeginGUI();
  382. Handles.color = color;
  383. Handles.DrawLine(start, end);
  384. Handles.color = previousColor;
  385. Handles.EndGUI();
  386. }
  387. /************************************************************************************************************************/
  388. /// <summary>Various <see cref="GUIStyle"/>s used by the <see cref="Editor"/>.</summary>
  389. protected static class Styles
  390. {
  391. /************************************************************************************************************************/
  392. public static readonly GUIStyle TitleArea = "In BigTitle";
  393. public static readonly GUIStyle Title = new GUIStyle(GUI.skin.label)
  394. {
  395. fontSize = 26,
  396. };
  397. public static readonly GUIStyle Block = GUI.skin.box;
  398. public static readonly GUIStyle HeaderLabel = new GUIStyle(GUI.skin.label)
  399. {
  400. stretchWidth = false,
  401. };
  402. public static readonly GUIStyle HeaderLink = new GUIStyle(HeaderLabel);
  403. public static readonly GUIStyle Description = new GUIStyle(GUI.skin.label)
  404. {
  405. alignment = TextAnchor.LowerLeft,
  406. };
  407. public static readonly GUIStyle URL = new GUIStyle(GUI.skin.label)
  408. {
  409. fontSize = 9,
  410. alignment = TextAnchor.LowerLeft,
  411. };
  412. /************************************************************************************************************************/
  413. static Styles()
  414. {
  415. HeaderLink.normal.textColor = HeaderLink.hover.textColor =
  416. new Color32(0x00, 0x78, 0xDA, 0xFF);
  417. URL.normal.textColor = Color.Lerp(URL.normal.textColor, Color.grey, 0.8f);
  418. }
  419. /************************************************************************************************************************/
  420. }
  421. /************************************************************************************************************************/
  422. /// <summary>A group of example scenes.</summary>
  423. private class ExampleGroup
  424. {
  425. /************************************************************************************************************************/
  426. /// <summary>The name of this group.</summary>
  427. public readonly string Name;
  428. /// <summary>The scenes in this group.</summary>
  429. public readonly List<SceneAsset> Scenes = new List<SceneAsset>();
  430. /// <summary>The folder paths of each of the <see cref="Scenes"/>.</summary>
  431. public readonly List<string> Directories = new List<string>();
  432. /// <summary>Indicates whether this group should show its contents in the GUI.</summary>
  433. private bool _IsExpanded;
  434. /************************************************************************************************************************/
  435. public static List<ExampleGroup> Gather(DefaultAsset rootDirectoryAsset, out string examplesDirectory)
  436. {
  437. if (rootDirectoryAsset == null)
  438. {
  439. examplesDirectory = null;
  440. return null;
  441. }
  442. examplesDirectory = AssetDatabase.GetAssetPath(rootDirectoryAsset);
  443. if (string.IsNullOrEmpty(examplesDirectory))
  444. return null;
  445. var directories = Directory.GetDirectories(examplesDirectory);
  446. var examples = new List<ExampleGroup>();
  447. for (int i = 0; i < directories.Length; i++)
  448. {
  449. var group = Gather(examplesDirectory, directories[i]);
  450. if (group != null)
  451. examples.Add(group);
  452. }
  453. if (examples.Count == 0)
  454. {
  455. var group = Gather(examplesDirectory, examplesDirectory);
  456. if (group != null)
  457. examples.Add(group);
  458. }
  459. examplesDirectory = Path.GetDirectoryName(examplesDirectory);
  460. return examples;
  461. }
  462. /************************************************************************************************************************/
  463. public static ExampleGroup Gather(string rootDirectory, string directory)
  464. {
  465. var files = Directory.GetFiles(directory, "*.unity", SearchOption.AllDirectories);
  466. List<SceneAsset> scenes = null;
  467. for (int j = 0; j < files.Length; j++)
  468. {
  469. var scene = AssetDatabase.LoadAssetAtPath<SceneAsset>(files[j]);
  470. if (scene != null)
  471. {
  472. if (scenes == null)
  473. scenes = new List<SceneAsset>();
  474. scenes.Add(scene);
  475. }
  476. }
  477. if (scenes == null)
  478. return null;
  479. return new ExampleGroup(rootDirectory, directory, scenes);
  480. }
  481. /************************************************************************************************************************/
  482. public ExampleGroup(string rootDirectory, string directory, List<SceneAsset> scenes)
  483. {
  484. var start = rootDirectory.Length + 1;
  485. Name = start < directory.Length ?
  486. directory.Substring(start, directory.Length - start) :
  487. Path.GetFileName(directory);
  488. Scenes = scenes;
  489. start = directory.Length + 1;
  490. for (int i = 0; i < scenes.Count; i++)
  491. {
  492. directory = AssetDatabase.GetAssetPath(scenes[i]);
  493. directory = directory.Substring(start, directory.Length - start);
  494. directory = Path.GetDirectoryName(directory);
  495. Directories.Add(directory);
  496. }
  497. }
  498. /************************************************************************************************************************/
  499. public static void DoExampleGUI(List<ExampleGroup> examples)
  500. {
  501. if (examples == null)
  502. return;
  503. for (int i = 0; i < examples.Count; i++)
  504. examples[i].DoExampleGUI();
  505. }
  506. public void DoExampleGUI()
  507. {
  508. EditorGUI.indentLevel++;
  509. using (ObjectPool.Disposable.AcquireContent(out var label, Name, null, false))
  510. _IsExpanded = EditorGUILayout.Foldout(_IsExpanded, label, true);
  511. if (_IsExpanded)
  512. {
  513. EditorGUI.indentLevel++;
  514. for (int i = 0; i < Scenes.Count; i++)
  515. EditorGUILayout.ObjectField(Directories[i], Scenes[i], typeof(SceneAsset), false);
  516. EditorGUI.indentLevel--;
  517. }
  518. EditorGUI.indentLevel--;
  519. }
  520. /************************************************************************************************************************/
  521. }
  522. /************************************************************************************************************************/
  523. }
  524. /************************************************************************************************************************/
  525. #endregion
  526. /************************************************************************************************************************/
  527. }
  528. }
  529. #endif