EditorApplication.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143
  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. #pragma warning disable 0414
  255. private static EditorApplication instance;
  256. #pragma warning restore 0414
  257. /// <summary>
  258. /// Constructs a new editor application. Called at editor start-up by the runtime, and any time assembly refresh
  259. /// occurrs.
  260. /// </summary>
  261. internal EditorApplication()
  262. {
  263. instance = this;
  264. codeManager = new ScriptCodeManager();
  265. const string soName = "EditorPersistentData";
  266. SceneObject so = Scene.Root.FindChild(soName);
  267. if (so == null)
  268. so = new SceneObject(soName, true);
  269. persistentData = so.GetComponent<EditorPersistentData>();
  270. if (persistentData == null)
  271. persistentData = so.AddComponent<EditorPersistentData>();
  272. // Register controls
  273. InputConfiguration inputConfig = VirtualInput.KeyConfig;
  274. inputConfig.RegisterButton(SceneCamera.MoveForwardBinding, ButtonCode.W);
  275. inputConfig.RegisterButton(SceneCamera.MoveBackBinding, ButtonCode.S);
  276. inputConfig.RegisterButton(SceneCamera.MoveLeftBinding, ButtonCode.A);
  277. inputConfig.RegisterButton(SceneCamera.MoveRightBinding, ButtonCode.D);
  278. inputConfig.RegisterButton(SceneCamera.MoveUpBinding, ButtonCode.E);
  279. inputConfig.RegisterButton(SceneCamera.MoveDownBinding, ButtonCode.Q);
  280. inputConfig.RegisterButton(SceneCamera.MoveForwardBinding, ButtonCode.Up);
  281. inputConfig.RegisterButton(SceneCamera.MoveBackBinding, ButtonCode.Down);
  282. inputConfig.RegisterButton(SceneCamera.MoveLeftBinding, ButtonCode.Left);
  283. inputConfig.RegisterButton(SceneCamera.MoveRightBinding, ButtonCode.Right);
  284. inputConfig.RegisterButton(SceneCamera.FastMoveBinding, ButtonCode.LeftShift);
  285. inputConfig.RegisterButton(SceneCamera.RotateBinding, ButtonCode.MouseRight);
  286. inputConfig.RegisterButton(SceneCamera.PanBinding, ButtonCode.MouseMiddle);
  287. inputConfig.RegisterAxis(SceneCamera.HorizontalAxisBinding, InputAxis.MouseX);
  288. inputConfig.RegisterAxis(SceneCamera.VerticalAxisBinding, InputAxis.MouseY);
  289. inputConfig.RegisterAxis(SceneCamera.ScrollAxisBinding, InputAxis.MouseZ);
  290. inputConfig.RegisterButton(SceneWindow.ToggleProfilerOverlayBinding, ButtonCode.P, ButtonModifier.CtrlAlt);
  291. inputConfig.RegisterButton(SceneWindow.ViewToolBinding, ButtonCode.Q);
  292. inputConfig.RegisterButton(SceneWindow.FrameBinding, ButtonCode.F);
  293. inputConfig.RegisterButton(SceneWindow.MoveToolBinding, ButtonCode.W);
  294. inputConfig.RegisterButton(SceneWindow.RotateToolBinding, ButtonCode.E);
  295. inputConfig.RegisterButton(SceneWindow.ScaleToolBinding, ButtonCode.R);
  296. inputConfig.RegisterButton(CutBinding, ButtonCode.X, ButtonModifier.Ctrl);
  297. inputConfig.RegisterButton(CopyBinding, ButtonCode.C, ButtonModifier.Ctrl);
  298. inputConfig.RegisterButton(PasteBinding, ButtonCode.V, ButtonModifier.Ctrl);
  299. inputConfig.RegisterButton(DuplicateBinding, ButtonCode.D, ButtonModifier.Ctrl);
  300. inputConfig.RegisterButton(DeleteBinding, ButtonCode.Delete);
  301. inputConfig.RegisterButton(RenameBinding, ButtonCode.F2);
  302. if (IsProjectLoaded)
  303. {
  304. monitor = new FolderMonitor(ProjectLibrary.ResourceFolder);
  305. monitor.OnAdded += OnAssetModified;
  306. monitor.OnRemoved += OnAssetModified;
  307. monitor.OnModified += OnAssetModified;
  308. }
  309. }
  310. /// <summary>
  311. /// Triggered when the folder monitor detects an asset in the monitored folder was modified.
  312. /// </summary>
  313. /// <param name="path">Path to the modified file or folder.</param>
  314. private static void OnAssetModified(string path)
  315. {
  316. ProjectLibrary.Refresh(path);
  317. }
  318. /// <summary>
  319. /// Called every frame by the runtime.
  320. /// </summary>
  321. internal void OnEditorUpdate()
  322. {
  323. // Update managers
  324. ProjectLibrary.Update();
  325. codeManager.Update();
  326. }
  327. /// <summary>
  328. /// Manually triggers a global shortcut.
  329. /// </summary>
  330. /// <param name="btn">Button for the shortcut. If this doesn't correspond to any shortcut, it is ignored.</param>
  331. internal static void TriggerGlobalShortcut(VirtualButton btn)
  332. {
  333. IGlobalShortcuts window = null;
  334. if (btn != PasteKey)
  335. {
  336. // The system ensures elsewhere that only either a resource or a scene object is selected, but not both
  337. if (Selection.ResourcePaths.Length > 0)
  338. {
  339. window = EditorWindow.GetWindow<LibraryWindow>();
  340. }
  341. else if (Selection.SceneObjects.Length > 0)
  342. {
  343. window = EditorWindow.GetWindow<HierarchyWindow>();
  344. if (window == null)
  345. window = EditorWindow.GetWindow<SceneWindow>();
  346. }
  347. if (window != null)
  348. {
  349. if (btn == CopyKey)
  350. window.OnCopyPressed();
  351. else if (btn == CutKey)
  352. window.OnCutPressed();
  353. else if (btn == PasteKey)
  354. window.OnPastePressed();
  355. else if (btn == DuplicateKey)
  356. window.OnDuplicatePressed();
  357. else if (btn == RenameKey)
  358. window.OnRenamePressed();
  359. else if (btn == DeleteKey)
  360. window.OnDeletePressed();
  361. }
  362. }
  363. else
  364. {
  365. HierarchyWindow hierarchy = EditorWindow.GetWindow<HierarchyWindow>();
  366. if (hierarchy != null && hierarchy.HasFocus)
  367. window = hierarchy;
  368. else
  369. {
  370. LibraryWindow library = EditorWindow.GetWindow<LibraryWindow>();
  371. if (library != null && library.HasFocus)
  372. window = library;
  373. }
  374. if (window != null)
  375. window.OnPastePressed();
  376. }
  377. }
  378. /// <summary>
  379. /// Creates a new empty scene.
  380. /// </summary>
  381. [MenuItem("File/New Scene", 10051, true)]
  382. private static void NewScene()
  383. {
  384. LoadScene(null);
  385. }
  386. /// <summary>
  387. /// Opens a dialog that allows the user to select a new prefab to load as the current scene. If current scene
  388. /// is modified the user is offered a chance to save it.
  389. /// </summary>
  390. [MenuItem("File/Open Scene", ButtonModifier.Ctrl, ButtonCode.L, 10050)]
  391. private static void LoadScene()
  392. {
  393. string[] scenePaths;
  394. if (BrowseDialog.OpenFile(ProjectLibrary.ResourceFolder, "", false, out scenePaths))
  395. {
  396. if (scenePaths.Length > 0)
  397. LoadScene(scenePaths[0]);
  398. }
  399. }
  400. /// <summary>
  401. /// Opens a dialog to allows the user to select a location where to save the current scene. If scene was previously
  402. /// saved it is instead automatically saved at the last location.
  403. /// </summary>
  404. public static void SaveScene(Action onSuccess = null, Action onFailure = null)
  405. {
  406. if (!Scene.ActiveSceneUUID.IsEmpty())
  407. {
  408. string scenePath = ProjectLibrary.GetPath(Scene.ActiveSceneUUID);
  409. if (!string.IsNullOrEmpty(scenePath))
  410. {
  411. if (Scene.IsGenericPrefab)
  412. {
  413. SaveGenericPrefab(onSuccess, onFailure);
  414. }
  415. else
  416. {
  417. SaveScene(scenePath);
  418. if (onSuccess != null)
  419. onSuccess();
  420. }
  421. }
  422. else
  423. SaveSceneAs(onSuccess, onFailure);
  424. }
  425. else
  426. SaveSceneAs(onSuccess, onFailure);
  427. }
  428. /// <summary>
  429. /// Opens a dialog to allows the user to select a location where to save the current scene.
  430. /// </summary>
  431. public static void SaveSceneAs(Action onSuccess = null, Action onFailure = null)
  432. {
  433. string scenePath = "";
  434. if (BrowseDialog.SaveFile(ProjectLibrary.ResourceFolder, "*.prefab", out scenePath))
  435. {
  436. if (!PathEx.IsPartOf(scenePath, ProjectLibrary.ResourceFolder))
  437. {
  438. DialogBox.Open("Error", "The location must be inside the Resources folder of the project.",
  439. DialogBox.Type.OK,
  440. x =>
  441. {
  442. if (onFailure != null)
  443. onFailure();
  444. });
  445. }
  446. else
  447. {
  448. // TODO - If path points to an existing non-scene asset or folder I should delete it otherwise
  449. // Internal_SaveScene will silently fail.
  450. scenePath = Path.ChangeExtension(scenePath, ".prefab");
  451. SaveScene(scenePath);
  452. }
  453. }
  454. else
  455. {
  456. // User canceled, so technically a success
  457. if (onSuccess != null)
  458. onSuccess();
  459. }
  460. }
  461. /// <summary>
  462. /// Loads a prefab as the current scene at the specified path. If current scene is modified the user is offered a
  463. /// chance to save it.
  464. /// </summary>
  465. /// <param name="path">Path to a valid prefab relative to the resource folder. If path is empty a brand new
  466. /// scene will be loaded.</param>
  467. public static void LoadScene(string path)
  468. {
  469. Action<string> continueLoad =
  470. (scenePath) =>
  471. {
  472. if (string.IsNullOrEmpty(path))
  473. {
  474. Scene.Clear();
  475. lastLoadedScene = null;
  476. }
  477. else
  478. lastLoadedScene = Scene.LoadAsync(path);
  479. SetSceneDirty(false);
  480. ProjectSettings.LastOpenScene = scenePath;
  481. ProjectSettings.Save();
  482. };
  483. Action<DialogBox.ResultType> dialogCallback =
  484. (result) =>
  485. {
  486. if (result == DialogBox.ResultType.Yes)
  487. {
  488. SaveScene();
  489. continueLoad(path);
  490. }
  491. else if (result == DialogBox.ResultType.No)
  492. continueLoad(path);
  493. };
  494. if (IsSceneModified())
  495. {
  496. DialogBox.Open("Warning", "You current scene has modifications. Do you wish to save them first?",
  497. DialogBox.Type.YesNoCancel, dialogCallback);
  498. }
  499. else
  500. continueLoad(path);
  501. }
  502. /// <summary>
  503. /// Saves the currently loaded scene to the specified path.
  504. /// </summary>
  505. /// <param name="path">Path relative to the resource folder. This can be the path to the existing scene
  506. /// prefab if it just needs updating. </param>
  507. internal static void SaveScene(string path)
  508. {
  509. Prefab scene = Internal_SaveScene(path);
  510. Scene.SetActive(scene);
  511. ProjectLibrary.Refresh(true);
  512. SetSceneDirty(false);
  513. }
  514. /// <summary>
  515. /// Attempts to save the current scene by applying the changes to a prefab, instead of saving it as a brand new
  516. /// scene. This is necessary for generic prefabs that have don't have a scene root included in the prefab. If the
  517. /// object added any other objects to the root, or has moved or deleted the original generic prefab the user
  518. /// will be asked to save the scene normally, creating a brand new prefab.
  519. /// </summary>
  520. private static void SaveGenericPrefab(Action onSuccess = null, Action onFailure = null)
  521. {
  522. // Find prefab root
  523. SceneObject root = null;
  524. int numChildren = Scene.Root.GetNumChildren();
  525. int numNormalChildren = 0;
  526. for (int i = 0; i < numChildren; i++)
  527. {
  528. SceneObject child = Scene.Root.GetChild(i);
  529. if (EditorUtility.IsInternal(child))
  530. continue;
  531. UUID prefabUUID = PrefabUtility.GetPrefabUUID(child);
  532. if (prefabUUID == Scene.ActiveSceneUUID)
  533. root = child;
  534. // If user added any other prefabs other than the initial one, the scene no longer represents a generic
  535. // prefab (as we can now longer save it by applying changes only to that prefab)
  536. numNormalChildren++;
  537. if (numNormalChildren > 1)
  538. {
  539. root = null;
  540. break;
  541. }
  542. }
  543. if (root != null)
  544. {
  545. PrefabUtility.ApplyPrefab(root, false);
  546. ProjectLibrary.Refresh(true);
  547. SetSceneDirty(false);
  548. if (onSuccess != null)
  549. onSuccess();
  550. }
  551. else
  552. {
  553. SaveSceneAs(onSuccess, onFailure);
  554. }
  555. }
  556. /// <summary>
  557. /// Checks does the folder at the provieded path contain a valid project.
  558. /// </summary>
  559. /// <param name="path">Absolute path to the root project folder.</param>
  560. /// <returns>True if the folder contains a valid project.</returns>
  561. public static bool IsValidProject(string path)
  562. {
  563. return Internal_IsValidProject(path);
  564. }
  565. /// <summary>
  566. /// Contains a new project in the provided folder.
  567. /// </summary>
  568. /// <param name="path">Absolute path to the folder to create the project in. Name of this folder will be used as the
  569. /// project's name.</param>
  570. public static void CreateProject(string path)
  571. {
  572. Internal_CreateProject(path);
  573. }
  574. /// <summary>
  575. /// Wrapper for menu items for <see cref="SaveScene(Action, Action)"/> method
  576. /// </summary>
  577. [MenuItem("File/Save Scene", ButtonModifier.Ctrl, ButtonCode.S, 10049)]
  578. [ToolbarItem("Save Scene", ToolbarIcon.SaveScene, "Save scene (Ctrl + S)", 1998)]
  579. private static void SaveSceneMenu()
  580. {
  581. SaveScene();
  582. }
  583. /// <summary>
  584. /// Wrapper for menu items for <see cref="SaveSceneAs(Action, Action)"/> method
  585. /// </summary>
  586. [MenuItem("File/Save Scene As", 10048)]
  587. private static void SaveSceneAsMenu()
  588. {
  589. SaveSceneAs();
  590. }
  591. /// <summary>
  592. /// Opens a Project Window allowing you to browse for or create a project.
  593. /// </summary>
  594. [MenuItem("File/Open Project", 10100)]
  595. [ToolbarItem("Open Project", ToolbarIcon.OpenProject, "Project manager", 2000)]
  596. public static void BrowseForProject()
  597. {
  598. ProjectWindow.Open();
  599. }
  600. /// <summary>
  601. /// Saves all data in the currently open project.
  602. /// </summary>
  603. [MenuItem("File/Save Project", 10099)]
  604. [ToolbarItem("Save Project", ToolbarIcon.SaveProject, "Save project", 1999)]
  605. public static void SaveProject()
  606. {
  607. OnProjectSave?.Invoke();
  608. // Apply changes to any animation clips edited using the animation editor
  609. foreach (var KVP in persistentData.dirtyAnimClips)
  610. KVP.Value.SaveToClip();
  611. // Save all dirty resources to disk
  612. foreach (var KVP in persistentData.dirtyResources)
  613. {
  614. UUID resourceUUID = KVP.Key;
  615. string path = ProjectLibrary.GetPath(resourceUUID);
  616. if (!IsNative(path))
  617. continue; // Imported resources can't be changed
  618. Resource resource = ProjectLibrary.Load<Resource>(path);
  619. if(resource != null)
  620. ProjectLibrary.Save(resource);
  621. }
  622. persistentData.dirtyAnimClips.Clear();
  623. persistentData.dirtyResources.Clear();
  624. SetStatusProject(false);
  625. Internal_SaveProject();
  626. }
  627. /// <summary>
  628. /// Loads the project at the specified path. This method executes asynchronously.
  629. /// </summary>
  630. /// <param name="path">Absolute path to the project's root folder.</param>
  631. public static void LoadProject(string path)
  632. {
  633. if (IsProjectLoaded && path == ProjectPath)
  634. return;
  635. if (!Internal_IsValidProject(path))
  636. {
  637. Debug.LogWarning("Provided path: \"" + path + "\" is not a valid project.");
  638. return;
  639. }
  640. if (IsProjectLoaded)
  641. UnloadProject();
  642. Internal_LoadProject(path); // Triggers Internal_OnProjectLoaded when done
  643. }
  644. /// <summary>
  645. /// Closes the editor.
  646. /// </summary>
  647. public static void Quit()
  648. {
  649. Internal_Quit();
  650. }
  651. /// <summary>
  652. /// Toggles an existing toolbar button into an on or off state which changes the visuals of the button.
  653. /// </summary>
  654. /// <param name="name">Name of the existing button to toggle</param>
  655. /// <param name="on">True to toggle on, false to toggle off (default)</param>
  656. public static void ToggleToolbarItem(string name, bool on)
  657. {
  658. Internal_ToggleToolbarItem(name, on);
  659. }
  660. /// <summary>
  661. /// Opens a folder in the default external application.
  662. /// </summary>
  663. /// <param name="path">Absolute path to the folder to open.</param>
  664. public static void OpenFolder(string path)
  665. {
  666. Internal_OpenFolder(path);
  667. }
  668. /// <summary>
  669. /// Marks a resource as dirty so that it may be saved the next time the project is saved. Optionally you may also
  670. /// call <see cref="ProjectLibrary.Save"/> to save it immediately.
  671. /// </summary>
  672. /// <param name="resource">Resource to mark as dirty</param>
  673. public static void SetDirty(Resource resource)
  674. {
  675. if (resource == null)
  676. return;
  677. SetStatusProject(true);
  678. persistentData.dirtyResources[resource.UUID] = true;
  679. }
  680. /// <summary>
  681. /// Marks the current project dirty (requires saving in order for changes not to be lost).
  682. /// </summary>
  683. public static void SetProjectDirty()
  684. {
  685. SetStatusProject(true);
  686. }
  687. /// <summary>
  688. /// Marks the current scene as dirty.
  689. /// </summary>
  690. public static void SetSceneDirty()
  691. {
  692. SetSceneDirty(true);
  693. }
  694. /// <summary>
  695. /// Marks the current scene as clean or dirty.
  696. /// </summary>
  697. /// <param name="dirty">Should the scene be marked as clean or dirty.</param>
  698. internal static void SetSceneDirty(bool dirty)
  699. {
  700. sceneDirty = dirty;
  701. SetStatusScene(Scene.ActiveSceneName, dirty);
  702. if (!dirty && !Scene.ActiveSceneUUID.IsEmpty())
  703. persistentData.dirtyResources.Remove(Scene.ActiveSceneUUID);
  704. }
  705. /// <summary>
  706. /// Checks is the specific resource dirty and needs saving.
  707. /// </summary>
  708. /// <param name="resource">Resource to check.</param>
  709. /// <returns>True if the resource requires saving, false otherwise.</returns>
  710. public static bool IsDirty(Resource resource)
  711. {
  712. return persistentData.dirtyResources.ContainsKey(resource.UUID);
  713. }
  714. /// <summary>
  715. /// Checks does the path represent a native resource.
  716. /// </summary>
  717. /// <param name="path">Filename or path to check.</param>
  718. /// <returns>True if the path represents a native resource.</returns>
  719. public static bool IsNative(string path)
  720. {
  721. string extension = Path.GetExtension(path);
  722. return extension == ".asset" || extension == ".prefab";
  723. }
  724. /// <summary>
  725. /// Unloads the currently loaded project. Offers the user a chance to save the current scene if it is modified.
  726. /// Automatically saves all project data before unloading.
  727. /// </summary>
  728. private static void UnloadProject()
  729. {
  730. Action continueUnload =
  731. () =>
  732. {
  733. Scene.Clear();
  734. if (monitor != null)
  735. {
  736. monitor.Destroy();
  737. monitor = null;
  738. }
  739. LibraryWindow window = EditorWindow.GetWindow<LibraryWindow>();
  740. if(window != null)
  741. window.Reset();
  742. SetSceneDirty(false);
  743. Internal_UnloadProject();
  744. SetStatusProject(false);
  745. };
  746. Action<DialogBox.ResultType> dialogCallback =
  747. (result) =>
  748. {
  749. if (result == DialogBox.ResultType.Yes)
  750. SaveScene();
  751. continueUnload();
  752. };
  753. if (IsSceneModified())
  754. {
  755. DialogBox.Open("Warning", "You current scene has modifications. Do you wish to save them first?",
  756. DialogBox.Type.YesNoCancel, dialogCallback);
  757. }
  758. else
  759. continueUnload();
  760. }
  761. /// <summary>
  762. /// Reloads all script assemblies in case they were modified. This action is delayed and will be executed
  763. /// at the beginning of the next frame.
  764. /// </summary>
  765. public static void ReloadAssemblies()
  766. {
  767. Internal_ReloadAssemblies();
  768. }
  769. /// <summary>
  770. /// Changes the scene displayed on the status bar.
  771. /// </summary>
  772. /// <param name="name">Name of the scene.</param>
  773. /// <param name="modified">Whether to display the scene as modified or not.</param>
  774. private static void SetStatusScene(string name, bool modified)
  775. {
  776. Internal_SetStatusScene(name, modified);
  777. }
  778. /// <summary>
  779. /// Changes the project state displayed on the status bar.
  780. /// </summary>
  781. /// <param name="modified">Whether to display the project as modified or not.</param>
  782. private static void SetStatusProject(bool modified)
  783. {
  784. Internal_SetStatusProject(modified);
  785. }
  786. /// <summary>
  787. /// Displays or hides the "compilation in progress" visual on the status bar.
  788. /// </summary>
  789. /// <param name="compiling">True to display the visual, false otherwise.</param>
  790. internal static void SetStatusCompiling(bool compiling)
  791. {
  792. Internal_SetStatusCompiling(compiling);
  793. }
  794. /// <summary>
  795. /// Displays or hides the "import in progress" visuals on the status bar and updates the related progress bar.
  796. /// </summary>
  797. /// <param name="importing">True to display the visual, false otherwise.</param>
  798. /// <param name="percent">Percent in range [0, 1] to display on the progress bar.</param>
  799. internal static void SetStatusImporting(bool importing, float percent)
  800. {
  801. Internal_SetStatusImporting(importing, percent);
  802. }
  803. /// <summary>
  804. /// Checks did we make any modifications to the scene since it was last saved.
  805. /// </summary>
  806. /// <returns>True if the scene was never saved, or was modified after last save.</returns>
  807. public static bool IsSceneModified()
  808. {
  809. return sceneDirty;
  810. }
  811. /// <summary>
  812. /// Runs a single frame of the game and pauses it. If the game is not currently running it will be started.
  813. /// </summary>
  814. public static void FrameStep()
  815. {
  816. if (IsStopped)
  817. {
  818. if (EditorSettings.GetBool(LogWindow.CLEAR_ON_PLAY_KEY, true))
  819. {
  820. Debug.Clear();
  821. LogWindow log = EditorWindow.GetWindow<LogWindow>();
  822. if (log != null)
  823. log.Refresh();
  824. }
  825. }
  826. ToggleToolbarItem("Play", false);
  827. ToggleToolbarItem("Pause", true);
  828. Internal_FrameStep();
  829. }
  830. /// <summary>
  831. /// Executes any editor-specific unit tests. This should be called after a project is loaded if possible.
  832. /// </summary>
  833. private static void RunUnitTests()
  834. {
  835. #if DEBUG
  836. Internal_RunUnitTests();
  837. #endif
  838. }
  839. /// <summary>
  840. /// Triggered by the runtime when <see cref="LoadProject"/> method completes.
  841. /// </summary>
  842. private static void Internal_OnProjectLoaded()
  843. {
  844. SetStatusProject(false);
  845. if (!unitTestsExecuted)
  846. {
  847. RunUnitTests();
  848. unitTestsExecuted = true;
  849. }
  850. if (!IsProjectLoaded)
  851. {
  852. ProjectWindow.Open();
  853. return;
  854. }
  855. string projectPath = ProjectPath;
  856. RecentProject[] recentProjects = EditorSettings.RecentProjects;
  857. bool foundPath = false;
  858. for (int i = 0; i < recentProjects.Length; i++)
  859. {
  860. if (PathEx.Compare(recentProjects[i].path, projectPath))
  861. {
  862. recentProjects[i].accessTimestamp = (ulong)DateTime.Now.Ticks;
  863. EditorSettings.RecentProjects = recentProjects;
  864. foundPath = true;
  865. break;
  866. }
  867. }
  868. if (!foundPath)
  869. {
  870. List<RecentProject> extendedRecentProjects = new List<RecentProject>();
  871. extendedRecentProjects.AddRange(recentProjects);
  872. RecentProject newProject = new RecentProject();
  873. newProject.path = projectPath;
  874. newProject.accessTimestamp = (ulong)DateTime.Now.Ticks;
  875. extendedRecentProjects.Add(newProject);
  876. EditorSettings.RecentProjects = extendedRecentProjects.ToArray();
  877. }
  878. EditorSettings.LastOpenProject = projectPath;
  879. EditorSettings.Save();
  880. ProjectLibrary.Refresh();
  881. if(monitor != null)
  882. {
  883. monitor.Destroy();
  884. monitor = null;
  885. }
  886. monitor = new FolderMonitor(ProjectLibrary.ResourceFolder);
  887. monitor.OnAdded += OnAssetModified;
  888. monitor.OnRemoved += OnAssetModified;
  889. monitor.OnModified += OnAssetModified;
  890. if (!string.IsNullOrWhiteSpace(ProjectSettings.LastOpenScene))
  891. {
  892. lastLoadedScene = Scene.LoadAsync(ProjectSettings.LastOpenScene);
  893. SetSceneDirty(false);
  894. }
  895. }
  896. /// <summary>
  897. /// Triggered by the runtime when the user clicks on the status bar.
  898. /// </summary>
  899. private static void Internal_OnStatusBarClicked()
  900. {
  901. EditorWindow.OpenWindow<LogWindow>();
  902. }
  903. [MethodImpl(MethodImplOptions.InternalCall)]
  904. private static extern void Internal_SetStatusScene(string name, bool modified);
  905. [MethodImpl(MethodImplOptions.InternalCall)]
  906. private static extern void Internal_SetStatusProject(bool modified);
  907. [MethodImpl(MethodImplOptions.InternalCall)]
  908. private static extern void Internal_SetStatusCompiling(bool compiling);
  909. [MethodImpl(MethodImplOptions.InternalCall)]
  910. private static extern void Internal_SetStatusImporting(bool importing, float percent);
  911. [MethodImpl(MethodImplOptions.InternalCall)]
  912. private static extern string Internal_GetProjectPath();
  913. [MethodImpl(MethodImplOptions.InternalCall)]
  914. private static extern string Internal_GetProjectName();
  915. [MethodImpl(MethodImplOptions.InternalCall)]
  916. private static extern bool Internal_GetProjectLoaded();
  917. [MethodImpl(MethodImplOptions.InternalCall)]
  918. private static extern string Internal_GetCompilerPath();
  919. [MethodImpl(MethodImplOptions.InternalCall)]
  920. private static extern string Internal_GetMonoExecPath();
  921. [MethodImpl(MethodImplOptions.InternalCall)]
  922. private static extern string Internal_GetBuiltinReleaseAssemblyPath();
  923. [MethodImpl(MethodImplOptions.InternalCall)]
  924. private static extern string Internal_GetBuiltinDebugAssemblyPath();
  925. [MethodImpl(MethodImplOptions.InternalCall)]
  926. private static extern string Internal_GetScriptAssemblyPath();
  927. [MethodImpl(MethodImplOptions.InternalCall)]
  928. private static extern string Internal_GetFrameworkAssemblyPath();
  929. [MethodImpl(MethodImplOptions.InternalCall)]
  930. private static extern string Internal_GetEngineAssemblyName();
  931. [MethodImpl(MethodImplOptions.InternalCall)]
  932. private static extern string Internal_GetEditorAssemblyName();
  933. [MethodImpl(MethodImplOptions.InternalCall)]
  934. private static extern string Internal_GetScriptGameAssemblyName();
  935. [MethodImpl(MethodImplOptions.InternalCall)]
  936. private static extern string Internal_GetScriptEditorAssemblyName();
  937. [MethodImpl(MethodImplOptions.InternalCall)]
  938. private static extern Prefab Internal_SaveScene(string path);
  939. [MethodImpl(MethodImplOptions.InternalCall)]
  940. private static extern bool Internal_IsValidProject(string path);
  941. [MethodImpl(MethodImplOptions.InternalCall)]
  942. private static extern void Internal_SaveProject();
  943. [MethodImpl(MethodImplOptions.InternalCall)]
  944. private static extern void Internal_LoadProject(string path);
  945. [MethodImpl(MethodImplOptions.InternalCall)]
  946. private static extern void Internal_UnloadProject();
  947. [MethodImpl(MethodImplOptions.InternalCall)]
  948. private static extern void Internal_CreateProject(string path);
  949. [MethodImpl(MethodImplOptions.InternalCall)]
  950. private static extern void Internal_ReloadAssemblies();
  951. [MethodImpl(MethodImplOptions.InternalCall)]
  952. private static extern void Internal_OpenFolder(string path);
  953. [MethodImpl(MethodImplOptions.InternalCall)]
  954. private static extern void Internal_RunUnitTests();
  955. [MethodImpl(MethodImplOptions.InternalCall)]
  956. private static extern void Internal_Quit();
  957. [MethodImpl(MethodImplOptions.InternalCall)]
  958. private static extern void Internal_ToggleToolbarItem(string name, bool on);
  959. [MethodImpl(MethodImplOptions.InternalCall)]
  960. private static extern bool Internal_GetIsPlaying();
  961. [MethodImpl(MethodImplOptions.InternalCall)]
  962. private static extern void Internal_SetIsPlaying(bool value);
  963. [MethodImpl(MethodImplOptions.InternalCall)]
  964. private static extern bool Internal_GetIsPaused();
  965. [MethodImpl(MethodImplOptions.InternalCall)]
  966. private static extern void Internal_SetIsPaused(bool value);
  967. [MethodImpl(MethodImplOptions.InternalCall)]
  968. private static extern void Internal_FrameStep();
  969. [MethodImpl(MethodImplOptions.InternalCall)]
  970. private static extern void Internal_SetMainRenderTarget(IntPtr rendertarget);
  971. [MethodImpl(MethodImplOptions.InternalCall)]
  972. private static extern bool Internal_HasFocus();
  973. }
  974. /** @} */
  975. }