2025-07-08 10:46:31 +00:00
using System ;
using System.Diagnostics.CodeAnalysis ;
using SingularityGroup.HotReload.DTO ;
using SingularityGroup.HotReload.Editor.Cli ;
using UnityEditor ;
using UnityEngine ;
using EditorGUI = UnityEditor . EditorGUI ;
namespace SingularityGroup.HotReload.Editor {
internal struct HotReloadSettingsTabState {
public readonly bool running ;
public readonly bool trialLicense ;
public readonly LoginStatusResponse loginStatus ;
public readonly bool isServerHealthy ;
public readonly bool registrationRequired ;
public HotReloadSettingsTabState (
bool running ,
bool trialLicense ,
LoginStatusResponse loginStatus ,
bool isServerHealthy ,
bool registrationRequired
) {
this . running = running ;
this . trialLicense = trialLicense ;
this . loginStatus = loginStatus ;
this . isServerHealthy = isServerHealthy ;
this . registrationRequired = registrationRequired ;
}
}
internal class HotReloadSettingsTab : HotReloadTabBase {
private readonly HotReloadOptionsSection optionsSection ;
// cached because changing built target triggers C# domain reload
// Also I suspect selectedBuildTargetGroup has chance to freeze Unity for several seconds (unconfirmed).
private readonly Lazy < BuildTargetGroup > currentBuildTarget = new Lazy < BuildTargetGroup > (
( ) = > BuildPipeline . GetBuildTargetGroup ( EditorUserBuildSettings . activeBuildTarget ) ) ;
private readonly Lazy < bool > isCurrentBuildTargetSupported = new Lazy < bool > ( ( ) = > {
var target = BuildPipeline . GetBuildTargetGroup ( EditorUserBuildSettings . activeBuildTarget ) ;
return HotReloadBuildHelper . IsMonoSupported ( target ) ;
} ) ;
// Resources.Load uses cache, so it's safe to call it every frame.
// Retrying Load every time fixes an issue where you import the package and constructor runs, but resources aren't loadable yet.
private Texture iconCheck = > Resources . Load < Texture > ( "icon_check_circle" ) ;
private Texture iconWarning = > Resources . Load < Texture > ( "icon_warning_circle" ) ;
[SuppressMessage("ReSharper", "Unity.UnknownResource")] // Rider doesn't check packages
public HotReloadSettingsTab ( HotReloadWindow window ) : base ( window ,
"Settings" ,
"_Popup" ,
"Make changes to a build running on-device." ) {
optionsSection = new HotReloadOptionsSection ( ) ;
}
private GUIStyle headlineStyle ;
private GUIStyle paddedStyle ;
private Vector2 _settingsTabScrollPos ;
HotReloadSettingsTabState currentState ;
public override void OnGUI ( ) {
// HotReloadAboutTabState ensures rendering is consistent between Layout and Repaint calls
// Without it errors like this happen:
// ArgumentException: Getting control 2's position in a group with only 2 controls when doing repaint
// See thread for more context: https://answers.unity.com/questions/17718/argumentexception-getting-control-2s-position-in-a.html
if ( Event . current . type = = EventType . Layout ) {
currentState = new HotReloadSettingsTabState (
running : EditorCodePatcher . Running ,
trialLicense : EditorCodePatcher . Status ! = null & & ( EditorCodePatcher . Status ? . isTrial = = true ) ,
loginStatus : EditorCodePatcher . Status ,
isServerHealthy : ServerHealthCheck . I . IsServerHealthy ,
registrationRequired : RedeemLicenseHelper . I . RegistrationRequired
) ;
}
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 ) ) ) ) {
_settingsTabScrollPos . x = scope . scrollPosition . x ;
_settingsTabScrollPos . y = scope . scrollPosition . y ;
using ( new EditorGUILayout . VerticalScope ( HotReloadWindowStyles . DynamicSectionHelpTab ) ) {
GUILayout . Space ( 10 ) ;
if ( ! EditorCodePatcher . LoginNotRequired
& & ! currentState . registrationRequired
// Delay showing login in settings to not confuse users that they need to login to use Free trial
& & ( HotReloadPrefs . RateAppShown
| | PackageConst . IsAssetStoreBuild )
) {
using ( new EditorGUILayout . HorizontalScope ( HotReloadWindowStyles . SectionOuterBoxCompact ) ) {
using ( new EditorGUILayout . HorizontalScope ( HotReloadWindowStyles . SectionInnerBoxWide ) ) {
using ( new EditorGUILayout . VerticalScope ( ) ) {
RenderLicenseInfoSection ( ) ;
}
}
}
}
using ( new EditorGUILayout . HorizontalScope ( HotReloadWindowStyles . SectionOuterBoxCompact ) ) {
using ( new EditorGUILayout . HorizontalScope ( HotReloadWindowStyles . SectionInnerBoxWide ) ) {
using ( new EditorGUILayout . VerticalScope ( ) ) {
HotReloadPrefs . ShowConfiguration = EditorGUILayout . Foldout ( HotReloadPrefs . ShowConfiguration , "Settings" , true , HotReloadWindowStyles . FoldoutStyle ) ;
if ( HotReloadPrefs . ShowConfiguration ) {
EditorGUILayout . Space ( ) ;
// main section
RenderUnityAutoRefresh ( ) ;
using ( new EditorGUI . DisabledScope ( ! EditorCodePatcher . autoRecompileUnsupportedChangesSupported ) ) {
RenderAutoRecompileUnsupportedChanges ( ) ;
if ( HotReloadPrefs . AutoRecompileUnsupportedChanges & & EditorCodePatcher . autoRecompileUnsupportedChangesSupported ) {
using ( new EditorGUILayout . VerticalScope ( paddedStyle ? ? ( paddedStyle = new GUIStyle { padding = new RectOffset ( 20 , 0 , 0 , 0 ) } ) ) ) {
RenderAutoRecompileUnsupportedChangesImmediately ( ) ;
RenderAutoRecompileUnsupportedChangesOnExitPlayMode ( ) ;
RenderAutoRecompileUnsupportedChangesInPlayMode ( ) ;
RenderAutoRecompilePartiallyUnsupportedChanges ( ) ;
RenderDisplayNewMonobehaviourMethodsAsPartiallySupported ( ) ;
}
}
EditorGUILayout . Space ( ) ;
}
RenderAssetRefresh ( ) ;
if ( HotReloadPrefs . AllAssetChanges ) {
using ( new EditorGUILayout . VerticalScope ( paddedStyle ? ? ( paddedStyle = new GUIStyle { padding = new RectOffset ( 20 , 0 , 0 , 0 ) } ) ) ) {
RenderIncludeShaderChanges ( ) ;
}
EditorGUILayout . Space ( ) ;
}
RenderDebuggerCompatibility ( ) ;
// // fields
// RenderShowFeatures();
// using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
// RenderShowApplyfieldInitializerEditsToExistingClassInstances();
//
// EditorGUILayout.Space();
// }
// visual feedback
if ( EditorWindowHelper . supportsNotifications ) {
RenderShowNotifications ( ) ;
using ( new EditorGUILayout . VerticalScope ( paddedStyle ? ? ( paddedStyle = new GUIStyle { padding = new RectOffset ( 20 , 0 , 0 , 0 ) } ) ) ) {
RenderShowPatchingNotifications ( ) ;
RenderShowCompilingUnsupportedNotifications ( ) ;
}
EditorGUILayout . Space ( ) ;
}
// misc
RenderMiscHeader ( ) ;
using ( new EditorGUILayout . VerticalScope ( paddedStyle ? ? ( paddedStyle = new GUIStyle { padding = new RectOffset ( 20 , 0 , 0 , 0 ) } ) ) ) {
RenderAutostart ( ) ;
RenderConsoleWindow ( ) ;
EditorGUILayout . Space ( ) ;
}
EditorGUILayout . Space ( ) ;
using ( new EditorGUILayout . HorizontalScope ( ) ) {
GUILayout . FlexibleSpace ( ) ;
HotReloadWindow . RenderShowOnStartup ( ) ;
}
}
}
}
}
if ( ! EditorCodePatcher . LoginNotRequired & & currentState . trialLicense & & currentState . running ) {
using ( new EditorGUILayout . HorizontalScope ( HotReloadWindowStyles . SectionOuterBoxCompact ) ) {
using ( new EditorGUILayout . HorizontalScope ( HotReloadWindowStyles . SectionInnerBoxWide ) ) {
using ( new EditorGUILayout . VerticalScope ( ) ) {
RenderPromoCodeSection ( ) ;
}
}
}
}
using ( new EditorGUILayout . HorizontalScope ( HotReloadWindowStyles . SectionOuterBoxCompact ) ) {
using ( new EditorGUILayout . HorizontalScope ( HotReloadWindowStyles . SectionInnerBoxWide ) ) {
using ( new EditorGUILayout . VerticalScope ( ) ) {
RenderOnDevice ( ) ;
}
}
}
using ( new EditorGUILayout . HorizontalScope ( HotReloadWindowStyles . SectionOuterBoxCompact ) ) {
using ( new EditorGUILayout . HorizontalScope ( HotReloadWindowStyles . SectionInnerBoxWide ) ) {
using ( new EditorGUILayout . VerticalScope ( ) ) {
HotReloadPrefs . ShowAdvanced = EditorGUILayout . Foldout ( HotReloadPrefs . ShowAdvanced , "Advanced" , true , HotReloadWindowStyles . FoldoutStyle ) ;
if ( HotReloadPrefs . ShowAdvanced ) {
EditorGUILayout . Space ( ) ;
DeactivateHotReload ( ) ;
DisableDetailedErrorReporting ( ) ;
}
}
}
}
}
}
}
void RenderUnityAutoRefresh ( ) {
var newSettings = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Manage Unity auto-refresh (recommended)" ) , HotReloadPrefs . AllowDisableUnityAutoRefresh ) ;
if ( newSettings ! = HotReloadPrefs . AllowDisableUnityAutoRefresh ) {
HotReloadPrefs . AllowDisableUnityAutoRefresh = newSettings ;
}
string toggleDescription ;
if ( HotReloadPrefs . AllowDisableUnityAutoRefresh ) {
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" ;
} else {
toggleDescription = "Enabled this setting to auto-manage Unity's Auto Refresh and Script Compilation settings. This reduces unncessary recompiling" ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
EditorGUILayout . Space ( 6f ) ;
}
void RenderAssetRefresh ( ) {
var newSettings = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Asset refresh (recommended)" ) , HotReloadPrefs . AllAssetChanges ) ;
if ( newSettings ! = HotReloadPrefs . AllAssetChanges ) {
HotReloadPrefs . AllAssetChanges = newSettings ;
// restart when setting changes
if ( ServerHealthCheck . I . IsServerHealthy ) {
var restartServer = EditorUtility . DisplayDialog ( "Hot Reload" ,
$"When changing 'Asset refresh', the Hot Reload server must be restarted for this to take effect." +
"\nDo you want to restart it now?" ,
"Restart Hot Reload" , "Don't restart" ) ;
if ( restartServer ) {
EditorCodePatcher . RestartCodePatcher ( ) . Forget ( ) ;
}
}
}
string toggleDescription ;
if ( HotReloadPrefs . AllAssetChanges ) {
toggleDescription = "Hot Reload will refresh changed assets such as sprites, prefabs, etc" ;
} else {
toggleDescription = "Enable to allow Hot Reload to refresh changed assets in the project. All asset types are supported including sprites, prefabs, shaders etc" ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
EditorGUILayout . Space ( 6f ) ;
}
void RenderDebuggerCompatibility ( ) {
var newSettings = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Auto-disable Hot Reload while a debugger is attached (recommended)" ) , HotReloadPrefs . AutoDisableHotReloadWithDebugger ) ;
if ( newSettings ! = HotReloadPrefs . AutoDisableHotReloadWithDebugger ) {
HotReloadPrefs . AutoDisableHotReloadWithDebugger = newSettings ;
CodePatcher . I . debuggerCompatibilityEnabled = ! HotReloadPrefs . AutoDisableHotReloadWithDebugger ;
}
string toggleDescription ;
if ( HotReloadPrefs . AutoDisableHotReloadWithDebugger ) {
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." ;
} else {
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." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
EditorGUILayout . Space ( 6f ) ;
}
void RenderIncludeShaderChanges ( ) {
HotReloadPrefs . IncludeShaderChanges = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Refresh shaders" ) , HotReloadPrefs . IncludeShaderChanges ) ;
string toggleDescription ;
if ( HotReloadPrefs . IncludeShaderChanges ) {
toggleDescription = "Hot Reload will auto refresh shaders. Note that enabling this setting might impact performance." ;
} else {
toggleDescription = "Enable to auto-refresh shaders. Note that enabling this setting might impact performance" ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
}
void RenderConsoleWindow ( ) {
if ( ! HotReloadCli . CanOpenInBackground ) {
return ;
}
var newSettings = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Hide console window on start" ) , HotReloadPrefs . DisableConsoleWindow ) ;
if ( newSettings ! = HotReloadPrefs . DisableConsoleWindow ) {
HotReloadPrefs . DisableConsoleWindow = newSettings ;
// restart when setting changes
if ( ServerHealthCheck . I . IsServerHealthy ) {
var restartServer = EditorUtility . DisplayDialog ( "Hot Reload" ,
$"When changing 'Hide console window on start', the Hot Reload server must be restarted for this to take effect." +
"\nDo you want to restart it now?" ,
"Restart server" , "Don't restart" ) ;
if ( restartServer ) {
EditorCodePatcher . RestartCodePatcher ( ) . Forget ( ) ;
}
}
}
string toggleDescription ;
if ( HotReloadPrefs . DisableConsoleWindow ) {
toggleDescription = "Hot Reload will start without creating a console window. Logs can be accessed through \"Help\" tab." ;
} else {
toggleDescription = "Enable to start Hot Reload without creating a console window." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
EditorGUILayout . Space ( 6f ) ;
}
void DeactivateHotReload ( ) {
var newSettings = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Deactivate Hot Reload" ) , HotReloadPrefs . DeactivateHotReload ) ;
if ( newSettings ! = HotReloadPrefs . DeactivateHotReload ) {
DeactivateHotReloadInner ( newSettings ) ;
}
string toggleDescription ;
if ( HotReloadPrefs . DeactivateHotReload ) {
toggleDescription = "Hot Reload is deactivated." ;
} else {
toggleDescription = "Enable to deactivate Hot Reload." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
EditorGUILayout . Space ( 6f ) ;
}
void DisableDetailedErrorReporting ( ) {
var newSettings = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Disable Detailed Error Reporting" ) , HotReloadPrefs . DisableDetailedErrorReporting ) ;
DisableDetailedErrorReportingInner ( newSettings ) ;
string toggleDescription ;
if ( HotReloadPrefs . DisableDetailedErrorReporting ) {
toggleDescription = "Detailed error reporting is disabled." ;
} else {
toggleDescription = "Toggle on to disable detailed error reporting." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
EditorGUILayout . Space ( 6f ) ;
}
public static void DisableDetailedErrorReportingInner ( bool newSetting ) {
if ( newSetting = = HotReloadPrefs . DisableDetailedErrorReporting ) {
return ;
}
HotReloadPrefs . DisableDetailedErrorReporting = newSetting ;
// restart when setting changes
if ( ServerHealthCheck . I . IsServerHealthy ) {
var restartServer = EditorUtility . DisplayDialog ( "Hot Reload" ,
$"When changing 'Disable Detailed Error Reporting', the Hot Reload server must be restarted for this to take effect." +
"\nDo you want to restart it now?" ,
"Restart server" , "Don't restart" ) ;
if ( restartServer ) {
EditorCodePatcher . RestartCodePatcher ( ) . Forget ( ) ;
}
}
}
static void DeactivateHotReloadInner ( bool deactivate ) {
var confirmed = ! deactivate | | EditorUtility . DisplayDialog ( "Hot Reload" ,
$"Hot Reload will be completely deactivated (unusable) until you activate it again." +
"\n\nDo you want to proceed?" ,
"Deactivate" , "Cancel" ) ;
if ( confirmed ) {
HotReloadPrefs . DeactivateHotReload = deactivate ;
if ( deactivate ) {
EditorCodePatcher . StopCodePatcher ( recompileOnDone : true ) . Forget ( ) ;
} else {
HotReloadRunTab . Recompile ( ) ;
}
}
}
void RenderAutostart ( ) {
var newSettings = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Autostart on Unity open" ) , HotReloadPrefs . LaunchOnEditorStart ) ;
if ( newSettings ! = HotReloadPrefs . LaunchOnEditorStart ) {
HotReloadPrefs . LaunchOnEditorStart = newSettings ;
}
string toggleDescription ;
if ( HotReloadPrefs . LaunchOnEditorStart ) {
toggleDescription = "Hot Reload will be launched when Unity project opens." ;
} else {
toggleDescription = "Enable to launch Hot Reload when Unity project opens." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
EditorGUILayout . Space ( ) ;
}
void RenderShowNotifications ( ) {
EditorGUILayout . Space ( 10f ) ;
GUILayout . Label ( "Visual Feedback" , HotReloadWindowStyles . NotificationsTitleStyle ) ;
EditorGUILayout . Space ( 10f ) ;
if ( ! EditorWindowHelper . supportsNotifications & & ! UnitySettingsHelper . I . playmodeTintSupported ) {
var toggleDescription = "Indications are not supported in the Unity version you use." ;
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
}
}
// void RenderShowFields() {
// EditorGUILayout.Space(14f);
// GUILayout.Label("Fields", HotReloadWindowStyles.NotificationsTitleStyle);
// }
void RenderMiscHeader ( ) {
EditorGUILayout . Space ( 10f ) ;
GUILayout . Label ( "Misc" , HotReloadWindowStyles . NotificationsTitleStyle ) ;
EditorGUILayout . Space ( 10f ) ;
}
void RenderShowPatchingNotifications ( ) {
HotReloadPrefs . ShowPatchingNotifications = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Patching Indication" ) , HotReloadPrefs . ShowPatchingNotifications ) ;
string toggleDescription ;
if ( ! EditorWindowHelper . supportsNotifications ) {
toggleDescription = "Patching Notification is not supported in the Unity version you use." ;
} else if ( ! HotReloadPrefs . ShowPatchingNotifications ) {
toggleDescription = "Enable to show GameView and SceneView indications when Patching." ;
} else {
toggleDescription = "Indications will be shown in GameView and SceneView when Patching." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
}
// void RenderShowApplyfieldInitializerEditsToExistingClassInstances() {
// var newSetting = EditorGUILayout.BeginToggleGroup(new GUIContent("Apply field initializer edits to existing class instances"), HotReloadPrefs.ApplyFieldInitiailzerEditsToExistingClassInstances);
// ApplyApplyFieldInitializerEditsToExistingClassInstances(newSetting);
// string toggleDescription;
// if (HotReloadPrefs.ApplyFieldInitiailzerEditsToExistingClassInstances) {
// toggleDescription = "New field initializers with constant value will update field value of existing objects.";
// } else {
// toggleDescription = "New field initializers will not modify existing objects.";
// }
// EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
// EditorGUILayout.EndToggleGroup();
// }
[Obsolete("Not implemented")]
public static void ApplyApplyFieldInitializerEditsToExistingClassInstances ( bool newSetting ) {
if ( newSetting ! = HotReloadPrefs . ApplyFieldInitiailzerEditsToExistingClassInstances ) {
HotReloadPrefs . ApplyFieldInitiailzerEditsToExistingClassInstances = newSetting ;
// restart when setting changes
if ( ServerHealthCheck . I . IsServerHealthy ) {
var restartServer = EditorUtility . DisplayDialog ( "Hot Reload" ,
$"When changing 'Apply field initializer edits to existing class instances' setting, the Hot Reload server must restart for it to take effect." +
"\nDo you want to restart it now?" ,
"Restart server" , "Don't restart" ) ;
if ( restartServer ) {
EditorCodePatcher . RestartCodePatcher ( ) . Forget ( ) ;
}
}
}
}
void RenderShowCompilingUnsupportedNotifications ( ) {
HotReloadPrefs . ShowCompilingUnsupportedNotifications = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Compiling Unsupported Changes Indication" ) , HotReloadPrefs . ShowCompilingUnsupportedNotifications ) ;
string toggleDescription ;
if ( ! EditorWindowHelper . supportsNotifications ) {
toggleDescription = "Compiling Unsupported Changes Notification is not supported in the Unity version you use." ;
} else if ( ! HotReloadPrefs . ShowCompilingUnsupportedNotifications ) {
toggleDescription = "Enable to show GameView and SceneView indications when compiling unsupported changes." ;
} else {
toggleDescription = "Indications will be shown in GameView and SceneView when compiling unsupported changes." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
}
void RenderAutoRecompileUnsupportedChanges ( ) {
HotReloadPrefs . AutoRecompileUnsupportedChanges = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Auto recompile unsupported changes (recommended)" ) , HotReloadPrefs . AutoRecompileUnsupportedChanges & & EditorCodePatcher . autoRecompileUnsupportedChangesSupported ) ;
string toggleDescription ;
if ( ! EditorCodePatcher . autoRecompileUnsupportedChangesSupported ) {
toggleDescription = "Auto recompiling unsupported changes is not supported in the Unity version you use." ;
} else if ( HotReloadPrefs . AutoRecompileUnsupportedChanges ) {
toggleDescription = "Hot Reload will recompile automatically after code changes that Hot Reload doesn't support." ;
} else {
toggleDescription = "When enabled, recompile happens automatically after code changes that Hot Reload doesn't support." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
}
void RenderAutoRecompilePartiallyUnsupportedChanges ( ) {
HotReloadPrefs . AutoRecompilePartiallyUnsupportedChanges = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Include partially unsupported changes" ) , HotReloadPrefs . AutoRecompilePartiallyUnsupportedChanges ) ;
string toggleDescription ;
if ( HotReloadPrefs . AutoRecompilePartiallyUnsupportedChanges ) {
toggleDescription = "Hot Reload will recompile partially unsupported changes." ;
} else {
toggleDescription = "Enable to recompile partially unsupported changes." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
}
void RenderDisplayNewMonobehaviourMethodsAsPartiallySupported ( ) {
HotReloadPrefs . DisplayNewMonobehaviourMethodsAsPartiallySupported = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Display new Monobehaviour methods as partially supported" ) , HotReloadPrefs . DisplayNewMonobehaviourMethodsAsPartiallySupported ) ;
string toggleDescription ;
if ( HotReloadPrefs . DisplayNewMonobehaviourMethodsAsPartiallySupported ) {
toggleDescription = "Hot Reload will display new monobehaviour methods as partially unsupported." ;
} else {
toggleDescription = "Enable to display new monobehaviour methods as partially unsupported." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
}
void RenderAutoRecompileUnsupportedChangesImmediately ( ) {
HotReloadPrefs . AutoRecompileUnsupportedChangesImmediately = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Recompile immediately" ) , HotReloadPrefs . AutoRecompileUnsupportedChangesImmediately ) ;
string toggleDescription ;
if ( HotReloadPrefs . AutoRecompileUnsupportedChangesImmediately ) {
toggleDescription = "Unsupported changes will be recompiled immediately." ;
} else {
toggleDescription = "Unsupported changes will be recompiled when editor is focused. Enable to recompile immediately." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
}
void RenderAutoRecompileUnsupportedChangesInPlayMode ( ) {
HotReloadPrefs . AutoRecompileUnsupportedChangesInPlayMode = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Recompile in Play Mode" ) , HotReloadPrefs . AutoRecompileUnsupportedChangesInPlayMode ) ;
string toggleDescription ;
if ( HotReloadPrefs . AutoRecompileUnsupportedChangesInPlayMode ) {
toggleDescription = "Hot Reload will exit Play Mode to recompile unsupported changes." ;
} else {
toggleDescription = "Enable to auto exit Play Mode to recompile unsupported changes." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
}
void RenderAutoRecompileUnsupportedChangesOnExitPlayMode ( ) {
HotReloadPrefs . AutoRecompileUnsupportedChangesOnExitPlayMode = EditorGUILayout . BeginToggleGroup ( new GUIContent ( "Recompile on exit Play Mode" ) , HotReloadPrefs . AutoRecompileUnsupportedChangesOnExitPlayMode ) ;
string toggleDescription ;
if ( HotReloadPrefs . AutoRecompileUnsupportedChangesOnExitPlayMode ) {
toggleDescription = "Hot Reload will recompile unsupported changes when exiting Play Mode." ;
} else {
toggleDescription = "Enable to recompile unsupported changes when exiting Play Mode." ;
}
EditorGUILayout . LabelField ( toggleDescription , HotReloadWindowStyles . WrapStyle ) ;
EditorGUILayout . EndToggleGroup ( ) ;
}
void RenderOnDevice ( ) {
HotReloadPrefs . ShowOnDevice = EditorGUILayout . Foldout ( HotReloadPrefs . ShowOnDevice , "On-Device" , true , HotReloadWindowStyles . FoldoutStyle ) ;
if ( ! HotReloadPrefs . ShowOnDevice ) {
return ;
}
// header with explainer image
{
if ( headlineStyle = = null ) {
// start with textArea for the background and border colors
headlineStyle = new GUIStyle ( GUI . skin . label ) {
fontStyle = FontStyle . Bold ,
alignment = TextAnchor . MiddleLeft
} ;
headlineStyle . normal . textColor = HotReloadWindowStyles . H2TitleStyle . normal . textColor ;
// bg color
if ( HotReloadWindowStyles . IsDarkMode ) {
headlineStyle . normal . background = EditorTextures . DarkGray40 ;
} else {
headlineStyle . normal . background = EditorTextures . LightGray225 ;
}
// layout
headlineStyle . padding = new RectOffset ( 8 , 8 , 0 , 0 ) ;
headlineStyle . margin = new RectOffset ( 6 , 6 , 6 , 6 ) ;
}
GUILayout . Space ( 9f ) ; // space between logo and headline
GUILayout . Label ( "Make changes to a build running on-device" ,
headlineStyle , GUILayout . MinHeight ( EditorGUIUtility . singleLineHeight * 1.4f ) ) ;
// image showing how Hot Reload works with a phone
// var bannerBox = GUILayoutUtility.GetRect(flowchart.width * 0.6f, flowchart.height * 0.6f);
// GUI.DrawTexture(bannerBox, flowchart, ScaleMode.ScaleToFit);
}
GUILayout . Space ( 16f ) ;
//ButtonToOpenBuildSettings();
{
GUILayout . Label ( "Manual connect" , HotReloadWindowStyles . H3TitleStyle ) ;
EditorGUILayout . Space ( ) ;
GUILayout . BeginHorizontal ( ) ;
// indent all controls (this works with non-labels)
GUILayout . Space ( 16f ) ;
GUILayout . BeginVertical ( ) ;
string text ;
var ip = IpHelper . GetIpAddressCached ( ) ;
if ( string . IsNullOrEmpty ( ip ) ) {
text = $"If auto-pair fails, find your local IP in OS settings, and use this format to connect: '{{ip}}:{RequestHelper.port}'" ;
} else {
text = $"If auto-pair fails, use this IP and port to connect: {ip}:{RequestHelper.port}" +
"\nMake sure you are on the same LAN/WiFi network" ;
}
GUILayout . Label ( text , HotReloadWindowStyles . H3TitleWrapStyle ) ;
if ( ! currentState . isServerHealthy ) {
DrawHorizontalCheck ( ServerHealthCheck . I . IsServerHealthy ,
"Hot Reload is running" ,
"Hot Reload is not running" ,
hasFix : false ) ;
}
if ( ! HotReloadPrefs . ExposeServerToLocalNetwork ) {
var summary = $"Enable '{new ExposeServerOption().ShortSummary}'" ;
DrawHorizontalCheck ( HotReloadPrefs . ExposeServerToLocalNetwork ,
summary ,
summary ) ;
}
// explainer image that shows phone needs same wifi to auto connect ?
GUILayout . EndVertical ( ) ;
GUILayout . EndHorizontal ( ) ;
}
GUILayout . Space ( 16f ) ;
// loading again is smooth, pretty sure AssetDatabase.LoadAssetAtPath is caching -Troy
var settingsObject = HotReloadSettingsEditor . LoadSettingsOrDefault ( ) ;
var so = new SerializedObject ( settingsObject ) ;
// if you build for Android now, will Hot Reload work?
{
EditorGUILayout . BeginHorizontal ( ) ;
GUILayout . Label ( "Build Settings Checklist" , HotReloadWindowStyles . H3TitleStyle ) ;
EditorGUI . BeginDisabledGroup ( isSupported ) ;
// One-click to change each setting to the supported value
if ( GUILayout . Button ( "Fix All" , GUILayout . MaxWidth ( 90f ) ) ) {
FixAllUnsupportedSettings ( so ) ;
}
EditorGUI . EndDisabledGroup ( ) ;
EditorGUILayout . EndHorizontal ( ) ;
// NOTE: After user changed some build settings, window may not immediately repaint
// (e.g. toggle Development Build in Build Settings window)
// We could show a refresh button (to encourage the user to click the window which makes it repaint).
DrawSectionCheckBuildSupport ( so ) ;
}
GUILayout . Space ( 16f ) ;
// Settings checkboxes (Hot Reload options)
{
GUILayout . Label ( "Options" , HotReloadWindowStyles . H3TitleStyle ) ;
if ( settingsObject ) {
optionsSection . DrawGUI ( so ) ;
}
}
GUILayout . FlexibleSpace ( ) ; // needed otherwise vertical scrollbar is appearing for no reason (Unity 2021 glitch perhaps)
}
private void RenderLicenseInfoSection ( ) {
HotReloadRunTab . RenderLicenseInfo (
_window . RunTabState ,
currentState . loginStatus ,
verbose : true ,
allowHide : false ,
overrideActionButton : "Activate License" ,
showConsumptions : true
) ;
}
private void RenderPromoCodeSection ( ) {
_window . RunTab . RenderPromoCodes ( ) ;
}
public void FocusLicenseFoldout ( ) {
HotReloadPrefs . ShowLogin = true ;
}
// note: changing scripting backend does not force Unity to recreate the GUI, so need to check it when drawing.
private ScriptingImplementation ScriptingBackend = > HotReloadBuildHelper . GetCurrentScriptingBackend ( ) ;
private ManagedStrippingLevel StrippingLevel = > HotReloadBuildHelper . GetCurrentStrippingLevel ( ) ;
public bool isSupported = true ;
/// <summary>
/// These options are drawn in the On-device tab
/// </summary>
// new on-device options should be added here
public static readonly IOption [ ] allOptions = new IOption [ ] {
new ExposeServerOption ( ) ,
IncludeInBuildOption . I ,
new AllowAndroidAppToMakeHttpRequestsOption ( ) ,
} ;
/// <summary>
/// Change each setting to the value supported by Hot Reload
/// </summary>
private void FixAllUnsupportedSettings ( SerializedObject so ) {
if ( ! isCurrentBuildTargetSupported . Value ) {
// try switch to Android platform
// (we also support Standalone but HotReload on mobile is a better selling point)
if ( ! TrySwitchToStandalone ( ) ) {
// skip changing other options (user won't readthe gray text) - user has to click Fix All again
return ;
}
}
foreach ( var buildOption in allOptions ) {
if ( ! buildOption . GetValue ( so ) ) {
buildOption . SetValue ( so , true ) ;
}
}
so . ApplyModifiedProperties ( ) ;
var settingsObject = so . targetObject as HotReloadSettingsObject ;
if ( settingsObject ) {
// when you click fix all, make sure to save the settings, otherwise ui does not update
HotReloadSettingsEditor . EnsureSettingsCreated ( settingsObject ) ;
}
if ( ! EditorUserBuildSettings . development ) {
EditorUserBuildSettings . development = true ;
}
HotReloadBuildHelper . SetCurrentScriptingBackend ( ScriptingImplementation . Mono2x ) ;
HotReloadBuildHelper . SetCurrentStrippingLevel ( ManagedStrippingLevel . Disabled ) ;
}
public static bool TrySwitchToStandalone ( ) {
BuildTarget buildTarget ;
if ( Application . platform = = RuntimePlatform . LinuxEditor ) {
buildTarget = BuildTarget . StandaloneLinux64 ;
} else if ( Application . platform = = RuntimePlatform . WindowsEditor ) {
buildTarget = BuildTarget . StandaloneWindows64 ;
} else if ( Application . platform = = RuntimePlatform . OSXEditor ) {
buildTarget = BuildTarget . StandaloneOSX ;
} else {
return false ;
}
var current = EditorUserBuildSettings . activeBuildTarget ;
if ( current = = buildTarget ) {
return true ;
}
var confirmed = EditorUtility . DisplayDialog ( "Switch Build Target" ,
"Switching the build target can take a while depending on project size." ,
$"Switch to Standalone" , "Cancel" ) ;
if ( confirmed ) {
EditorUserBuildSettings . SwitchActiveBuildTargetAsync ( BuildTargetGroup . Standalone , buildTarget ) ;
Log . Info ( $"Build target is switching to {buildTarget}." ) ;
return true ;
} else {
return false ;
}
}
/// <summary>
/// Section that user can check before making a Unity Player build.
/// </summary>
/// <param name="so"></param>
/// <remarks>
/// This section is for confirming your build will work with Hot Reload.<br/>
/// Options that can be changed after the build is made should be drawn elsewhere.
/// </remarks>
public void DrawSectionCheckBuildSupport ( SerializedObject so ) {
isSupported = true ;
var selectedPlatform = currentBuildTarget . Value ;
DrawHorizontalCheck ( isCurrentBuildTargetSupported . Value ,
$"The {selectedPlatform.ToString()} platform is selected" ,
$"The current platform is {selectedPlatform.ToString()} which is not supported" ) ;
using ( new EditorGUI . DisabledScope ( ! isCurrentBuildTargetSupported . Value ) ) {
foreach ( var option in allOptions ) {
DrawHorizontalCheck ( option . GetValue ( so ) ,
$"Enable \" { option . ShortSummary } \ "" ,
$"Enable \" { option . ShortSummary } \ "" ) ;
}
DrawHorizontalCheck ( EditorUserBuildSettings . development ,
"Development Build is enabled" ,
"Enable \"Development Build\"" ) ;
DrawHorizontalCheck ( ScriptingBackend = = ScriptingImplementation . Mono2x ,
$"Scripting Backend is set to Mono" ,
$"Set Scripting Backend to Mono" ) ;
DrawHorizontalCheck ( StrippingLevel = = ManagedStrippingLevel . Disabled ,
$"Stripping Level = {StrippingLevel}" ,
$"Stripping Level = {StrippingLevel}" ,
suggestedSolutionText : "Code stripping needs to be disabled to ensure that all methods are available for patching."
) ;
}
}
/// <summary>
/// Draw a box with a tick or warning icon on the left, with text describing the tick or warning
/// </summary>
/// <param name="condition">The condition to check. True to show a tick icon, False to show a warning.</param>
/// <param name="okText">Shown when condition is true</param>
/// <param name="notOkText">Shown when condition is false</param>
/// <param name="suggestedSolutionText">Shown when <paramref name="condition"/> is false</param>
void DrawHorizontalCheck ( bool condition , string okText , string notOkText = null , string suggestedSolutionText = null , bool hasFix = true ) {
if ( okText = = null ) {
throw new ArgumentNullException ( nameof ( okText ) ) ;
}
if ( notOkText = = null ) {
notOkText = okText ;
}
// include some horizontal space around the icon
var boxWidth = GUILayout . Width ( EditorGUIUtility . singleLineHeight * 1.31f ) ;
var height = GUILayout . Height ( EditorGUIUtility . singleLineHeight * 1.01f ) ;
GUILayout . BeginHorizontal ( HotReloadWindowStyles . BoxStyle , height , GUILayout . ExpandWidth ( true ) ) ;
var style = HotReloadWindowStyles . NoPaddingMiddleLeftStyle ;
var iconRect = GUILayoutUtility . GetRect (
Mathf . Round ( EditorGUIUtility . singleLineHeight * 1.31f ) ,
Mathf . Round ( EditorGUIUtility . singleLineHeight * 1.01f ) ,
style , boxWidth , height , GUILayout . ExpandWidth ( false ) ) ;
// rounded so we can have pixel perfect black circle bg
iconRect . Set ( Mathf . Round ( iconRect . x ) , Mathf . Round ( iconRect . y ) , Mathf . CeilToInt ( iconRect . width ) ,
Mathf . CeilToInt ( iconRect . height ) ) ;
var text = condition ? okText : notOkText ;
var icon = condition ? iconCheck : iconWarning ;
if ( GUI . enabled ) {
DrawBlackCircle ( iconRect ) ;
// resource can be null when building player (Editor Resources not available)
if ( icon ) {
GUI . DrawTexture ( iconRect , icon , ScaleMode . ScaleToFit ) ;
}
} else {
// show something (instead of hiding) so that layout stays same size
DrawDisabledCircle ( iconRect ) ;
}
GUILayout . Space ( 4f ) ;
GUILayout . Label ( text , style , height ) ;
if ( ! condition & & hasFix ) {
isSupported = false ;
}
GUILayout . EndHorizontal ( ) ;
if ( ! condition & & ! String . IsNullOrEmpty ( suggestedSolutionText ) ) {
// suggest to the user how they can resolve the issue
EditorGUI . indentLevel + + ;
GUILayout . Label ( suggestedSolutionText , HotReloadWindowStyles . WrapStyle ) ;
EditorGUI . indentLevel - - ;
}
}
void DrawDisabledCircle ( Rect rect ) = > DrawCircleIcon ( rect ,
Resources . Load < Texture > ( "icon_circle_gray" ) ,
Color . clear ) ; // smaller circle draws less attention
void DrawBlackCircle ( Rect rect ) = > DrawCircleIcon ( rect ,
Resources . Load < Texture > ( "icon_circle_black" ) ,
new Color ( 0.14f , 0.14f , 0.14f ) ) ; // black is too dark in unity light theme
void DrawCircleIcon ( Rect rect , Texture circleIcon , Color borderColor ) {
// Note: drawing texture from resources is pixelated on the edges, so it has some transperancy around the edges.
// While building for Android, Resources.Load returns null for our editor Resources.
if ( circleIcon ! = null ) {
GUI . DrawTexture ( rect , circleIcon , ScaleMode . ScaleToFit ) ;
}
// Draw smooth circle border
const float borderWidth = 2f ;
GUI . DrawTexture ( rect , EditorTextures . White , ScaleMode . ScaleToFit , true ,
0f ,
borderColor ,
new Vector4 ( borderWidth , borderWidth , borderWidth , borderWidth ) ,
Mathf . Min ( rect . height , rect . width ) / 2f ) ;
}
}
}