HotReloadSettingsTab.cs 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. using System;
  2. using System.Diagnostics.CodeAnalysis;
  3. using SingularityGroup.HotReload.DTO;
  4. using SingularityGroup.HotReload.Editor.Cli;
  5. using UnityEditor;
  6. using UnityEngine;
  7. using EditorGUI = UnityEditor.EditorGUI;
  8. namespace SingularityGroup.HotReload.Editor {
  9. internal struct HotReloadSettingsTabState {
  10. public readonly bool running;
  11. public readonly bool trialLicense;
  12. public readonly LoginStatusResponse loginStatus;
  13. public readonly bool isServerHealthy;
  14. public readonly bool registrationRequired;
  15. public HotReloadSettingsTabState(
  16. bool running,
  17. bool trialLicense,
  18. LoginStatusResponse loginStatus,
  19. bool isServerHealthy,
  20. bool registrationRequired
  21. ) {
  22. this.running = running;
  23. this.trialLicense = trialLicense;
  24. this.loginStatus = loginStatus;
  25. this.isServerHealthy = isServerHealthy;
  26. this.registrationRequired = registrationRequired;
  27. }
  28. }
  29. internal class HotReloadSettingsTab : HotReloadTabBase {
  30. private readonly HotReloadOptionsSection optionsSection;
  31. // cached because changing built target triggers C# domain reload
  32. // Also I suspect selectedBuildTargetGroup has chance to freeze Unity for several seconds (unconfirmed).
  33. private readonly Lazy<BuildTargetGroup> currentBuildTarget = new Lazy<BuildTargetGroup>(
  34. () => BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget));
  35. private readonly Lazy<bool> isCurrentBuildTargetSupported = new Lazy<bool>(() => {
  36. var target = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
  37. return HotReloadBuildHelper.IsMonoSupported(target);
  38. });
  39. // Resources.Load uses cache, so it's safe to call it every frame.
  40. // Retrying Load every time fixes an issue where you import the package and constructor runs, but resources aren't loadable yet.
  41. private Texture iconCheck => Resources.Load<Texture>("icon_check_circle");
  42. private Texture iconWarning => Resources.Load<Texture>("icon_warning_circle");
  43. [SuppressMessage("ReSharper", "Unity.UnknownResource")] // Rider doesn't check packages
  44. public HotReloadSettingsTab(HotReloadWindow window) : base(window,
  45. "Settings",
  46. "_Popup",
  47. "Make changes to a build running on-device.") {
  48. optionsSection = new HotReloadOptionsSection();
  49. }
  50. private GUIStyle headlineStyle;
  51. private GUIStyle paddedStyle;
  52. private Vector2 _settingsTabScrollPos;
  53. HotReloadSettingsTabState currentState;
  54. public override void OnGUI() {
  55. // HotReloadAboutTabState ensures rendering is consistent between Layout and Repaint calls
  56. // Without it errors like this happen:
  57. // ArgumentException: Getting control 2's position in a group with only 2 controls when doing repaint
  58. // See thread for more context: https://answers.unity.com/questions/17718/argumentexception-getting-control-2s-position-in-a.html
  59. if (Event.current.type == EventType.Layout) {
  60. currentState = new HotReloadSettingsTabState(
  61. running: EditorCodePatcher.Running,
  62. trialLicense: EditorCodePatcher.Status != null && (EditorCodePatcher.Status?.isTrial == true),
  63. loginStatus: EditorCodePatcher.Status,
  64. isServerHealthy: ServerHealthCheck.I.IsServerHealthy,
  65. registrationRequired: RedeemLicenseHelper.I.RegistrationRequired
  66. );
  67. }
  68. using (var scope = new EditorGUILayout.ScrollViewScope(_settingsTabScrollPos, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUILayout.MaxHeight(Math.Max(HotReloadWindowStyles.windowScreenHeight, 800)), GUILayout.MaxWidth(Math.Max(HotReloadWindowStyles.windowScreenWidth, 800)))) {
  69. _settingsTabScrollPos.x = scope.scrollPosition.x;
  70. _settingsTabScrollPos.y = scope.scrollPosition.y;
  71. using (new EditorGUILayout.VerticalScope(HotReloadWindowStyles.DynamicSectionHelpTab)) {
  72. GUILayout.Space(10);
  73. if (!EditorCodePatcher.LoginNotRequired
  74. && !currentState.registrationRequired
  75. // Delay showing login in settings to not confuse users that they need to login to use Free trial
  76. && (HotReloadPrefs.RateAppShown
  77. || PackageConst.IsAssetStoreBuild)
  78. ) {
  79. using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
  80. using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
  81. using (new EditorGUILayout.VerticalScope()) {
  82. RenderLicenseInfoSection();
  83. }
  84. }
  85. }
  86. }
  87. using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
  88. using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
  89. using (new EditorGUILayout.VerticalScope()) {
  90. HotReloadPrefs.ShowConfiguration = EditorGUILayout.Foldout(HotReloadPrefs.ShowConfiguration, "Settings", true, HotReloadWindowStyles.FoldoutStyle);
  91. if (HotReloadPrefs.ShowConfiguration) {
  92. EditorGUILayout.Space();
  93. // main section
  94. RenderUnityAutoRefresh();
  95. using (new EditorGUI.DisabledScope(!EditorCodePatcher.autoRecompileUnsupportedChangesSupported)) {
  96. RenderAutoRecompileUnsupportedChanges();
  97. if (HotReloadPrefs.AutoRecompileUnsupportedChanges && EditorCodePatcher.autoRecompileUnsupportedChangesSupported) {
  98. using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
  99. RenderAutoRecompileUnsupportedChangesImmediately();
  100. RenderAutoRecompileUnsupportedChangesOnExitPlayMode();
  101. RenderAutoRecompileUnsupportedChangesInPlayMode();
  102. RenderAutoRecompilePartiallyUnsupportedChanges();
  103. RenderDisplayNewMonobehaviourMethodsAsPartiallySupported();
  104. }
  105. }
  106. EditorGUILayout.Space();
  107. }
  108. RenderAssetRefresh();
  109. if (HotReloadPrefs.AllAssetChanges) {
  110. using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
  111. RenderIncludeShaderChanges();
  112. }
  113. EditorGUILayout.Space();
  114. }
  115. RenderDebuggerCompatibility();
  116. // // fields
  117. // RenderShowFeatures();
  118. // using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
  119. // RenderShowApplyfieldInitializerEditsToExistingClassInstances();
  120. //
  121. // EditorGUILayout.Space();
  122. // }
  123. // visual feedback
  124. if (EditorWindowHelper.supportsNotifications) {
  125. RenderShowNotifications();
  126. using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
  127. RenderShowPatchingNotifications();
  128. RenderShowCompilingUnsupportedNotifications();
  129. }
  130. EditorGUILayout.Space();
  131. }
  132. // misc
  133. RenderMiscHeader();
  134. using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
  135. RenderAutostart();
  136. RenderConsoleWindow();
  137. EditorGUILayout.Space();
  138. }
  139. EditorGUILayout.Space();
  140. using (new EditorGUILayout.HorizontalScope()) {
  141. GUILayout.FlexibleSpace();
  142. HotReloadWindow.RenderShowOnStartup();
  143. }
  144. }
  145. }
  146. }
  147. }
  148. if (!EditorCodePatcher.LoginNotRequired && currentState.trialLicense && currentState.running) {
  149. using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
  150. using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
  151. using (new EditorGUILayout.VerticalScope()) {
  152. RenderPromoCodeSection();
  153. }
  154. }
  155. }
  156. }
  157. using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
  158. using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
  159. using (new EditorGUILayout.VerticalScope()) {
  160. RenderOnDevice();
  161. }
  162. }
  163. }
  164. using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
  165. using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
  166. using (new EditorGUILayout.VerticalScope()) {
  167. HotReloadPrefs.ShowAdvanced = EditorGUILayout.Foldout(HotReloadPrefs.ShowAdvanced, "Advanced", true, HotReloadWindowStyles.FoldoutStyle);
  168. if (HotReloadPrefs.ShowAdvanced) {
  169. EditorGUILayout.Space();
  170. DeactivateHotReload();
  171. DisableDetailedErrorReporting();
  172. }
  173. }
  174. }
  175. }
  176. }
  177. }
  178. }
  179. void RenderUnityAutoRefresh() {
  180. var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Manage Unity auto-refresh (recommended)"), HotReloadPrefs.AllowDisableUnityAutoRefresh);
  181. if (newSettings != HotReloadPrefs.AllowDisableUnityAutoRefresh) {
  182. HotReloadPrefs.AllowDisableUnityAutoRefresh = newSettings;
  183. }
  184. string toggleDescription;
  185. if (HotReloadPrefs.AllowDisableUnityAutoRefresh) {
  186. toggleDescription = "To avoid unnecessary recompiling, Hot Reload will automatically change Unity's Auto Refresh and Script Compilation settings. Previous settings will be restored when Hot Reload is stopped";
  187. } else {
  188. toggleDescription = "Enabled this setting to auto-manage Unity's Auto Refresh and Script Compilation settings. This reduces unncessary recompiling";
  189. }
  190. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  191. EditorGUILayout.EndToggleGroup();
  192. EditorGUILayout.Space(6f);
  193. }
  194. void RenderAssetRefresh() {
  195. var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Asset refresh (recommended)"), HotReloadPrefs.AllAssetChanges);
  196. if (newSettings != HotReloadPrefs.AllAssetChanges) {
  197. HotReloadPrefs.AllAssetChanges = newSettings;
  198. // restart when setting changes
  199. if (ServerHealthCheck.I.IsServerHealthy) {
  200. var restartServer = EditorUtility.DisplayDialog("Hot Reload",
  201. $"When changing 'Asset refresh', the Hot Reload server must be restarted for this to take effect." +
  202. "\nDo you want to restart it now?",
  203. "Restart Hot Reload", "Don't restart");
  204. if (restartServer) {
  205. EditorCodePatcher.RestartCodePatcher().Forget();
  206. }
  207. }
  208. }
  209. string toggleDescription;
  210. if (HotReloadPrefs.AllAssetChanges) {
  211. toggleDescription = "Hot Reload will refresh changed assets such as sprites, prefabs, etc";
  212. } else {
  213. toggleDescription = "Enable to allow Hot Reload to refresh changed assets in the project. All asset types are supported including sprites, prefabs, shaders etc";
  214. }
  215. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  216. EditorGUILayout.EndToggleGroup();
  217. EditorGUILayout.Space(6f);
  218. }
  219. void RenderDebuggerCompatibility() {
  220. var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Auto-disable Hot Reload while a debugger is attached (recommended)"), HotReloadPrefs.AutoDisableHotReloadWithDebugger);
  221. if (newSettings != HotReloadPrefs.AutoDisableHotReloadWithDebugger) {
  222. HotReloadPrefs.AutoDisableHotReloadWithDebugger = newSettings;
  223. CodePatcher.I.debuggerCompatibilityEnabled = !HotReloadPrefs.AutoDisableHotReloadWithDebugger;
  224. }
  225. string toggleDescription;
  226. if (HotReloadPrefs.AutoDisableHotReloadWithDebugger) {
  227. toggleDescription = "Hot Reload automatically disables itself while a debugger is attached, as it can otherwise interfere with certain debugger features. Please read the documentation if you consider disabling this setting.";
  228. } else {
  229. toggleDescription = "When a debugger is attached, Hot Reload will be active, but certain debugger features might not work as expected. Please read our documentation to learn about the limitations.";
  230. }
  231. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  232. EditorGUILayout.EndToggleGroup();
  233. EditorGUILayout.Space(6f);
  234. }
  235. void RenderIncludeShaderChanges() {
  236. HotReloadPrefs.IncludeShaderChanges = EditorGUILayout.BeginToggleGroup(new GUIContent("Refresh shaders"), HotReloadPrefs.IncludeShaderChanges);
  237. string toggleDescription;
  238. if (HotReloadPrefs.IncludeShaderChanges) {
  239. toggleDescription = "Hot Reload will auto refresh shaders. Note that enabling this setting might impact performance.";
  240. } else {
  241. toggleDescription = "Enable to auto-refresh shaders. Note that enabling this setting might impact performance";
  242. }
  243. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  244. EditorGUILayout.EndToggleGroup();
  245. }
  246. void RenderConsoleWindow() {
  247. if (!HotReloadCli.CanOpenInBackground) {
  248. return;
  249. }
  250. var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Hide console window on start"), HotReloadPrefs.DisableConsoleWindow);
  251. if (newSettings != HotReloadPrefs.DisableConsoleWindow) {
  252. HotReloadPrefs.DisableConsoleWindow = newSettings;
  253. // restart when setting changes
  254. if (ServerHealthCheck.I.IsServerHealthy) {
  255. var restartServer = EditorUtility.DisplayDialog("Hot Reload",
  256. $"When changing 'Hide console window on start', the Hot Reload server must be restarted for this to take effect." +
  257. "\nDo you want to restart it now?",
  258. "Restart server", "Don't restart");
  259. if (restartServer) {
  260. EditorCodePatcher.RestartCodePatcher().Forget();
  261. }
  262. }
  263. }
  264. string toggleDescription;
  265. if (HotReloadPrefs.DisableConsoleWindow) {
  266. toggleDescription = "Hot Reload will start without creating a console window. Logs can be accessed through \"Help\" tab.";
  267. } else {
  268. toggleDescription = "Enable to start Hot Reload without creating a console window.";
  269. }
  270. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  271. EditorGUILayout.EndToggleGroup();
  272. EditorGUILayout.Space(6f);
  273. }
  274. void DeactivateHotReload() {
  275. var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Deactivate Hot Reload"), HotReloadPrefs.DeactivateHotReload);
  276. if (newSettings != HotReloadPrefs.DeactivateHotReload) {
  277. DeactivateHotReloadInner(newSettings);
  278. }
  279. string toggleDescription;
  280. if (HotReloadPrefs.DeactivateHotReload) {
  281. toggleDescription = "Hot Reload is deactivated.";
  282. } else {
  283. toggleDescription = "Enable to deactivate Hot Reload.";
  284. }
  285. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  286. EditorGUILayout.EndToggleGroup();
  287. EditorGUILayout.Space(6f);
  288. }
  289. void DisableDetailedErrorReporting() {
  290. var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Disable Detailed Error Reporting"), HotReloadPrefs.DisableDetailedErrorReporting);
  291. DisableDetailedErrorReportingInner(newSettings);
  292. string toggleDescription;
  293. if (HotReloadPrefs.DisableDetailedErrorReporting) {
  294. toggleDescription = "Detailed error reporting is disabled.";
  295. } else {
  296. toggleDescription = "Toggle on to disable detailed error reporting.";
  297. }
  298. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  299. EditorGUILayout.EndToggleGroup();
  300. EditorGUILayout.Space(6f);
  301. }
  302. public static void DisableDetailedErrorReportingInner(bool newSetting) {
  303. if (newSetting == HotReloadPrefs.DisableDetailedErrorReporting) {
  304. return;
  305. }
  306. HotReloadPrefs.DisableDetailedErrorReporting = newSetting;
  307. // restart when setting changes
  308. if (ServerHealthCheck.I.IsServerHealthy) {
  309. var restartServer = EditorUtility.DisplayDialog("Hot Reload",
  310. $"When changing 'Disable Detailed Error Reporting', the Hot Reload server must be restarted for this to take effect." +
  311. "\nDo you want to restart it now?",
  312. "Restart server", "Don't restart");
  313. if (restartServer) {
  314. EditorCodePatcher.RestartCodePatcher().Forget();
  315. }
  316. }
  317. }
  318. static void DeactivateHotReloadInner(bool deactivate) {
  319. var confirmed = !deactivate || EditorUtility.DisplayDialog("Hot Reload",
  320. $"Hot Reload will be completely deactivated (unusable) until you activate it again." +
  321. "\n\nDo you want to proceed?",
  322. "Deactivate", "Cancel");
  323. if (confirmed) {
  324. HotReloadPrefs.DeactivateHotReload = deactivate;
  325. if (deactivate) {
  326. EditorCodePatcher.StopCodePatcher(recompileOnDone: true).Forget();
  327. } else {
  328. HotReloadRunTab.Recompile();
  329. }
  330. }
  331. }
  332. void RenderAutostart() {
  333. var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Autostart on Unity open"), HotReloadPrefs.LaunchOnEditorStart);
  334. if (newSettings != HotReloadPrefs.LaunchOnEditorStart) {
  335. HotReloadPrefs.LaunchOnEditorStart = newSettings;
  336. }
  337. string toggleDescription;
  338. if (HotReloadPrefs.LaunchOnEditorStart) {
  339. toggleDescription = "Hot Reload will be launched when Unity project opens.";
  340. } else {
  341. toggleDescription = "Enable to launch Hot Reload when Unity project opens.";
  342. }
  343. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  344. EditorGUILayout.EndToggleGroup();
  345. EditorGUILayout.Space();
  346. }
  347. void RenderShowNotifications() {
  348. EditorGUILayout.Space(10f);
  349. GUILayout.Label("Visual Feedback", HotReloadWindowStyles.NotificationsTitleStyle);
  350. EditorGUILayout.Space(10f);
  351. if (!EditorWindowHelper.supportsNotifications && !UnitySettingsHelper.I.playmodeTintSupported) {
  352. var toggleDescription = "Indications are not supported in the Unity version you use.";
  353. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  354. }
  355. }
  356. // void RenderShowFields() {
  357. // EditorGUILayout.Space(14f);
  358. // GUILayout.Label("Fields", HotReloadWindowStyles.NotificationsTitleStyle);
  359. // }
  360. void RenderMiscHeader() {
  361. EditorGUILayout.Space(10f);
  362. GUILayout.Label("Misc", HotReloadWindowStyles.NotificationsTitleStyle);
  363. EditorGUILayout.Space(10f);
  364. }
  365. void RenderShowPatchingNotifications() {
  366. HotReloadPrefs.ShowPatchingNotifications = EditorGUILayout.BeginToggleGroup(new GUIContent("Patching Indication"), HotReloadPrefs.ShowPatchingNotifications);
  367. string toggleDescription;
  368. if (!EditorWindowHelper.supportsNotifications) {
  369. toggleDescription = "Patching Notification is not supported in the Unity version you use.";
  370. } else if (!HotReloadPrefs.ShowPatchingNotifications) {
  371. toggleDescription = "Enable to show GameView and SceneView indications when Patching.";
  372. } else {
  373. toggleDescription = "Indications will be shown in GameView and SceneView when Patching.";
  374. }
  375. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  376. EditorGUILayout.EndToggleGroup();
  377. }
  378. // void RenderShowApplyfieldInitializerEditsToExistingClassInstances() {
  379. // var newSetting = EditorGUILayout.BeginToggleGroup(new GUIContent("Apply field initializer edits to existing class instances"), HotReloadPrefs.ApplyFieldInitiailzerEditsToExistingClassInstances);
  380. // ApplyApplyFieldInitializerEditsToExistingClassInstances(newSetting);
  381. // string toggleDescription;
  382. // if (HotReloadPrefs.ApplyFieldInitiailzerEditsToExistingClassInstances) {
  383. // toggleDescription = "New field initializers with constant value will update field value of existing objects.";
  384. // } else {
  385. // toggleDescription = "New field initializers will not modify existing objects.";
  386. // }
  387. // EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  388. // EditorGUILayout.EndToggleGroup();
  389. // }
  390. [Obsolete("Not implemented")]
  391. public static void ApplyApplyFieldInitializerEditsToExistingClassInstances(bool newSetting) {
  392. if (newSetting != HotReloadPrefs.ApplyFieldInitiailzerEditsToExistingClassInstances) {
  393. HotReloadPrefs.ApplyFieldInitiailzerEditsToExistingClassInstances = newSetting;
  394. // restart when setting changes
  395. if (ServerHealthCheck.I.IsServerHealthy) {
  396. var restartServer = EditorUtility.DisplayDialog("Hot Reload",
  397. $"When changing 'Apply field initializer edits to existing class instances' setting, the Hot Reload server must restart for it to take effect." +
  398. "\nDo you want to restart it now?",
  399. "Restart server", "Don't restart");
  400. if (restartServer) {
  401. EditorCodePatcher.RestartCodePatcher().Forget();
  402. }
  403. }
  404. }
  405. }
  406. void RenderShowCompilingUnsupportedNotifications() {
  407. HotReloadPrefs.ShowCompilingUnsupportedNotifications = EditorGUILayout.BeginToggleGroup(new GUIContent("Compiling Unsupported Changes Indication"), HotReloadPrefs.ShowCompilingUnsupportedNotifications);
  408. string toggleDescription;
  409. if (!EditorWindowHelper.supportsNotifications) {
  410. toggleDescription = "Compiling Unsupported Changes Notification is not supported in the Unity version you use.";
  411. } else if (!HotReloadPrefs.ShowCompilingUnsupportedNotifications) {
  412. toggleDescription = "Enable to show GameView and SceneView indications when compiling unsupported changes.";
  413. } else {
  414. toggleDescription = "Indications will be shown in GameView and SceneView when compiling unsupported changes.";
  415. }
  416. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  417. EditorGUILayout.EndToggleGroup();
  418. }
  419. void RenderAutoRecompileUnsupportedChanges() {
  420. HotReloadPrefs.AutoRecompileUnsupportedChanges = EditorGUILayout.BeginToggleGroup(new GUIContent("Auto recompile unsupported changes (recommended)"), HotReloadPrefs.AutoRecompileUnsupportedChanges && EditorCodePatcher.autoRecompileUnsupportedChangesSupported);
  421. string toggleDescription;
  422. if (!EditorCodePatcher.autoRecompileUnsupportedChangesSupported) {
  423. toggleDescription = "Auto recompiling unsupported changes is not supported in the Unity version you use.";
  424. } else if (HotReloadPrefs.AutoRecompileUnsupportedChanges) {
  425. toggleDescription = "Hot Reload will recompile automatically after code changes that Hot Reload doesn't support.";
  426. } else {
  427. toggleDescription = "When enabled, recompile happens automatically after code changes that Hot Reload doesn't support.";
  428. }
  429. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  430. EditorGUILayout.EndToggleGroup();
  431. }
  432. void RenderAutoRecompilePartiallyUnsupportedChanges() {
  433. HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges = EditorGUILayout.BeginToggleGroup(new GUIContent("Include partially unsupported changes"), HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges);
  434. string toggleDescription;
  435. if (HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges) {
  436. toggleDescription = "Hot Reload will recompile partially unsupported changes.";
  437. } else {
  438. toggleDescription = "Enable to recompile partially unsupported changes.";
  439. }
  440. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  441. EditorGUILayout.EndToggleGroup();
  442. }
  443. void RenderDisplayNewMonobehaviourMethodsAsPartiallySupported() {
  444. HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported = EditorGUILayout.BeginToggleGroup(new GUIContent("Display new Monobehaviour methods as partially supported"), HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported);
  445. string toggleDescription;
  446. if (HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported) {
  447. toggleDescription = "Hot Reload will display new monobehaviour methods as partially unsupported.";
  448. } else {
  449. toggleDescription = "Enable to display new monobehaviour methods as partially unsupported.";
  450. }
  451. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  452. EditorGUILayout.EndToggleGroup();
  453. }
  454. void RenderAutoRecompileUnsupportedChangesImmediately() {
  455. HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately = EditorGUILayout.BeginToggleGroup(new GUIContent("Recompile immediately"), HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately);
  456. string toggleDescription;
  457. if (HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately) {
  458. toggleDescription = "Unsupported changes will be recompiled immediately.";
  459. } else {
  460. toggleDescription = "Unsupported changes will be recompiled when editor is focused. Enable to recompile immediately.";
  461. }
  462. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  463. EditorGUILayout.EndToggleGroup();
  464. }
  465. void RenderAutoRecompileUnsupportedChangesInPlayMode() {
  466. HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode = EditorGUILayout.BeginToggleGroup(new GUIContent("Recompile in Play Mode"), HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode);
  467. string toggleDescription;
  468. if (HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode) {
  469. toggleDescription = "Hot Reload will exit Play Mode to recompile unsupported changes.";
  470. } else {
  471. toggleDescription = "Enable to auto exit Play Mode to recompile unsupported changes.";
  472. }
  473. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  474. EditorGUILayout.EndToggleGroup();
  475. }
  476. void RenderAutoRecompileUnsupportedChangesOnExitPlayMode() {
  477. HotReloadPrefs.AutoRecompileUnsupportedChangesOnExitPlayMode = EditorGUILayout.BeginToggleGroup(new GUIContent("Recompile on exit Play Mode"), HotReloadPrefs.AutoRecompileUnsupportedChangesOnExitPlayMode);
  478. string toggleDescription;
  479. if (HotReloadPrefs.AutoRecompileUnsupportedChangesOnExitPlayMode) {
  480. toggleDescription = "Hot Reload will recompile unsupported changes when exiting Play Mode.";
  481. } else {
  482. toggleDescription = "Enable to recompile unsupported changes when exiting Play Mode.";
  483. }
  484. EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
  485. EditorGUILayout.EndToggleGroup();
  486. }
  487. void RenderOnDevice() {
  488. HotReloadPrefs.ShowOnDevice = EditorGUILayout.Foldout(HotReloadPrefs.ShowOnDevice, "On-Device", true, HotReloadWindowStyles.FoldoutStyle);
  489. if (!HotReloadPrefs.ShowOnDevice) {
  490. return;
  491. }
  492. // header with explainer image
  493. {
  494. if (headlineStyle == null) {
  495. // start with textArea for the background and border colors
  496. headlineStyle = new GUIStyle(GUI.skin.label) {
  497. fontStyle = FontStyle.Bold,
  498. alignment = TextAnchor.MiddleLeft
  499. };
  500. headlineStyle.normal.textColor = HotReloadWindowStyles.H2TitleStyle.normal.textColor;
  501. // bg color
  502. if (HotReloadWindowStyles.IsDarkMode) {
  503. headlineStyle.normal.background = EditorTextures.DarkGray40;
  504. } else {
  505. headlineStyle.normal.background = EditorTextures.LightGray225;
  506. }
  507. // layout
  508. headlineStyle.padding = new RectOffset(8, 8, 0, 0);
  509. headlineStyle.margin = new RectOffset(6, 6, 6, 6);
  510. }
  511. GUILayout.Space(9f); // space between logo and headline
  512. GUILayout.Label("Make changes to a build running on-device",
  513. headlineStyle, GUILayout.MinHeight(EditorGUIUtility.singleLineHeight * 1.4f));
  514. // image showing how Hot Reload works with a phone
  515. // var bannerBox = GUILayoutUtility.GetRect(flowchart.width * 0.6f, flowchart.height * 0.6f);
  516. // GUI.DrawTexture(bannerBox, flowchart, ScaleMode.ScaleToFit);
  517. }
  518. GUILayout.Space(16f);
  519. //ButtonToOpenBuildSettings();
  520. {
  521. GUILayout.Label("Manual connect", HotReloadWindowStyles.H3TitleStyle);
  522. EditorGUILayout.Space();
  523. GUILayout.BeginHorizontal();
  524. // indent all controls (this works with non-labels)
  525. GUILayout.Space(16f);
  526. GUILayout.BeginVertical();
  527. string text;
  528. var ip = IpHelper.GetIpAddressCached();
  529. if (string.IsNullOrEmpty(ip)) {
  530. text = $"If auto-pair fails, find your local IP in OS settings, and use this format to connect: '{{ip}}:{RequestHelper.port}'";
  531. } else {
  532. text = $"If auto-pair fails, use this IP and port to connect: {ip}:{RequestHelper.port}" +
  533. "\nMake sure you are on the same LAN/WiFi network";
  534. }
  535. GUILayout.Label(text, HotReloadWindowStyles.H3TitleWrapStyle);
  536. if (!currentState.isServerHealthy) {
  537. DrawHorizontalCheck(ServerHealthCheck.I.IsServerHealthy,
  538. "Hot Reload is running",
  539. "Hot Reload is not running",
  540. hasFix: false);
  541. }
  542. if (!HotReloadPrefs.ExposeServerToLocalNetwork) {
  543. var summary = $"Enable '{new ExposeServerOption().ShortSummary}'";
  544. DrawHorizontalCheck(HotReloadPrefs.ExposeServerToLocalNetwork,
  545. summary,
  546. summary);
  547. }
  548. // explainer image that shows phone needs same wifi to auto connect ?
  549. GUILayout.EndVertical();
  550. GUILayout.EndHorizontal();
  551. }
  552. GUILayout.Space(16f);
  553. // loading again is smooth, pretty sure AssetDatabase.LoadAssetAtPath is caching -Troy
  554. var settingsObject = HotReloadSettingsEditor.LoadSettingsOrDefault();
  555. var so = new SerializedObject(settingsObject);
  556. // if you build for Android now, will Hot Reload work?
  557. {
  558. EditorGUILayout.BeginHorizontal();
  559. GUILayout.Label("Build Settings Checklist", HotReloadWindowStyles.H3TitleStyle);
  560. EditorGUI.BeginDisabledGroup(isSupported);
  561. // One-click to change each setting to the supported value
  562. if (GUILayout.Button("Fix All", GUILayout.MaxWidth(90f))) {
  563. FixAllUnsupportedSettings(so);
  564. }
  565. EditorGUI.EndDisabledGroup();
  566. EditorGUILayout.EndHorizontal();
  567. // NOTE: After user changed some build settings, window may not immediately repaint
  568. // (e.g. toggle Development Build in Build Settings window)
  569. // We could show a refresh button (to encourage the user to click the window which makes it repaint).
  570. DrawSectionCheckBuildSupport(so);
  571. }
  572. GUILayout.Space(16f);
  573. // Settings checkboxes (Hot Reload options)
  574. {
  575. GUILayout.Label("Options", HotReloadWindowStyles.H3TitleStyle);
  576. if (settingsObject) {
  577. optionsSection.DrawGUI(so);
  578. }
  579. }
  580. GUILayout.FlexibleSpace(); // needed otherwise vertical scrollbar is appearing for no reason (Unity 2021 glitch perhaps)
  581. }
  582. private void RenderLicenseInfoSection() {
  583. HotReloadRunTab.RenderLicenseInfo(
  584. _window.RunTabState,
  585. currentState.loginStatus,
  586. verbose: true,
  587. allowHide: false,
  588. overrideActionButton: "Activate License",
  589. showConsumptions: true
  590. );
  591. }
  592. private void RenderPromoCodeSection() {
  593. _window.RunTab.RenderPromoCodes();
  594. }
  595. public void FocusLicenseFoldout() {
  596. HotReloadPrefs.ShowLogin = true;
  597. }
  598. // note: changing scripting backend does not force Unity to recreate the GUI, so need to check it when drawing.
  599. private ScriptingImplementation ScriptingBackend => HotReloadBuildHelper.GetCurrentScriptingBackend();
  600. private ManagedStrippingLevel StrippingLevel => HotReloadBuildHelper.GetCurrentStrippingLevel();
  601. public bool isSupported = true;
  602. /// <summary>
  603. /// These options are drawn in the On-device tab
  604. /// </summary>
  605. // new on-device options should be added here
  606. public static readonly IOption[] allOptions = new IOption[] {
  607. new ExposeServerOption(),
  608. IncludeInBuildOption.I,
  609. new AllowAndroidAppToMakeHttpRequestsOption(),
  610. };
  611. /// <summary>
  612. /// Change each setting to the value supported by Hot Reload
  613. /// </summary>
  614. private void FixAllUnsupportedSettings(SerializedObject so) {
  615. if (!isCurrentBuildTargetSupported.Value) {
  616. // try switch to Android platform
  617. // (we also support Standalone but HotReload on mobile is a better selling point)
  618. if (!TrySwitchToStandalone()) {
  619. // skip changing other options (user won't readthe gray text) - user has to click Fix All again
  620. return;
  621. }
  622. }
  623. foreach (var buildOption in allOptions) {
  624. if (!buildOption.GetValue(so)) {
  625. buildOption.SetValue(so, true);
  626. }
  627. }
  628. so.ApplyModifiedProperties();
  629. var settingsObject = so.targetObject as HotReloadSettingsObject;
  630. if (settingsObject) {
  631. // when you click fix all, make sure to save the settings, otherwise ui does not update
  632. HotReloadSettingsEditor.EnsureSettingsCreated(settingsObject);
  633. }
  634. if (!EditorUserBuildSettings.development) {
  635. EditorUserBuildSettings.development = true;
  636. }
  637. HotReloadBuildHelper.SetCurrentScriptingBackend(ScriptingImplementation.Mono2x);
  638. HotReloadBuildHelper.SetCurrentStrippingLevel(ManagedStrippingLevel.Disabled);
  639. }
  640. public static bool TrySwitchToStandalone() {
  641. BuildTarget buildTarget;
  642. if (Application.platform == RuntimePlatform.LinuxEditor) {
  643. buildTarget = BuildTarget.StandaloneLinux64;
  644. } else if (Application.platform == RuntimePlatform.WindowsEditor) {
  645. buildTarget = BuildTarget.StandaloneWindows64;
  646. } else if (Application.platform == RuntimePlatform.OSXEditor) {
  647. buildTarget = BuildTarget.StandaloneOSX;
  648. } else {
  649. return false;
  650. }
  651. var current = EditorUserBuildSettings.activeBuildTarget;
  652. if (current == buildTarget) {
  653. return true;
  654. }
  655. var confirmed = EditorUtility.DisplayDialog("Switch Build Target",
  656. "Switching the build target can take a while depending on project size.",
  657. $"Switch to Standalone", "Cancel");
  658. if (confirmed) {
  659. EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.Standalone, buildTarget);
  660. Log.Info($"Build target is switching to {buildTarget}.");
  661. return true;
  662. } else {
  663. return false;
  664. }
  665. }
  666. /// <summary>
  667. /// Section that user can check before making a Unity Player build.
  668. /// </summary>
  669. /// <param name="so"></param>
  670. /// <remarks>
  671. /// This section is for confirming your build will work with Hot Reload.<br/>
  672. /// Options that can be changed after the build is made should be drawn elsewhere.
  673. /// </remarks>
  674. public void DrawSectionCheckBuildSupport(SerializedObject so) {
  675. isSupported = true;
  676. var selectedPlatform = currentBuildTarget.Value;
  677. DrawHorizontalCheck(isCurrentBuildTargetSupported.Value,
  678. $"The {selectedPlatform.ToString()} platform is selected",
  679. $"The current platform is {selectedPlatform.ToString()} which is not supported");
  680. using (new EditorGUI.DisabledScope(!isCurrentBuildTargetSupported.Value)) {
  681. foreach (var option in allOptions) {
  682. DrawHorizontalCheck(option.GetValue(so),
  683. $"Enable \"{option.ShortSummary}\"",
  684. $"Enable \"{option.ShortSummary}\"");
  685. }
  686. DrawHorizontalCheck(EditorUserBuildSettings.development,
  687. "Development Build is enabled",
  688. "Enable \"Development Build\"");
  689. DrawHorizontalCheck(ScriptingBackend == ScriptingImplementation.Mono2x,
  690. $"Scripting Backend is set to Mono",
  691. $"Set Scripting Backend to Mono");
  692. DrawHorizontalCheck(StrippingLevel == ManagedStrippingLevel.Disabled,
  693. $"Stripping Level = {StrippingLevel}",
  694. $"Stripping Level = {StrippingLevel}",
  695. suggestedSolutionText: "Code stripping needs to be disabled to ensure that all methods are available for patching."
  696. );
  697. }
  698. }
  699. /// <summary>
  700. /// Draw a box with a tick or warning icon on the left, with text describing the tick or warning
  701. /// </summary>
  702. /// <param name="condition">The condition to check. True to show a tick icon, False to show a warning.</param>
  703. /// <param name="okText">Shown when condition is true</param>
  704. /// <param name="notOkText">Shown when condition is false</param>
  705. /// <param name="suggestedSolutionText">Shown when <paramref name="condition"/> is false</param>
  706. void DrawHorizontalCheck(bool condition, string okText, string notOkText = null, string suggestedSolutionText = null, bool hasFix = true) {
  707. if (okText == null) {
  708. throw new ArgumentNullException(nameof(okText));
  709. }
  710. if (notOkText == null) {
  711. notOkText = okText;
  712. }
  713. // include some horizontal space around the icon
  714. var boxWidth = GUILayout.Width(EditorGUIUtility.singleLineHeight * 1.31f);
  715. var height = GUILayout.Height(EditorGUIUtility.singleLineHeight * 1.01f);
  716. GUILayout.BeginHorizontal(HotReloadWindowStyles.BoxStyle, height, GUILayout.ExpandWidth(true));
  717. var style = HotReloadWindowStyles.NoPaddingMiddleLeftStyle;
  718. var iconRect = GUILayoutUtility.GetRect(
  719. Mathf.Round(EditorGUIUtility.singleLineHeight * 1.31f),
  720. Mathf.Round(EditorGUIUtility.singleLineHeight * 1.01f),
  721. style, boxWidth, height, GUILayout.ExpandWidth(false));
  722. // rounded so we can have pixel perfect black circle bg
  723. iconRect.Set(Mathf.Round(iconRect.x), Mathf.Round(iconRect.y), Mathf.CeilToInt(iconRect.width),
  724. Mathf.CeilToInt(iconRect.height));
  725. var text = condition ? okText : notOkText;
  726. var icon = condition ? iconCheck : iconWarning;
  727. if (GUI.enabled) {
  728. DrawBlackCircle(iconRect);
  729. // resource can be null when building player (Editor Resources not available)
  730. if (icon) {
  731. GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit);
  732. }
  733. } else {
  734. // show something (instead of hiding) so that layout stays same size
  735. DrawDisabledCircle(iconRect);
  736. }
  737. GUILayout.Space(4f);
  738. GUILayout.Label(text, style, height);
  739. if (!condition && hasFix) {
  740. isSupported = false;
  741. }
  742. GUILayout.EndHorizontal();
  743. if (!condition && !String.IsNullOrEmpty(suggestedSolutionText)) {
  744. // suggest to the user how they can resolve the issue
  745. EditorGUI.indentLevel++;
  746. GUILayout.Label(suggestedSolutionText, HotReloadWindowStyles.WrapStyle);
  747. EditorGUI.indentLevel--;
  748. }
  749. }
  750. void DrawDisabledCircle(Rect rect) => DrawCircleIcon(rect,
  751. Resources.Load<Texture>("icon_circle_gray"),
  752. Color.clear); // smaller circle draws less attention
  753. void DrawBlackCircle(Rect rect) => DrawCircleIcon(rect,
  754. Resources.Load<Texture>("icon_circle_black"),
  755. new Color(0.14f, 0.14f, 0.14f)); // black is too dark in unity light theme
  756. void DrawCircleIcon(Rect rect, Texture circleIcon, Color borderColor) {
  757. // Note: drawing texture from resources is pixelated on the edges, so it has some transperancy around the edges.
  758. // While building for Android, Resources.Load returns null for our editor Resources.
  759. if (circleIcon != null) {
  760. GUI.DrawTexture(rect, circleIcon, ScaleMode.ScaleToFit);
  761. }
  762. // Draw smooth circle border
  763. const float borderWidth = 2f;
  764. GUI.DrawTexture(rect, EditorTextures.White, ScaleMode.ScaleToFit, true,
  765. 0f,
  766. borderColor,
  767. new Vector4(borderWidth, borderWidth, borderWidth, borderWidth),
  768. Mathf.Min(rect.height, rect.width) / 2f);
  769. }
  770. }
  771. }