2
0

EditorApplication.cs 46 KB

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