EditorApplication.cs 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Runtime.CompilerServices;
  6. using System.IO;
  7. using bs;
  8. namespace bs.Editor
  9. {
  10. /** @addtogroup General
  11. * @{
  12. */
  13. /// <summary>
  14. /// Available tools in the scene view.
  15. /// </summary>
  16. public enum SceneViewTool
  17. {
  18. View,
  19. Move,
  20. Rotate,
  21. Scale
  22. }
  23. /// <summary>
  24. /// Pivot mode used by the scene view tools.
  25. /// </summary>
  26. public enum HandlePivotMode
  27. {
  28. Center,
  29. Pivot
  30. }
  31. /// <summary>
  32. /// Coordinate mode used by the scene view tools.
  33. /// </summary>
  34. public enum HandleCoordinateMode
  35. {
  36. Local,
  37. World
  38. }
  39. /// <summary>
  40. /// Manages various generic and global settings relating to the editor.
  41. /// </summary>
  42. public class EditorApplication
  43. {
  44. internal const string CutBinding = "Cut";
  45. internal const string CopyBinding = "Copy";
  46. internal const string RenameBinding = "Rename";
  47. internal const string DuplicateBinding = "Duplicate";
  48. internal const string DeleteBinding = "Delete";
  49. internal const string PasteBinding = "Paste";
  50. /// <summary>
  51. /// Determines the active tool shown in the scene view.
  52. /// </summary>
  53. public static SceneViewTool ActiveSceneTool
  54. {
  55. get { return EditorSettings.ActiveSceneTool; }
  56. set { EditorSettings.ActiveSceneTool = value; }
  57. }
  58. /// <summary>
  59. /// Determines the coordinate mode used by the tools in the scene view.
  60. /// </summary>
  61. public static HandleCoordinateMode ActiveCoordinateMode
  62. {
  63. get { return EditorSettings.ActiveCoordinateMode; }
  64. set { EditorSettings.ActiveCoordinateMode = value; }
  65. }
  66. /// <summary>
  67. /// Determines the pivot mode used by the tools in the scene view.
  68. /// </summary>
  69. public static HandlePivotMode ActivePivotMode
  70. {
  71. get { return EditorSettings.ActivePivotMode; }
  72. set { EditorSettings.ActivePivotMode = value; }
  73. }
  74. /// <summary>
  75. /// Camera used for rendering the scene view.
  76. /// </summary>
  77. public static Camera SceneViewCamera
  78. {
  79. get
  80. {
  81. SceneWindow sceneWindow = EditorWindow.GetWindow<SceneWindow>();
  82. return sceneWindow?.Camera.Camera;
  83. }
  84. }
  85. /// <summary>
  86. /// Absolute path to the folder containing the currently open project.
  87. /// </summary>
  88. public static string ProjectPath { get { return Internal_GetProjectPath(); } }
  89. /// <summary>
  90. /// Name of the currently open project.
  91. /// </summary>
  92. public static string ProjectName { get { return Internal_GetProjectName(); } }
  93. /// <summary>
  94. /// Checks is any project currently loaded.
  95. /// </summary>
  96. public static bool IsProjectLoaded { get { return Internal_GetProjectLoaded(); } }
  97. /// <summary>
  98. /// Determines is the game currently running in the editor, or is it stopped or paused. Setting this value to false
  99. /// will stop the game, but if you just want to pause it use <see cref="IsPaused"/> property.
  100. /// </summary>
  101. public static bool IsPlaying
  102. {
  103. get { return Internal_GetIsPlaying(); }
  104. set
  105. {
  106. ToggleToolbarItem("Play", value);
  107. ToggleToolbarItem("Pause", false);
  108. if (!value)
  109. Selection.SceneObject = null;
  110. else
  111. {
  112. if (EditorSettings.GetBool(LogWindow.CLEAR_ON_PLAY_KEY, true))
  113. {
  114. Debug.Clear();
  115. LogWindow log = EditorWindow.GetWindow<LogWindow>();
  116. if (log != null)
  117. log.Refresh();
  118. }
  119. }
  120. Internal_SetIsPlaying(value);
  121. }
  122. }
  123. /// <summary>
  124. /// Determines if the game is currently running in the editor, but paused. If the game is stopped and not running
  125. /// this will return false. If the game is not running and this is enabled, the game will start running but be
  126. /// immediately paused.
  127. /// </summary>
  128. public static bool IsPaused
  129. {
  130. get { return Internal_GetIsPaused(); }
  131. set
  132. {
  133. ToggleToolbarItem("Play", !value);
  134. ToggleToolbarItem("Pause", value);
  135. Internal_SetIsPaused(value);
  136. }
  137. }
  138. /// <summary>
  139. /// Returns true if the game is currently neither running nor paused. Use <see cref="IsPlaying"/> or
  140. /// <see cref="IsPaused"/> to actually change these states.
  141. /// </summary>
  142. public static bool IsStopped
  143. {
  144. get { return !IsPlaying && !IsPaused; }
  145. }
  146. /// <summary>
  147. /// Checks whether the editor currently has focus.
  148. /// </summary>
  149. public static bool HasFocus
  150. {
  151. get { return Internal_HasFocus(); }
  152. }
  153. /// <summary>
  154. /// Returns true if the editor is waiting on a scene to be asynchronously loaded.
  155. /// </summary>
  156. public static bool IsSceneLoading
  157. {
  158. get
  159. {
  160. if (lastLoadedScene != null)
  161. return !lastLoadedScene.IsLoaded;
  162. return false;
  163. }
  164. }
  165. /// <summary>
  166. /// Returns the load progress of the scene that's being asynchronously loaded
  167. /// </summary>
  168. public static float SceneLoadProgress
  169. {
  170. get
  171. {
  172. if (lastLoadedScene != null)
  173. return Resources.GetLoadProgress(lastLoadedScene);
  174. return 0.0f;
  175. }
  176. }
  177. /// <summary>
  178. /// Triggered right before the project is being saved.
  179. /// </summary>
  180. public static event Action OnProjectSave;
  181. /// <summary>
  182. /// Render target that the main camera in the scene (if any) will render its view to. This generally means the main
  183. /// game window when running standalone, or the Game viewport when running in editor.
  184. /// </summary>
  185. internal static RenderTarget MainRenderTarget
  186. {
  187. set
  188. {
  189. IntPtr rtPtr = IntPtr.Zero;
  190. if (value != null)
  191. rtPtr = value.GetCachedPtr();
  192. Internal_SetMainRenderTarget(rtPtr);
  193. }
  194. }
  195. /// <summary>
  196. /// Returns an object that can be used for storing data that persists throughout the entire editor session.
  197. /// </summary>
  198. internal static EditorPersistentData PersistentData
  199. {
  200. get { return persistentData; }
  201. }
  202. /// <summary>
  203. /// Returns the path where the script compiler is located at.
  204. /// </summary>
  205. internal static string CompilerPath { get { return Internal_GetCompilerPath(); } }
  206. /// <summary>
  207. /// Returns the absolute path to the executable capable of executing managed assemblies.
  208. /// </summary>
  209. internal static string MonoExecPath { get { return Internal_GetMonoExecPath(); } }
  210. /// <summary>
  211. /// Returns the path to the folder where the custom script assemblies are located at.
  212. /// </summary>
  213. internal static string ScriptAssemblyPath { get { return Internal_GetScriptAssemblyPath(); } }
  214. /// <summary>
  215. /// Returns the path to the folder where the .NET framework assemblies are located at.
  216. /// </summary>
  217. internal static string FrameworkAssemblyPath { get { return Internal_GetFrameworkAssemblyPath(); } }
  218. /// <summary>
  219. /// Name of the builtin assembly containing engine specific types.
  220. /// </summary>
  221. internal static string EngineAssemblyName { get { return Internal_GetEngineAssemblyName(); } }
  222. /// <summary>
  223. /// Name of the builtin assembly containing editor specific types.
  224. /// </summary>
  225. internal static string EditorAssemblyName { get { return Internal_GetEditorAssemblyName(); } }
  226. /// <summary>
  227. /// Name of the custom assembly compiled from non-editor scripts within the project.
  228. /// </summary>
  229. internal static string ScriptGameAssemblyName { get { return Internal_GetScriptGameAssemblyName(); } }
  230. /// <summary>
  231. /// Name of the custom assembly compiled from editor scripts within the project.
  232. /// </summary>
  233. internal static string ScriptEditorAssemblyName { get { return Internal_GetScriptEditorAssemblyName(); } }
  234. /// <summary>
  235. /// Returns the path to the folder where the builtin release script assemblies are located at.
  236. /// </summary>
  237. internal static string BuiltinReleaseAssemblyPath { get { return Internal_GetBuiltinReleaseAssemblyPath(); } }
  238. /// <summary>
  239. /// Returns the path to the folder where the builtin debug script assemblies are located at.
  240. /// </summary>
  241. internal static string BuiltinDebugAssemblyPath { get { return Internal_GetBuiltinDebugAssemblyPath(); } }
  242. internal static VirtualButton CutKey = new VirtualButton(CutBinding);
  243. internal static VirtualButton CopyKey = new VirtualButton(CopyBinding);
  244. internal static VirtualButton PasteKey = new VirtualButton(PasteBinding);
  245. internal static VirtualButton RenameKey = new VirtualButton(RenameBinding);
  246. internal static VirtualButton DuplicateKey = new VirtualButton(DuplicateBinding);
  247. internal static VirtualButton DeleteKey = new VirtualButton(DeleteBinding);
  248. private static FolderMonitor monitor;
  249. private static ScriptCodeManager codeManager;
  250. private static RRef<Prefab> lastLoadedScene;
  251. private static bool sceneDirty;
  252. private static bool unitTestsExecuted;
  253. private static EditorPersistentData persistentData;
  254. private static bool delayUnloadProject;
  255. private static Action delayUnloadCallback;
  256. #pragma warning disable 0414
  257. private static EditorApplication instance;
  258. #pragma warning restore 0414
  259. /// <summary>
  260. /// Constructs a new editor application. Called at editor start-up by the runtime, and any time assembly refresh
  261. /// occurrs.
  262. /// </summary>
  263. internal EditorApplication()
  264. {
  265. instance = this;
  266. codeManager = new ScriptCodeManager();
  267. const string soName = "EditorPersistentData";
  268. SceneObject so = Scene.Root.FindChild(soName);
  269. if (so == null)
  270. so = new SceneObject(soName, true);
  271. persistentData = so.GetComponent<EditorPersistentData>();
  272. if (persistentData == null)
  273. persistentData = so.AddComponent<EditorPersistentData>();
  274. // Register controls
  275. InputConfiguration inputConfig = VirtualInput.KeyConfig;
  276. inputConfig.RegisterButton(SceneCamera.MoveForwardBinding, ButtonCode.W);
  277. inputConfig.RegisterButton(SceneCamera.MoveBackBinding, ButtonCode.S);
  278. inputConfig.RegisterButton(SceneCamera.MoveLeftBinding, ButtonCode.A);
  279. inputConfig.RegisterButton(SceneCamera.MoveRightBinding, ButtonCode.D);
  280. inputConfig.RegisterButton(SceneCamera.MoveUpBinding, ButtonCode.E);
  281. inputConfig.RegisterButton(SceneCamera.MoveDownBinding, ButtonCode.Q);
  282. inputConfig.RegisterButton(SceneCamera.MoveForwardBinding, ButtonCode.Up);
  283. inputConfig.RegisterButton(SceneCamera.MoveBackBinding, ButtonCode.Down);
  284. inputConfig.RegisterButton(SceneCamera.MoveLeftBinding, ButtonCode.Left);
  285. inputConfig.RegisterButton(SceneCamera.MoveRightBinding, ButtonCode.Right);
  286. inputConfig.RegisterButton(SceneCamera.FastMoveBinding, ButtonCode.LeftShift);
  287. inputConfig.RegisterButton(SceneCamera.RotateBinding, ButtonCode.MouseRight);
  288. inputConfig.RegisterButton(SceneCamera.PanBinding, ButtonCode.MouseMiddle);
  289. inputConfig.RegisterAxis(SceneCamera.HorizontalAxisBinding, InputAxis.MouseX);
  290. inputConfig.RegisterAxis(SceneCamera.VerticalAxisBinding, InputAxis.MouseY);
  291. inputConfig.RegisterAxis(SceneCamera.ScrollAxisBinding, InputAxis.MouseZ);
  292. inputConfig.RegisterButton(SceneWindow.ToggleProfilerOverlayBinding, ButtonCode.P, ButtonModifier.CtrlAlt);
  293. inputConfig.RegisterButton(SceneWindow.ViewToolBinding, ButtonCode.Q);
  294. inputConfig.RegisterButton(SceneWindow.FrameBinding, ButtonCode.F);
  295. inputConfig.RegisterButton(SceneWindow.MoveToolBinding, ButtonCode.W);
  296. inputConfig.RegisterButton(SceneWindow.RotateToolBinding, ButtonCode.E);
  297. inputConfig.RegisterButton(SceneWindow.ScaleToolBinding, ButtonCode.R);
  298. inputConfig.RegisterButton(CutBinding, ButtonCode.X, ButtonModifier.Ctrl);
  299. inputConfig.RegisterButton(CopyBinding, ButtonCode.C, ButtonModifier.Ctrl);
  300. inputConfig.RegisterButton(PasteBinding, ButtonCode.V, ButtonModifier.Ctrl);
  301. inputConfig.RegisterButton(DuplicateBinding, ButtonCode.D, ButtonModifier.Ctrl);
  302. inputConfig.RegisterButton(DeleteBinding, ButtonCode.Delete);
  303. inputConfig.RegisterButton(RenameBinding, ButtonCode.F2);
  304. if (IsProjectLoaded)
  305. {
  306. monitor = new FolderMonitor(ProjectLibrary.ResourceFolder);
  307. monitor.OnAdded += OnAssetModified;
  308. monitor.OnRemoved += OnAssetModified;
  309. monitor.OnModified += OnAssetModified;
  310. }
  311. }
  312. /// <summary>
  313. /// Triggered when the folder monitor detects an asset in the monitored folder was modified.
  314. /// </summary>
  315. /// <param name="path">Path to the modified file or folder.</param>
  316. private static void OnAssetModified(string path)
  317. {
  318. ProjectLibrary.Refresh(path);
  319. }
  320. /// <summary>
  321. /// Called every frame by the runtime.
  322. /// </summary>
  323. internal void OnEditorUpdate()
  324. {
  325. // Update managers
  326. ProjectLibrary.Update();
  327. codeManager.Update();
  328. if (delayUnloadProject)
  329. {
  330. delayUnloadProject = false;
  331. UnloadProject();
  332. delayUnloadCallback?.Invoke();
  333. delayUnloadCallback = null;
  334. }
  335. }
  336. /// <summary>
  337. /// Manually triggers a global shortcut.
  338. /// </summary>
  339. /// <param name="btn">Button for the shortcut. If this doesn't correspond to any shortcut, it is ignored.</param>
  340. internal static void TriggerGlobalShortcut(VirtualButton btn)
  341. {
  342. IGlobalShortcuts window = null;
  343. if (btn != PasteKey)
  344. {
  345. // The system ensures elsewhere that only either a resource or a scene object is selected, but not both
  346. if (Selection.ResourcePaths.Length > 0)
  347. {
  348. window = EditorWindow.GetWindow<LibraryWindow>();
  349. }
  350. else if (Selection.SceneObjects.Length > 0)
  351. {
  352. window = EditorWindow.GetWindow<HierarchyWindow>();
  353. if (window == null)
  354. window = EditorWindow.GetWindow<SceneWindow>();
  355. }
  356. if (window != null)
  357. {
  358. if (btn == CopyKey)
  359. window.OnCopyPressed();
  360. else if (btn == CutKey)
  361. window.OnCutPressed();
  362. else if (btn == PasteKey)
  363. window.OnPastePressed();
  364. else if (btn == DuplicateKey)
  365. window.OnDuplicatePressed();
  366. else if (btn == RenameKey)
  367. window.OnRenamePressed();
  368. else if (btn == DeleteKey)
  369. window.OnDeletePressed();
  370. }
  371. }
  372. else
  373. {
  374. HierarchyWindow hierarchy = EditorWindow.GetWindow<HierarchyWindow>();
  375. if (hierarchy != null && hierarchy.HasFocus)
  376. window = hierarchy;
  377. else
  378. {
  379. LibraryWindow library = EditorWindow.GetWindow<LibraryWindow>();
  380. if (library != null && library.HasFocus)
  381. window = library;
  382. }
  383. if (window != null)
  384. window.OnPastePressed();
  385. }
  386. }
  387. /// <summary>
  388. /// Creates a new empty scene.
  389. /// </summary>
  390. [MenuItem("File/New Scene", 10051, true)]
  391. private static void NewScene()
  392. {
  393. LoadScene(null);
  394. }
  395. /// <summary>
  396. /// Opens a dialog that allows the user to select a new prefab to load as the current scene. If current scene
  397. /// is modified the user is offered a chance to save it.
  398. /// </summary>
  399. [MenuItem("File/Open Scene", ButtonModifier.Ctrl, ButtonCode.L, 10050)]
  400. private static void LoadScene()
  401. {
  402. string[] scenePaths;
  403. if (BrowseDialog.OpenFile(ProjectLibrary.ResourceFolder, "", false, out scenePaths))
  404. {
  405. if (scenePaths.Length > 0)
  406. LoadScene(scenePaths[0]);
  407. }
  408. }
  409. /// <summary>
  410. /// Opens a dialog to allows the user to select a location where to save the current scene. If scene was previously
  411. /// saved it is instead automatically saved at the last location.
  412. /// </summary>
  413. public static void SaveScene(Action onSuccess = null, Action onFailure = null)
  414. {
  415. if (!Scene.ActiveSceneUUID.IsEmpty())
  416. {
  417. string scenePath = ProjectLibrary.GetPath(Scene.ActiveSceneUUID);
  418. if (!string.IsNullOrEmpty(scenePath))
  419. {
  420. if (Scene.IsGenericPrefab)
  421. {
  422. SaveGenericPrefab(onSuccess, onFailure);
  423. }
  424. else
  425. {
  426. SaveScene(scenePath);
  427. if (onSuccess != null)
  428. onSuccess();
  429. }
  430. }
  431. else
  432. SaveSceneAs(onSuccess, onFailure);
  433. }
  434. else
  435. SaveSceneAs(onSuccess, onFailure);
  436. }
  437. /// <summary>
  438. /// Opens a dialog to allows the user to select a location where to save the current scene.
  439. /// </summary>
  440. public static void SaveSceneAs(Action onSuccess = null, Action onFailure = null)
  441. {
  442. string scenePath = "";
  443. if (BrowseDialog.SaveFile(ProjectLibrary.ResourceFolder, "*.prefab", out scenePath))
  444. {
  445. if (!PathEx.IsPartOf(scenePath, ProjectLibrary.ResourceFolder))
  446. {
  447. DialogBox.Open("Error", "The location must be inside the Resources folder of the project.",
  448. DialogBox.Type.OK,
  449. x =>
  450. {
  451. if (onFailure != null)
  452. onFailure();
  453. });
  454. }
  455. else
  456. {
  457. // TODO - If path points to an existing non-scene asset or folder I should delete it otherwise
  458. // Internal_SaveScene will silently fail.
  459. scenePath = Path.ChangeExtension(scenePath, ".prefab");
  460. SaveScene(scenePath);
  461. }
  462. }
  463. else
  464. {
  465. // User canceled, so technically a success
  466. if (onSuccess != null)
  467. onSuccess();
  468. }
  469. }
  470. /// <summary>
  471. /// Loads a prefab as the current scene at the specified path. If current scene is modified the user is offered a
  472. /// chance to save it.
  473. /// </summary>
  474. /// <param name="path">Path to a valid prefab relative to the resource folder. If path is empty a brand new
  475. /// scene will be loaded.</param>
  476. public static void LoadScene(string path)
  477. {
  478. Action<string> continueLoad =
  479. (scenePath) =>
  480. {
  481. if (string.IsNullOrEmpty(path))
  482. {
  483. Scene.Clear();
  484. lastLoadedScene = null;
  485. }
  486. else
  487. lastLoadedScene = Scene.LoadAsync(path);
  488. SetSceneDirty(false);
  489. ProjectSettings.LastOpenScene = scenePath;
  490. ProjectSettings.Save();
  491. };
  492. Action<DialogBox.ResultType> dialogCallback =
  493. (result) =>
  494. {
  495. if (result == DialogBox.ResultType.Yes)
  496. {
  497. SaveScene();
  498. continueLoad(path);
  499. }
  500. else if (result == DialogBox.ResultType.No)
  501. continueLoad(path);
  502. };
  503. if (IsSceneModified())
  504. {
  505. DialogBox.Open("Warning", "You current scene has modifications. Do you wish to save them first?",
  506. DialogBox.Type.YesNoCancel, dialogCallback);
  507. }
  508. else
  509. continueLoad(path);
  510. }
  511. /// <summary>
  512. /// Saves the currently loaded scene to the specified path.
  513. /// </summary>
  514. /// <param name="path">Path relative to the resource folder. This can be the path to the existing scene
  515. /// prefab if it just needs updating. </param>
  516. internal static void SaveScene(string path)
  517. {
  518. Prefab scene = Internal_SaveScene(path);
  519. Scene.SetActive(scene);
  520. ProjectLibrary.Refresh(true);
  521. SetSceneDirty(false);
  522. }
  523. /// <summary>
  524. /// Attempts to save the current scene by applying the changes to a prefab, instead of saving it as a brand new
  525. /// scene. This is necessary for generic prefabs that have don't have a scene root included in the prefab. If the
  526. /// object added any other objects to the root, or has moved or deleted the original generic prefab the user
  527. /// will be asked to save the scene normally, creating a brand new prefab.
  528. /// </summary>
  529. private static void SaveGenericPrefab(Action onSuccess = null, Action onFailure = null)
  530. {
  531. // Find prefab root
  532. SceneObject root = null;
  533. int numChildren = Scene.Root.GetNumChildren();
  534. int numNormalChildren = 0;
  535. for (int i = 0; i < numChildren; i++)
  536. {
  537. SceneObject child = Scene.Root.GetChild(i);
  538. if (EditorUtility.IsInternal(child))
  539. continue;
  540. UUID prefabUUID = PrefabUtility.GetPrefabUUID(child);
  541. if (prefabUUID == Scene.ActiveSceneUUID)
  542. root = child;
  543. // If user added any other prefabs other than the initial one, the scene no longer represents a generic
  544. // prefab (as we can now longer save it by applying changes only to that prefab)
  545. numNormalChildren++;
  546. if (numNormalChildren > 1)
  547. {
  548. root = null;
  549. break;
  550. }
  551. }
  552. if (root != null)
  553. {
  554. PrefabUtility.ApplyPrefab(root, false);
  555. ProjectLibrary.Refresh(true);
  556. SetSceneDirty(false);
  557. if (onSuccess != null)
  558. onSuccess();
  559. }
  560. else
  561. {
  562. SaveSceneAs(onSuccess, onFailure);
  563. }
  564. }
  565. /// <summary>
  566. /// Checks does the folder at the provieded path contain a valid project.
  567. /// </summary>
  568. /// <param name="path">Absolute path to the root project folder.</param>
  569. /// <returns>True if the folder contains a valid project.</returns>
  570. public static bool IsValidProject(string path)
  571. {
  572. return Internal_IsValidProject(path);
  573. }
  574. /// <summary>
  575. /// Contains a new project in the provided folder.
  576. /// </summary>
  577. /// <param name="path">Absolute path to the folder to create the project in. Name of this folder will be used as the
  578. /// project's name.</param>
  579. public static void CreateProject(string path)
  580. {
  581. Internal_CreateProject(path);
  582. }
  583. /// <summary>
  584. /// Wrapper for menu items for <see cref="SaveScene(Action, Action)"/> method
  585. /// </summary>
  586. [MenuItem("File/Save Scene", ButtonModifier.Ctrl, ButtonCode.S, 10049)]
  587. [ToolbarItem("Save Scene", ToolbarIcon.SaveScene, "Save scene (Ctrl + S)", 1998)]
  588. private static void SaveSceneMenu()
  589. {
  590. SaveScene();
  591. }
  592. /// <summary>
  593. /// Wrapper for menu items for <see cref="SaveSceneAs(Action, Action)"/> method
  594. /// </summary>
  595. [MenuItem("File/Save Scene As", 10048)]
  596. private static void SaveSceneAsMenu()
  597. {
  598. SaveSceneAs();
  599. }
  600. /// <summary>
  601. /// Opens a Project Window allowing you to browse for or create a project.
  602. /// </summary>
  603. [MenuItem("File/Open Project", 10100)]
  604. [ToolbarItem("Open Project", ToolbarIcon.OpenProject, "Project manager", 2000)]
  605. public static void BrowseForProject()
  606. {
  607. ProjectWindow.Open();
  608. }
  609. /// <summary>
  610. /// Saves all data in the currently open project.
  611. /// </summary>
  612. [MenuItem("File/Save Project", 10099)]
  613. [ToolbarItem("Save Project", ToolbarIcon.SaveProject, "Save project", 1999)]
  614. public static void SaveProject()
  615. {
  616. OnProjectSave?.Invoke();
  617. // Apply changes to any animation clips edited using the animation editor
  618. foreach (var KVP in persistentData.dirtyAnimClips)
  619. KVP.Value.SaveToClip();
  620. // Save all dirty resources to disk
  621. foreach (var KVP in persistentData.dirtyResources)
  622. {
  623. UUID resourceUUID = KVP.Key;
  624. string path = ProjectLibrary.GetPath(resourceUUID);
  625. if (!IsNative(path))
  626. continue; // Imported resources can't be changed
  627. Resource resource = ProjectLibrary.Load<Resource>(path);
  628. if(resource != null)
  629. ProjectLibrary.Save(resource);
  630. }
  631. persistentData.dirtyAnimClips.Clear();
  632. persistentData.dirtyResources.Clear();
  633. SetStatusProject(false);
  634. Internal_SaveProject();
  635. }
  636. /// <summary>
  637. /// Loads the project at the specified path. This method executes asynchronously.
  638. /// </summary>
  639. /// <param name="path">Absolute path to the project's root folder.</param>
  640. public static void LoadProject(string path)
  641. {
  642. if (IsProjectLoaded && path == ProjectPath)
  643. return;
  644. if (!Internal_IsValidProject(path))
  645. {
  646. Debug.LogWarning("Provided path: \"" + path + "\" is not a valid project.");
  647. return;
  648. }
  649. if (IsProjectLoaded)
  650. TryUnloadProject(() => Internal_LoadProject(path));
  651. else
  652. Internal_LoadProject(path); // Triggers Internal_OnProjectLoaded when done
  653. }
  654. /// <summary>
  655. /// Closes the editor.
  656. /// </summary>
  657. public static void Quit()
  658. {
  659. Internal_Quit();
  660. }
  661. /// <summary>
  662. /// Toggles an existing toolbar button into an on or off state which changes the visuals of the button.
  663. /// </summary>
  664. /// <param name="name">Name of the existing button to toggle</param>
  665. /// <param name="on">True to toggle on, false to toggle off (default)</param>
  666. public static void ToggleToolbarItem(string name, bool on)
  667. {
  668. Internal_ToggleToolbarItem(name, on);
  669. }
  670. /// <summary>
  671. /// Opens a folder in the default external application.
  672. /// </summary>
  673. /// <param name="path">Absolute path to the folder to open.</param>
  674. public static void OpenFolder(string path)
  675. {
  676. Internal_OpenFolder(path);
  677. }
  678. /// <summary>
  679. /// Marks a resource as dirty so that it may be saved the next time the project is saved. Optionally you may also
  680. /// call <see cref="ProjectLibrary.Save"/> to save it immediately.
  681. /// </summary>
  682. /// <param name="resource">Resource to mark as dirty</param>
  683. public static void SetDirty(Resource resource)
  684. {
  685. if (resource == null)
  686. return;
  687. SetStatusProject(true);
  688. persistentData.dirtyResources[resource.UUID] = true;
  689. }
  690. /// <summary>
  691. /// Marks the current project dirty (requires saving in order for changes not to be lost).
  692. /// </summary>
  693. public static void SetProjectDirty()
  694. {
  695. SetStatusProject(true);
  696. }
  697. /// <summary>
  698. /// Marks the current scene as dirty.
  699. /// </summary>
  700. public static void SetSceneDirty()
  701. {
  702. SetSceneDirty(true);
  703. }
  704. /// <summary>
  705. /// Marks the current scene as clean or dirty.
  706. /// </summary>
  707. /// <param name="dirty">Should the scene be marked as clean or dirty.</param>
  708. internal static void SetSceneDirty(bool dirty)
  709. {
  710. sceneDirty = dirty;
  711. SetStatusScene(Scene.ActiveSceneName, dirty);
  712. if (!dirty && !Scene.ActiveSceneUUID.IsEmpty())
  713. persistentData.dirtyResources.Remove(Scene.ActiveSceneUUID);
  714. }
  715. /// <summary>
  716. /// Checks is the specific resource dirty and needs saving.
  717. /// </summary>
  718. /// <param name="resource">Resource to check.</param>
  719. /// <returns>True if the resource requires saving, false otherwise.</returns>
  720. public static bool IsDirty(Resource resource)
  721. {
  722. return persistentData.dirtyResources.ContainsKey(resource.UUID);
  723. }
  724. /// <summary>
  725. /// Checks does the path represent a native resource.
  726. /// </summary>
  727. /// <param name="path">Filename or path to check.</param>
  728. /// <returns>True if the path represents a native resource.</returns>
  729. public static bool IsNative(string path)
  730. {
  731. string extension = Path.GetExtension(path);
  732. return extension == ".asset" || extension == ".prefab";
  733. }
  734. /// <summary>
  735. /// Attempts to unload the currently loaded project. Offers the user a chance to save the current scene if it is
  736. /// modified. Automatically saves all project data before unloading.
  737. /// </summary>
  738. /// <param name="onDone">Callback to trigger when project project unload is done.</param>
  739. private static void TryUnloadProject(Action onDone)
  740. {
  741. if (delayUnloadProject)
  742. return;
  743. AskToSaveSceneAndContinue(
  744. () =>
  745. {
  746. if (ProjectLibrary.ImportInProgress)
  747. {
  748. ConfirmImportInProgressWindow.Show();
  749. delayUnloadCallback = onDone;
  750. delayUnloadProject = true;
  751. }
  752. else
  753. {
  754. UnloadProject();
  755. onDone?.Invoke();
  756. }
  757. });
  758. }
  759. /// <summary>
  760. /// Unloads the currently loaded project, without making any checks or requiring confirmation.
  761. /// </summary>
  762. private static void UnloadProject()
  763. {
  764. Scene.Clear();
  765. if (monitor != null)
  766. {
  767. monitor.Destroy();
  768. monitor = null;
  769. }
  770. LibraryWindow window = EditorWindow.GetWindow<LibraryWindow>();
  771. if (window != null)
  772. window.Reset();
  773. SetSceneDirty(false);
  774. Internal_UnloadProject();
  775. SetStatusProject(false);
  776. }
  777. /// <summary>
  778. /// Checks if the current scene is modified and asks the user to save the scene if it is. Triggers the
  779. /// <see cref="next"/> callback when done, unless user cancels the operation.
  780. /// </summary>
  781. /// <param name="next">Callback to trigger after this method finishes.</param>
  782. internal static void AskToSaveSceneAndContinue(Action next)
  783. {
  784. Action trySaveScene = null;
  785. trySaveScene = () =>
  786. {
  787. SaveScene(next, trySaveScene);
  788. };
  789. Action<DialogBox.ResultType> dialogCallback =
  790. (result) =>
  791. {
  792. if (result == DialogBox.ResultType.Yes)
  793. trySaveScene();
  794. else if (result == DialogBox.ResultType.No)
  795. next?.Invoke();
  796. };
  797. if (IsSceneModified())
  798. {
  799. DialogBox.Open("Warning", "You current scene has modifications. Do you wish to save them first?",
  800. DialogBox.Type.YesNoCancel, dialogCallback);
  801. }
  802. else
  803. next?.Invoke();
  804. }
  805. /// <summary>
  806. /// Reloads all script assemblies in case they were modified. This action is delayed and will be executed
  807. /// at the beginning of the next frame.
  808. /// </summary>
  809. public static void ReloadAssemblies()
  810. {
  811. Internal_ReloadAssemblies();
  812. }
  813. /// <summary>
  814. /// Changes the scene displayed on the status bar.
  815. /// </summary>
  816. /// <param name="name">Name of the scene.</param>
  817. /// <param name="modified">Whether to display the scene as modified or not.</param>
  818. private static void SetStatusScene(string name, bool modified)
  819. {
  820. Internal_SetStatusScene(name, modified);
  821. }
  822. /// <summary>
  823. /// Changes the project state displayed on the status bar.
  824. /// </summary>
  825. /// <param name="modified">Whether to display the project as modified or not.</param>
  826. private static void SetStatusProject(bool modified)
  827. {
  828. Internal_SetStatusProject(modified);
  829. }
  830. /// <summary>
  831. /// Displays or hides the "compilation in progress" visual on the status bar.
  832. /// </summary>
  833. /// <param name="compiling">True to display the visual, false otherwise.</param>
  834. internal static void SetStatusCompiling(bool compiling)
  835. {
  836. Internal_SetStatusCompiling(compiling);
  837. }
  838. /// <summary>
  839. /// Displays or hides the "import in progress" visuals on the status bar and updates the related progress bar.
  840. /// </summary>
  841. /// <param name="importing">True to display the visual, false otherwise.</param>
  842. /// <param name="percent">Percent in range [0, 1] to display on the progress bar.</param>
  843. internal static void SetStatusImporting(bool importing, float percent)
  844. {
  845. Internal_SetStatusImporting(importing, percent);
  846. }
  847. /// <summary>
  848. /// Checks did we make any modifications to the scene since it was last saved.
  849. /// </summary>
  850. /// <returns>True if the scene was never saved, or was modified after last save.</returns>
  851. public static bool IsSceneModified()
  852. {
  853. return sceneDirty;
  854. }
  855. /// <summary>
  856. /// Runs a single frame of the game and pauses it. If the game is not currently running it will be started.
  857. /// </summary>
  858. public static void FrameStep()
  859. {
  860. if (IsStopped)
  861. {
  862. if (EditorSettings.GetBool(LogWindow.CLEAR_ON_PLAY_KEY, true))
  863. {
  864. Debug.Clear();
  865. LogWindow log = EditorWindow.GetWindow<LogWindow>();
  866. if (log != null)
  867. log.Refresh();
  868. }
  869. }
  870. ToggleToolbarItem("Play", false);
  871. ToggleToolbarItem("Pause", true);
  872. Internal_FrameStep();
  873. }
  874. /// <summary>
  875. /// Executes any editor-specific unit tests. This should be called after a project is loaded if possible.
  876. /// </summary>
  877. private static void RunUnitTests()
  878. {
  879. #if DEBUG
  880. Internal_RunUnitTests();
  881. #endif
  882. }
  883. /// <summary>
  884. /// Triggered by the runtime when <see cref="LoadProject"/> method completes.
  885. /// </summary>
  886. private static void Internal_OnProjectLoaded()
  887. {
  888. SetStatusProject(false);
  889. if (!unitTestsExecuted)
  890. {
  891. RunUnitTests();
  892. unitTestsExecuted = true;
  893. }
  894. if (!IsProjectLoaded)
  895. {
  896. ProjectWindow.Open();
  897. return;
  898. }
  899. string projectPath = ProjectPath;
  900. RecentProject[] recentProjects = EditorSettings.RecentProjects;
  901. bool foundPath = false;
  902. for (int i = 0; i < recentProjects.Length; i++)
  903. {
  904. if (PathEx.Compare(recentProjects[i].path, projectPath))
  905. {
  906. recentProjects[i].accessTimestamp = (ulong)DateTime.Now.Ticks;
  907. EditorSettings.RecentProjects = recentProjects;
  908. foundPath = true;
  909. break;
  910. }
  911. }
  912. if (!foundPath)
  913. {
  914. List<RecentProject> extendedRecentProjects = new List<RecentProject>();
  915. extendedRecentProjects.AddRange(recentProjects);
  916. RecentProject newProject = new RecentProject();
  917. newProject.path = projectPath;
  918. newProject.accessTimestamp = (ulong)DateTime.Now.Ticks;
  919. extendedRecentProjects.Add(newProject);
  920. EditorSettings.RecentProjects = extendedRecentProjects.ToArray();
  921. }
  922. EditorSettings.LastOpenProject = projectPath;
  923. EditorSettings.Save();
  924. ProjectLibrary.Refresh();
  925. if(monitor != null)
  926. {
  927. monitor.Destroy();
  928. monitor = null;
  929. }
  930. monitor = new FolderMonitor(ProjectLibrary.ResourceFolder);
  931. monitor.OnAdded += OnAssetModified;
  932. monitor.OnRemoved += OnAssetModified;
  933. monitor.OnModified += OnAssetModified;
  934. if (!string.IsNullOrWhiteSpace(ProjectSettings.LastOpenScene))
  935. {
  936. lastLoadedScene = Scene.LoadAsync(ProjectSettings.LastOpenScene);
  937. SetSceneDirty(false);
  938. }
  939. }
  940. /// <summary>
  941. /// Triggered by the runtime when the user clicks on the status bar.
  942. /// </summary>
  943. private static void Internal_OnStatusBarClicked()
  944. {
  945. EditorWindow.OpenWindow<LogWindow>();
  946. }
  947. [MethodImpl(MethodImplOptions.InternalCall)]
  948. private static extern void Internal_SetStatusScene(string name, bool modified);
  949. [MethodImpl(MethodImplOptions.InternalCall)]
  950. private static extern void Internal_SetStatusProject(bool modified);
  951. [MethodImpl(MethodImplOptions.InternalCall)]
  952. private static extern void Internal_SetStatusCompiling(bool compiling);
  953. [MethodImpl(MethodImplOptions.InternalCall)]
  954. private static extern void Internal_SetStatusImporting(bool importing, float percent);
  955. [MethodImpl(MethodImplOptions.InternalCall)]
  956. private static extern string Internal_GetProjectPath();
  957. [MethodImpl(MethodImplOptions.InternalCall)]
  958. private static extern string Internal_GetProjectName();
  959. [MethodImpl(MethodImplOptions.InternalCall)]
  960. private static extern bool Internal_GetProjectLoaded();
  961. [MethodImpl(MethodImplOptions.InternalCall)]
  962. private static extern string Internal_GetCompilerPath();
  963. [MethodImpl(MethodImplOptions.InternalCall)]
  964. private static extern string Internal_GetMonoExecPath();
  965. [MethodImpl(MethodImplOptions.InternalCall)]
  966. private static extern string Internal_GetBuiltinReleaseAssemblyPath();
  967. [MethodImpl(MethodImplOptions.InternalCall)]
  968. private static extern string Internal_GetBuiltinDebugAssemblyPath();
  969. [MethodImpl(MethodImplOptions.InternalCall)]
  970. private static extern string Internal_GetScriptAssemblyPath();
  971. [MethodImpl(MethodImplOptions.InternalCall)]
  972. private static extern string Internal_GetFrameworkAssemblyPath();
  973. [MethodImpl(MethodImplOptions.InternalCall)]
  974. private static extern string Internal_GetEngineAssemblyName();
  975. [MethodImpl(MethodImplOptions.InternalCall)]
  976. private static extern string Internal_GetEditorAssemblyName();
  977. [MethodImpl(MethodImplOptions.InternalCall)]
  978. private static extern string Internal_GetScriptGameAssemblyName();
  979. [MethodImpl(MethodImplOptions.InternalCall)]
  980. private static extern string Internal_GetScriptEditorAssemblyName();
  981. [MethodImpl(MethodImplOptions.InternalCall)]
  982. private static extern Prefab Internal_SaveScene(string path);
  983. [MethodImpl(MethodImplOptions.InternalCall)]
  984. private static extern bool Internal_IsValidProject(string path);
  985. [MethodImpl(MethodImplOptions.InternalCall)]
  986. private static extern void Internal_SaveProject();
  987. [MethodImpl(MethodImplOptions.InternalCall)]
  988. private static extern void Internal_LoadProject(string path);
  989. [MethodImpl(MethodImplOptions.InternalCall)]
  990. private static extern void Internal_UnloadProject();
  991. [MethodImpl(MethodImplOptions.InternalCall)]
  992. private static extern void Internal_CreateProject(string path);
  993. [MethodImpl(MethodImplOptions.InternalCall)]
  994. private static extern void Internal_ReloadAssemblies();
  995. [MethodImpl(MethodImplOptions.InternalCall)]
  996. private static extern void Internal_OpenFolder(string path);
  997. [MethodImpl(MethodImplOptions.InternalCall)]
  998. private static extern void Internal_RunUnitTests();
  999. [MethodImpl(MethodImplOptions.InternalCall)]
  1000. private static extern void Internal_Quit();
  1001. [MethodImpl(MethodImplOptions.InternalCall)]
  1002. private static extern void Internal_ToggleToolbarItem(string name, bool on);
  1003. [MethodImpl(MethodImplOptions.InternalCall)]
  1004. private static extern bool Internal_GetIsPlaying();
  1005. [MethodImpl(MethodImplOptions.InternalCall)]
  1006. private static extern void Internal_SetIsPlaying(bool value);
  1007. [MethodImpl(MethodImplOptions.InternalCall)]
  1008. private static extern bool Internal_GetIsPaused();
  1009. [MethodImpl(MethodImplOptions.InternalCall)]
  1010. private static extern void Internal_SetIsPaused(bool value);
  1011. [MethodImpl(MethodImplOptions.InternalCall)]
  1012. private static extern void Internal_FrameStep();
  1013. [MethodImpl(MethodImplOptions.InternalCall)]
  1014. private static extern void Internal_SetMainRenderTarget(IntPtr rendertarget);
  1015. [MethodImpl(MethodImplOptions.InternalCall)]
  1016. private static extern bool Internal_HasFocus();
  1017. }
  1018. /** @} */
  1019. }