[Add] Hot Reload

This commit is contained in:
2026-02-27 03:16:18 +07:00
parent 5067cb51a1
commit b37579153b
431 changed files with 43054 additions and 1 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dddc1cae3f951f84da98305ec6228f25
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 86f1446dfdbc2a94aac993437231aaa4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,42 @@
using UnityEditor;
using UnityEngine;
namespace SingularityGroup.HotReload.Editor {
internal class OpenDialogueButton : IGUIComponent {
public readonly string text;
public readonly string url;
public readonly string title;
public readonly string message;
public readonly string ok;
public readonly string cancel;
public OpenDialogueButton(string text, string url, string title, string message, string ok, string cancel) {
this.text = text;
this.url = url;
this.title = title;
this.message = message;
this.ok = ok;
this.cancel = cancel;
}
public void OnGUI() {
Render(text, url, title, message, ok, cancel);
}
public static void Render(string text, string url, string title, string message, string ok, string cancel) {
if (GUILayout.Button(new GUIContent(text.StartsWith(" ") ? text : " " + text))) {
if (EditorUtility.DisplayDialog(title, message, ok, cancel)) {
Application.OpenURL(url);
}
}
}
public static void RenderRaw(Rect rect, string text, string url, string title, string message, string ok, string cancel, GUIStyle style = null) {
if (GUI.Button(rect, new GUIContent(text.StartsWith(" ") ? text : " " + text), style ?? GUI.skin.button)) {
if (EditorUtility.DisplayDialog(title, message, ok, cancel)) {
Application.OpenURL(url);
}
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 97ca8174f0514e8e9ee5d4be26ed8078
timeCreated: 1674416481
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Buttons/OpenDialogueButton.cs
uploadId: 870414
@@ -0,0 +1,29 @@
using UnityEditor;
using UnityEngine;
namespace SingularityGroup.HotReload.Editor {
internal class OpenURLButton : IGUIComponent {
public readonly string text;
public readonly string url;
public OpenURLButton(string text, string url) {
this.text = text;
this.url = url;
}
public void OnGUI() {
Render(text, url);
}
public static void Render(string text, string url) {
if (GUILayout.Button(new GUIContent(text.StartsWith(" ") ? text : " " + text))) {
Application.OpenURL(url);
}
}
public static void RenderRaw(Rect rect, string text, string url, GUIStyle style = null) {
if (GUI.Button(rect, new GUIContent(text.StartsWith(" ") ? text : " " + text), style ?? GUI.skin.button)) {
Application.OpenURL(url);
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ef12252fc9d1f9f438cbd34cf8f7364b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Buttons/OpenURLButton.cs
uploadId: 870414
@@ -0,0 +1,116 @@
using UnityEngine;
namespace SingularityGroup.HotReload.Editor {
/// <summary>
/// Create a new texture only once. Safe access to generated textures.
/// </summary>
/// <remarks>
/// If </remarks>
internal static class EditorTextures {
private static Texture2D black;
private static Texture2D white;
private static Texture2D lightGray225;
private static Texture2D lightGray235;
private static Texture2D darkGray17;
private static Texture2D darkGray30;
// Texture2D.blackTexture doesn't render properly in Editor GUI.
public static Texture2D Black {
get {
if (!black) {
black = new Texture2D(2, 2, TextureFormat.RGBA32, false);
var pixels = black.GetPixels32();
for (var i = 0; i < pixels.Length; i++) {
pixels[i] = new Color32(0, 0, 0, byte.MaxValue);
}
black.SetPixels32(pixels);
black.Apply();
}
return black;
}
}
// Texture2D.whiteTexture might not render properly in Editor GUI.
public static Texture2D White {
get {
if (!white) {
white = new Texture2D(2, 2, TextureFormat.RGBA32, false);
var pixels = white.GetPixels32();
for (var i = 0; i < pixels.Length; i++) {
pixels[i] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
}
white.SetPixels32(pixels);
white.Apply();
}
return white;
}
}
public static Texture2D DarkGray17 {
get {
if (!darkGray17) {
darkGray17 = new Texture2D(2, 2, TextureFormat.RGBA32, false);
var pixels = darkGray17.GetPixels32();
for (var i = 0; i < pixels.Length; i++) {
pixels[i] = new Color32(17, 17, 17, byte.MaxValue);
}
darkGray17.SetPixels32(pixels);
darkGray17.Apply();
}
return darkGray17;
}
}
public static Texture2D DarkGray40 {
get {
if (!darkGray30) {
darkGray30 = new Texture2D(2, 2, TextureFormat.RGBA32, false);
var pixels = darkGray30.GetPixels32();
for (var i = 0; i < pixels.Length; i++) {
pixels[i] = new Color32(40, 40, 40, byte.MaxValue);
}
darkGray30.SetPixels32(pixels);
darkGray30.Apply();
}
return darkGray30;
}
}
public static Texture2D LightGray238 {
get {
if (!lightGray235) {
lightGray235 = new Texture2D(2, 2, TextureFormat.RGBA32, false);
var pixels = lightGray235.GetPixels32();
for (var i = 0; i < pixels.Length; i++) {
pixels[i] = new Color32(238, 238, 238, byte.MaxValue);
}
lightGray235.SetPixels32(pixels);
lightGray235.Apply();
}
return lightGray235;
}
}
public static Texture2D LightGray225 {
get {
if (!lightGray225) {
lightGray225 = new Texture2D(2, 2, TextureFormat.RGBA32, false);
var pixels = lightGray225.GetPixels32();
for (var i = 0; i < pixels.Length; i++) {
pixels[i] = new Color32(225, 225, 225, byte.MaxValue);
}
lightGray225.SetPixels32(pixels);
lightGray225.Apply();
}
return lightGray225;
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 9116854180be4f2b8fcc0422bcf570a5
timeCreated: 1674127121
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/EditorTextures.cs
uploadId: 870414
@@ -0,0 +1,5 @@
namespace SingularityGroup.HotReload.Editor {
internal interface IGUIComponent {
void OnGUI();
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 893cb208871dab94488cb988920f0ebd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/IGUIComponent.cs
uploadId: 870414
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2b3fa5ea1ed3545429de96b41801942f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
using SingularityGroup.HotReload.Editor.Localization;
using UnityEditor;
namespace SingularityGroup.HotReload.Editor {
internal class AllowAndroidAppToMakeHttpRequestsOption : ProjectOptionBase {
public override string ShortSummary {
get {
return Translations.Settings.OptionAllowHttpRequests;
}
}
public override string Summary => ShortSummary;
public override bool GetValue(SerializedObject so) {
#if UNITY_2022_1_OR_NEWER
// use PlayerSettings as the source of truth
return PlayerSettings.insecureHttpOption != InsecureHttpOption.NotAllowed;
#else
return GetProperty(so).boolValue;
#endif
}
public override string ObjectPropertyName =>
nameof(HotReloadSettingsObject.AllowAndroidAppToMakeHttpRequests);
public override void SetValue(SerializedObject so, bool value) {
base.SetValue(so, value);
// Enabling on Unity 2022 or newer → set the Unity option to Development Builds only
#if UNITY_2022_1_OR_NEWER
var notAllowed = PlayerSettings.insecureHttpOption == InsecureHttpOption.NotAllowed;
if (value) {
// user chose to enable it
if (notAllowed) {
PlayerSettings.insecureHttpOption = InsecureHttpOption.DevelopmentOnly;
}
} else {
// user chose to disable it
PlayerSettings.insecureHttpOption = InsecureHttpOption.NotAllowed;
}
#endif
}
public override void InnerOnGUI(SerializedObject so) {
var description = Translations.Settings.OptionAllowHttpRequestsDescription;
EditorGUILayout.LabelField(description, HotReloadWindowStyles.WrapStyle);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 0a7442cee510ab4498ca2a846e0c4e92
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Options/AllowAndroidAppToMakeHttpRequestsOption.cs
uploadId: 870414
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bb8474c37f13d704d96b43e0f681680d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,57 @@
using UnityEditor;
namespace SingularityGroup.HotReload.Editor {
/// <summary>
/// An option stored inside the current Unity project.
/// </summary>
internal abstract class ProjectOptionBase : IOption, ISerializedProjectOption {
public abstract string ShortSummary { get; }
public abstract string Summary { get; }
public virtual bool GetValue(SerializedObject so) {
return so.FindProperty(ObjectPropertyName).boolValue;
}
protected SerializedProperty GetProperty(SerializedObject so) {
return so.FindProperty(ObjectPropertyName);
}
public virtual void SetValue(SerializedObject so, bool value) {
so.FindProperty(ObjectPropertyName).boolValue = value;
}
public virtual void InnerOnGUI(SerializedObject so) { }
public abstract string ObjectPropertyName { get; }
/// <remarks>
/// Override this if your option is not needed for on-device Hot Reload to work.<br/>
/// (by default, a project option must be true for Hot Reload to work)
/// </remarks>
public virtual bool IsRequiredForBuild() {
return true;
}
}
/// <summary>
/// An option that is stored on the user's computer (shared between Unity projects).
/// </summary>
internal abstract class ComputerOptionBase : IOption {
public abstract string ShortSummary { get; }
public abstract string Summary { get; }
public abstract bool GetValue();
/// Uses <see cref="HotReloadPrefs"/> for storing the value on the user's computer.
public virtual void SetValue(bool value) { }
public bool GetValue(SerializedObject so) => GetValue();
public virtual void SetValue(SerializedObject so, bool value) => SetValue(value);
void IOption.InnerOnGUI(SerializedObject so) {
InnerOnGUI();
}
public virtual void InnerOnGUI() { }
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: dab8ef53c2ee30a40ab6a7e4abd1260c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Options/Base/HotReloadOptionBase.cs
uploadId: 870414
@@ -0,0 +1,34 @@
using UnityEditor;
namespace SingularityGroup.HotReload.Editor {
public interface IOption {
string ShortSummary { get; }
string Summary { get; }
/// <param name="so">The <see cref="HotReloadSettingsObject"/> wrapped by SerializedObject</param>
bool GetValue(SerializedObject so);
/// <summary>
/// Handle the new value.
/// </summary>
/// <remarks>
/// Note: caller must skip calling this if value same as GetValue!
/// </remarks>
/// <param name="so">The <see cref="HotReloadSettingsObject"/> wrapped by SerializedObject</param>
/// <param name="value"></param>
void SetValue(SerializedObject so, bool value);
/// <param name="so">The <see cref="HotReloadSettingsObject"/> wrapped by SerializedObject</param>
void InnerOnGUI(SerializedObject so);
}
/// <summary>
/// An option scoped to the current Unity project.
/// </summary>
/// <remarks>
/// These options are intended to be shared with collaborators and used by Unity Player builds.
/// </remarks>
public interface ISerializedProjectOption {
string ObjectPropertyName { get; }
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 0a626aa97160471f85de4646a634bdf1
timeCreated: 1674574633
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Options/Base/OptionInterfaces.cs
uploadId: 870414
@@ -0,0 +1,70 @@
using System;
using System.Threading.Tasks;
using SingularityGroup.HotReload.Editor.Cli;
using SingularityGroup.HotReload.Editor.Localization;
using UnityEditor;
namespace SingularityGroup.HotReload.Editor {
internal sealed class ExposeServerOption : ComputerOptionBase {
public override string ShortSummary => Translations.Settings.OptionExposeServerShort;
public override string Summary => Translations.Settings.OptionExposeServerFull;
public override void InnerOnGUI() {
string description;
if (GetValue()) {
description = Translations.Settings.OptionExposeServerDescriptionEnabled;
} else {
description = Translations.Settings.OptionExposeServerDescriptionDisabled;
}
EditorGUILayout.LabelField(description, HotReloadWindowStyles.WrapStyle);
}
public override bool GetValue() {
return HotReloadPrefs.ExposeServerToLocalNetwork;
}
public override void SetValue(SerializedObject so, bool val) {
// AllowAndroidAppToMakeHttpRequestsOption
if (val == HotReloadPrefs.ExposeServerToLocalNetwork) {
return;
}
HotReloadPrefs.ExposeServerToLocalNetwork = val;
if (val) {
// they allowed this one for mobile builds, so now we allow everything else needed for player build to work with HR
new AllowAndroidAppToMakeHttpRequestsOption().SetValue(so, true);
}
RunTask(() => {
RunOnMainThreadSync(() => {
var isRunningResult = ServerHealthCheck.I.IsServerHealthy;
if (isRunningResult) {
var restartServer = EditorUtility.DisplayDialog(Translations.Dialogs.DialogTitleHotReload,
string.Format(Translations.Dialogs.DialogMessageRestartExposeServer, Summary),
Translations.Dialogs.DialogButtonRestartServer, Translations.Dialogs.DialogButtonDontRestart);
if (restartServer) {
CodePatcher.I.ClearPatchedMethods();
EditorCodePatcher.RestartCodePatcher().Forget();
}
}
});
});
}
void RunTask(Action action) {
var token = HotReloadWindow.Current.cancelToken;
Task.Run(() => {
if (token.IsCancellationRequested) return;
try {
action();
} catch (Exception ex) {
ThreadUtility.LogException(ex, token);
}
}, token);
}
void RunOnMainThreadSync(Action action) {
ThreadUtility.RunOnMainThread(action, HotReloadWindow.Current.cancelToken);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5ab0973d3ae1275469237480381842c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Options/ExposeServerOption.cs
uploadId: 870414
@@ -0,0 +1,25 @@
using SingularityGroup.HotReload.Editor.Localization;
using UnityEditor;
namespace SingularityGroup.HotReload.Editor {
internal class IncludeInBuildOption : ProjectOptionBase, ISerializedProjectOption {
static IncludeInBuildOption _I;
public static IncludeInBuildOption I = _I ?? (_I = new IncludeInBuildOption());
public override string ShortSummary => Translations.Settings.OptionIncludeInBuild;
public override string Summary => ShortSummary;
public override string ObjectPropertyName =>
nameof(HotReloadSettingsObject.IncludeInBuild);
public override void InnerOnGUI(SerializedObject so) {
string description;
if (GetValue(so)) {
description = Translations.Settings.OptionIncludeInBuildDescriptionEnabled;
} else {
description = Translations.Settings.OptionIncludeInBuildDescriptionDisabled;
}
description += Translations.Settings.OptionIncludeInBuildDescriptionSuffix;
EditorGUILayout.LabelField(description, HotReloadWindowStyles.WrapStyle);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 39ed4f822bcd81340bdf7189b3bc5016
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Options/IncludeInBuildOption.cs
uploadId: 870414
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c0f7811020465d46bcd0305e2f83e8a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 58d14712b7ef14540ba4817a5ef873a6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
using UnityEditor;
using UnityEngine;
namespace SingularityGroup.HotReload.Editor {
internal abstract class HotReloadTabBase : IGUIComponent {
protected readonly HotReloadWindow _window;
public string Title { get; }
public Texture Icon { get; }
public string Tooltip { get; }
public HotReloadTabBase(HotReloadWindow window, string title, Texture iconImage, string tooltip) {
_window = window;
Title = title;
Icon = iconImage;
Tooltip = tooltip;
}
public HotReloadTabBase(HotReloadWindow window, string title, string iconName, string tooltip) :
this(window, title, EditorGUIUtility.IconContent(iconName).image, tooltip) {
}
protected void Repaint() {
_window.Repaint();
}
public virtual void Update() { }
public abstract void OnGUI();
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c2c79b82bd9636d499449f91f93fae2a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/Base/HotReloadTabBase.cs
uploadId: 870414
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a089a7225d904b00b2893a34b514ad28
timeCreated: 1689791626
@@ -0,0 +1,308 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using SingularityGroup.HotReload.DTO;
using SingularityGroup.HotReload.Editor.Localization;
using SingularityGroup.HotReload.Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
namespace SingularityGroup.HotReload.Editor {
internal enum RedeemStage {
None,
Registration,
Redeem,
Login
}
// IMPORTANT: don't rename
internal enum RegistrationOutcome {
None,
Indie,
Business,
}
internal class RedeemLicenseHelper {
public static readonly RedeemLicenseHelper I = new RedeemLicenseHelper();
private string _pendingCompanySize;
private string _pendingInvoiceNumber;
private string _pendingRedeemEmail;
private static string registerFlagPath = PackageConst.LibraryCachePath + "/registerFlag.txt";
public static string registerOutcomePath = PackageConst.LibraryCachePath + "/registerOutcome.txt";
public RedeemStage RedeemStage { get; private set; }
public RegistrationOutcome RegistrationOutcome { get; private set; }
public bool RegistrationRequired => RedeemStage != RedeemStage.None;
private string status;
private string error;
const string statusSuccess = "success";
const string statusAlreadyClaimed = "already redeemed by this user/device";
private GUILayoutOption[] secondaryButtonLayoutOptions = new[] { GUILayout.MaxWidth(100) };
private bool requestingRedeem;
private HttpClient redeemClient;
const string redeemUrl = "https://vmhzj6jonn3qy7hk7tx7levpli0bstpj.lambda-url.us-east-1.on.aws/redeem";
public RedeemLicenseHelper() {
if (File.Exists(registerFlagPath)) {
RedeemStage = RedeemStage.Registration;
}
try {
if (File.Exists(registerOutcomePath)) {
RegistrationOutcome outcome;
if (Enum.TryParse(File.ReadAllText(registerOutcomePath), out outcome)) {
RegistrationOutcome = outcome;
}
}
} catch (Exception e) {
Log.Warning(Translations.Errors.WarningFailedDeterminingRegistration, e.GetType().Name, e.Message);
}
}
public void RenderStage(HotReloadRunTabState state) {
if (state.redeemStage == RedeemStage.Registration) {
RenderRegistration();
} else if (state.redeemStage == RedeemStage.Redeem) {
RenderRedeem();
} else if (state.redeemStage == RedeemStage.Login) {
RenderLogin(state);
}
}
private void RenderRegistration() {
var message = PackageConst.IsAssetStoreBuild
? Translations.Registration.MessageRegistrationProUsers
: Translations.Registration.MessageRegistrationLicensingModel;
if (error != null) {
EditorGUILayout.HelpBox(error, MessageType.Warning);
} else {
EditorGUILayout.HelpBox(message, MessageType.Info);
}
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.LabelField(Translations.Common.LabelCompanySize);
GUI.SetNextControlName("company_size");
_pendingCompanySize = EditorGUILayout.TextField(_pendingCompanySize)?.Trim();
EditorGUILayout.Space();
if (GUILayout.Button(Translations.Common.ButtonProceed)) {
int companySize;
if (!int.TryParse(_pendingCompanySize, out companySize)) {
error = Translations.Errors.ErrorEnterNumber;
} else {
error = null;
HandleRegistration(companySize);
}
}
}
void HandleRegistration(int companySize) {
RequestHelper.RequestEditorEvent(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.Licensing, StatEventType.Register), new EditorExtraData { { StatKey.CompanySize, companySize } });
if (companySize > 10) {
FinishRegistration(RegistrationOutcome.Business);
EditorCodePatcher.DownloadAndRun().Forget();
} else if (PackageConst.IsAssetStoreBuild) {
SwitchToStage(RedeemStage.Redeem);
} else {
FinishRegistration(RegistrationOutcome.Indie);
EditorCodePatcher.DownloadAndRun().Forget();
}
}
private void RenderRedeem() {
if (error != null) {
EditorGUILayout.HelpBox(error, MessageType.Warning);
} else {
EditorGUILayout.HelpBox(Translations.Registration.MessageRedeemInstructions, MessageType.Info);
}
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.LabelField(Translations.Common.LabelInvoiceNumber);
GUI.SetNextControlName("invoice_number");
_pendingInvoiceNumber = EditorGUILayout.TextField(_pendingInvoiceNumber ?? HotReloadPrefs.RedeemLicenseInvoice)?.Trim();
EditorGUILayout.Space();
EditorGUILayout.LabelField(Translations.Common.LabelEmail);
GUI.SetNextControlName("email_redeem");
_pendingRedeemEmail = EditorGUILayout.TextField(_pendingRedeemEmail ?? HotReloadPrefs.RedeemLicenseEmail);
EditorGUILayout.Space();
using (new EditorGUI.DisabledScope(requestingRedeem)) {
if (GUILayout.Button(Translations.Common.ButtonRedeem, HotReloadRunTab.bigButtonHeight)) {
RedeemLicense(email: _pendingRedeemEmail, invoiceNumber: _pendingInvoiceNumber).Forget();
}
}
EditorGUILayout.Space();
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope()) {
GUILayout.FlexibleSpace();
if (GUILayout.Button(Translations.Common.ButtonSkip, secondaryButtonLayoutOptions)) {
SwitchToStage(RedeemStage.Login);
}
GUILayout.FlexibleSpace();
}
}
async Task RedeemLicense(string email, string invoiceNumber) {
string validationError;
if (string.IsNullOrEmpty(invoiceNumber)) {
validationError = Translations.Errors.ErrorEnterInvoiceNumber;
} else {
validationError = HotReloadRunTab.ValidateEmail(email);
}
if (validationError != null) {
error = validationError;
return;
}
var resp = await RequestRedeem(email: email, invoiceNumber: invoiceNumber);
status = resp?.status;
if (status != null) {
if (status != statusSuccess && status != statusAlreadyClaimed) {
Log.Error(Translations.Errors.WarningRedeemStatusUnknown);
error = Translations.Registration.UnknownRedeemError;
} else {
HotReloadPrefs.RedeemLicenseEmail = email;
HotReloadPrefs.RedeemLicenseInvoice = invoiceNumber;
// prepare data for login screen
HotReloadPrefs.LicenseEmail = email;
HotReloadPrefs.LicensePassword = null;
SwitchToStage(RedeemStage.Login);
}
} else if (resp?.error != null) {
Log.Warning(Translations.Errors.WarningRedeemingLicenseFailed, resp.error);
error = GetPrettyError(resp);
} else {
Log.Warning(Translations.Errors.WarningRedeemUnknownError);
error = Translations.Registration.UnknownRedeemError;
}
}
string GetPrettyError(RedeemResponse response) {
var err = response?.error;
if (err == null) {
return Translations.Registration.UnknownRedeemError;
}
if (err.Contains("Invalid email")) {
return Translations.Errors.ErrorInvalidEmailAddress;
} else if (err.Contains("License invoice already redeemed")) {
return Translations.Errors.ErrorLicenseInvoiceRedeemed;
} else if (err.Contains("Different license already redeemed by given email")) {
return Translations.Errors.ErrorEmailAlreadyUsed;
} else if (err.Contains("Invoice not found")) {
return Translations.Errors.ErrorInvoiceNotFound;
} else if (err.Contains("Invoice refunded")) {
return Translations.Errors.ErrorInvoiceRefunded;
} else {
return Translations.Registration.UnknownRedeemError;
}
}
async Task<RedeemResponse> RequestRedeem(string email, string invoiceNumber) {
requestingRedeem = true;
await ThreadUtility.SwitchToThreadPool();
try {
redeemClient = redeemClient ?? (redeemClient = HttpClientUtils.CreateHttpClient());
var input = new Dictionary<string, string> {
{ "email", email },
{ "invoice", invoiceNumber }
};
var content = new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8, "application/json");
using (var resp = await redeemClient.PostAsync(redeemUrl, content, HotReloadWindow.Current.cancelToken).ConfigureAwait(false)) {
if (resp.StatusCode != HttpStatusCode.OK) {
return new RedeemResponse(null, string.Format(Translations.Errors.ErrorRedeemRequestFailed, (int)resp.StatusCode, resp.ReasonPhrase));
}
var str = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
try {
return JsonConvert.DeserializeObject<RedeemResponse>(str);
} catch (Exception ex) {
return new RedeemResponse(null, string.Format(Translations.Errors.ErrorFailedDeserializingRedeem, ex.GetType().Name, ex.Message));
}
}
} catch (WebException ex) {
return new RedeemResponse(null, string.Format(Translations.Errors.ErrorRedeemingWebException, ex.Message));
} finally {
requestingRedeem = false;
}
}
private class RedeemResponse {
public string status;
public string error;
public RedeemResponse(string status, string error) {
this.status = status;
this.error = error;
}
}
private void RenderLogin(HotReloadRunTabState state) {
if (status == statusSuccess) {
EditorGUILayout.HelpBox(Translations.Registration.MessageRedeemSuccess, MessageType.Info);
} else if (status == statusAlreadyClaimed) {
EditorGUILayout.HelpBox(Translations.Registration.MessageRedeemAlreadyClaimed, MessageType.Info);
}
EditorGUILayout.Space();
EditorGUILayout.Space();
HotReloadRunTab.RenderLicenseInnerPanel(state, renderLogout: false);
EditorGUILayout.Space();
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope()) {
GUILayout.FlexibleSpace();
if (GUILayout.Button(Translations.Common.ButtonGoBack, secondaryButtonLayoutOptions)) {
SwitchToStage(RedeemStage.Redeem);
}
GUILayout.FlexibleSpace();
}
}
public void StartRegistration() {
// ReSharper disable once AssignNullToNotNullAttribute
Directory.CreateDirectory(Path.GetDirectoryName(registerFlagPath));
using (File.Create(registerFlagPath)) {
}
RedeemStage = RedeemStage.Registration;
RegistrationOutcome = RegistrationOutcome.None;
}
public void FinishRegistration(RegistrationOutcome outcome) {
// ReSharper disable once AssignNullToNotNullAttribute
Directory.CreateDirectory(Path.GetDirectoryName(registerFlagPath));
File.WriteAllText(registerOutcomePath, outcome.ToString());
File.Delete(registerFlagPath);
RegistrationOutcome = outcome;
SwitchToStage(RedeemStage.None);
Cleanup();
}
void SwitchToStage(RedeemStage stage) {
// remove focus so that the input field re-renders
GUI.FocusControl(null);
RedeemStage = stage;
}
void Cleanup() {
redeemClient?.Dispose();
redeemClient = null;
_pendingCompanySize = null;
_pendingInvoiceNumber = null;
_pendingRedeemEmail = null;
status = null;
error = null;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ad73f74d3c494c02aae937e2dfa305a2
timeCreated: 1689791373
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/Helpers/RedeemLicenseHelper.cs
uploadId: 870414
@@ -0,0 +1,312 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using SingularityGroup.HotReload.Editor.Localization;
using UnityEditor;
using UnityEngine;
using System.Threading.Tasks;
using System.IO;
using SingularityGroup.HotReload.Newtonsoft.Json;
using SingularityGroup.HotReload.EditorDependencies;
namespace SingularityGroup.HotReload.Editor {
internal struct HotReloadAboutTabState {
public readonly bool logsFodlerExists;
public readonly IReadOnlyList<ChangelogVersion> changelog;
public readonly bool loginRequired;
public readonly bool hasTrialLicense;
public readonly bool hasPayedLicense;
public HotReloadAboutTabState(
bool logsFodlerExists,
IReadOnlyList<ChangelogVersion> changelog,
bool loginRequired,
bool hasTrialLicense,
bool hasPayedLicense
) {
this.logsFodlerExists = logsFodlerExists;
this.changelog = changelog;
this.loginRequired = loginRequired;
this.hasTrialLicense = hasTrialLicense;
this.hasPayedLicense = hasPayedLicense;
}
}
internal class HotReloadAboutTab : HotReloadTabBase {
internal static readonly OpenURLButton seeMore = new OpenURLButton(Translations.About.ButtonSeeMore, Constants.ChangelogURL);
internal static readonly OpenDialogueButton manageLicenseButton = new OpenDialogueButton(Translations.About.ButtonManageLicense, Constants.ManageLicenseURL, Translations.About.ButtonManageLicense, Translations.Dialogs.DialogManageLicenseMessage, Translations.Common.ButtonOpenInBrowser, Translations.Common.ButtonCancel);
internal static readonly OpenDialogueButton manageAccountButton = new OpenDialogueButton(Translations.About.ButtonManageAccount, Constants.ManageAccountURL, Translations.About.ButtonManageAccount, Translations.Dialogs.DialogManageAccountMessage, Translations.Common.ButtonOpenInBrowser, Translations.Common.ButtonCancel);
internal static readonly OpenURLButton contactButton = new OpenURLButton(Translations.About.ButtonContact, Constants.ContactURL);
internal static readonly OpenURLButton discordButton = new OpenURLButton(Translations.About.ButtonJoinDiscord, Constants.DiscordInviteUrl);
internal static readonly OpenDialogueButton reportIssueButton = new OpenDialogueButton(Translations.About.ButtonReportIssue, Constants.ReportIssueURL, Translations.About.ButtonReportIssue, Translations.Dialogs.DialogReportIssueMessage, Translations.Common.ButtonOpenInBrowser, Translations.Common.ButtonCancel);
private Vector2 _changelogScroll;
private IReadOnlyList<ChangelogVersion> _changelog = new List<ChangelogVersion>();
private bool _requestedChangelog;
private int _changelogRequestAttempt;
private string _changelogDir = Path.Combine(PackageConst.LibraryCachePath, "changelog.json");
public static string logsPath = Path.Combine(PackageConst.LibraryCachePath, "logs");
private static bool LatestChangelogLoaded(IReadOnlyList<ChangelogVersion> changelog) {
return changelog.Any() && changelog[0].versionNum == PackageUpdateChecker.lastRemotePackageVersion;
}
private async Task FetchChangelog() {
if(!_changelog.Any()) {
var file = new FileInfo(_changelogDir);
if (file.Exists) {
await Task.Run(() => {
var bytes = File.ReadAllText(_changelogDir);
_changelog = JsonConvert.DeserializeObject<List<ChangelogVersion>>(bytes);
});
}
}
if (_requestedChangelog || LatestChangelogLoaded(_changelog)) {
return;
}
_requestedChangelog = true;
try {
do {
var changelogRequestTimeout = ExponentialBackoff.GetTimeout(_changelogRequestAttempt);
_changelog = await RequestHelper.FetchChangelog() ?? _changelog;
if (LatestChangelogLoaded(_changelog)) {
await Task.Run(() => {
Directory.CreateDirectory(PackageConst.LibraryCachePath);
File.WriteAllText(_changelogDir, JsonConvert.SerializeObject(_changelog));
});
Repaint();
return;
}
await Task.Delay(changelogRequestTimeout);
} while (_changelogRequestAttempt++ < 1000 && !LatestChangelogLoaded(_changelog));
} catch {
// ignore
} finally {
_requestedChangelog = false;
}
}
public HotReloadAboutTab(HotReloadWindow window) : base(window, Translations.About.AboutTitle, "_Help", Translations.About.AboutDescription) { }
string GetRelativeDate(DateTime givenDate) {
const int second = 1;
const int minute = 60 * second;
const int hour = 60 * minute;
const int day = 24 * hour;
const int month = 30 * day;
var ts = new TimeSpan(DateTime.UtcNow.Ticks - givenDate.Ticks);
var delta = Math.Abs(ts.TotalSeconds);
if (delta < 24 * hour)
return Translations.About.AboutToday;
if (delta < 48 * hour)
return Translations.About.AboutYesterday;
if (delta < 30 * day)
return string.Format(Translations.About.AboutDaysAgo, ts.Days);
if (delta < 12 * month) {
var months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
return months <= 1 ? Translations.About.AboutOneMonthAgo : string.Format(Translations.About.AboutMonthsAgo, months);
}
var years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
return years <= 1 ? Translations.About.AboutOneYearAgo : string.Format(Translations.About.AboutYearsAgo, years);
}
void RenderVersion(ChangelogVersion version) {
var tempTextString = "";
//version number
EditorGUILayout.TextArea(version.versionNum, HotReloadWindowStyles.H1TitleStyle);
//general info
if (version.generalInfo != null) {
EditorGUILayout.TextArea(version.generalInfo, HotReloadWindowStyles.H3TitleStyle);
}
//features
if (version.features != null) {
EditorGUILayout.TextArea(Translations.About.AboutFeatures, HotReloadWindowStyles.H2TitleStyle);
tempTextString = "";
foreach (var feature in version.features) {
tempTextString += "• " + feature + "\n";
}
EditorGUILayout.TextArea(tempTextString, HotReloadWindowStyles.ChangelogPointerStyle);
}
//improvements
if (version.improvements != null) {
EditorGUILayout.TextArea(Translations.About.AboutImprovements, HotReloadWindowStyles.H2TitleStyle);
tempTextString = "";
foreach (var improvement in version.improvements) {
tempTextString += "• " + improvement + "\n";
}
EditorGUILayout.TextArea(tempTextString, HotReloadWindowStyles.ChangelogPointerStyle);
}
//fixes
if (version.fixes != null) {
EditorGUILayout.TextArea(Translations.About.AboutFixes, HotReloadWindowStyles.H2TitleStyle);
tempTextString = "";
foreach (var fix in version.fixes) {
tempTextString += "• " + fix + "\n";
}
EditorGUILayout.TextArea(tempTextString, HotReloadWindowStyles.ChangelogPointerStyle);
}
//date
DateTime date;
if (DateTime.TryParseExact(version.date, "dd/MM/yyyy", null, DateTimeStyles.None, out date)) {
var relativeDate = GetRelativeDate(date);
GUILayout.TextArea(relativeDate, HotReloadWindowStyles.H3TitleStyle);
}
}
void RenderChangelog() {
FetchChangelog().Forget();
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
using (new EditorGUILayout.VerticalScope()) {
HotReloadPrefs.ShowChangeLog = EditorGUILayout.Foldout(HotReloadPrefs.ShowChangeLog, Translations.Miscellaneous.ChangelogTitle, true, HotReloadWindowStyles.FoldoutStyle);
if (!HotReloadPrefs.ShowChangeLog) {
return;
}
// changelog versions
var maxChangeLogs = 5;
var index = 0;
foreach (var version in currentState.changelog) {
index++;
if (index > maxChangeLogs) {
break;
}
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.ChangelogSectionInnerBox)) {
using (new EditorGUILayout.VerticalScope()) {
RenderVersion(version);
}
}
}
// see more button
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.ChangelogSectionInnerBox)) {
seeMore.OnGUI();
}
}
}
}
private Vector2 _aboutTabScrollPos;
HotReloadAboutTabState 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 HotReloadAboutTabState(
logsFodlerExists: Directory.Exists(logsPath),
changelog: _changelog,
loginRequired: EditorCodePatcher.LoginNotRequired,
hasTrialLicense: _window.RunTab.TrialLicense,
hasPayedLicense: _window.RunTab.HasPayedLicense
);
}
using (var scope = new EditorGUILayout.ScrollViewScope(_aboutTabScrollPos, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUILayout.MaxHeight(Math.Max(HotReloadWindowStyles.windowScreenHeight, 800)), GUILayout.MaxWidth(Math.Max(HotReloadWindowStyles.windowScreenWidth, 800)))) {
_aboutTabScrollPos.x = scope.scrollPosition.x;
_aboutTabScrollPos.y = scope.scrollPosition.y;
using (new EditorGUILayout.VerticalScope(HotReloadWindowStyles.DynamicSectionHelpTab)) {
using (new EditorGUILayout.VerticalScope()) {
GUILayout.Space(10);
RenderLogButtons();
EditorGUILayout.Space();
EditorGUILayout.HelpBox(string.Format(Translations.About.AboutVersionInfo, PackageConst.Version), MessageType.Info);
EditorGUILayout.Space();
RenderHelpButtons();
GUILayout.Space(15);
try {
RenderChangelog();
} catch {
// ignore
}
}
}
}
}
void RenderHelpButtons() {
var labelRect = GUILayoutUtility.GetLastRect();
using (new EditorGUILayout.HorizontalScope()) {
using (new EditorGUILayout.VerticalScope()) {
var buttonHeight = 19;
var bigButtonRect = new Rect(labelRect.x + 3, labelRect.y + 5, labelRect.width - 6, buttonHeight);
OpenURLButton.RenderRaw(bigButtonRect, Translations.About.ButtonDocumentation, Constants.DocumentationURL, HotReloadWindowStyles.HelpTabButton);
var firstLayerX = bigButtonRect.x;
var firstLayerY = bigButtonRect.y + buttonHeight + 3;
var firstLayerWidth = (int)((bigButtonRect.width / 2) - 3);
var secondLayerX = firstLayerX + firstLayerWidth + 5;
var secondLayerY = firstLayerY + buttonHeight + 3;
var secondLayerWidth = bigButtonRect.width - firstLayerWidth - 5;
using (new EditorGUILayout.HorizontalScope()) {
OpenURLButton.RenderRaw(new Rect { x = firstLayerX, y = firstLayerY, width = firstLayerWidth, height = buttonHeight }, contactButton.text, contactButton.url, HotReloadWindowStyles.HelpTabButton);
OpenURLButton.RenderRaw(new Rect { x = secondLayerX, y = firstLayerY, width = secondLayerWidth, height = buttonHeight }, Translations.About.ButtonUnityForum, Constants.ForumURL, HotReloadWindowStyles.HelpTabButton);
}
using (new EditorGUILayout.HorizontalScope()) {
OpenDialogueButton.RenderRaw(rect: new Rect { x = firstLayerX, y = secondLayerY, width = firstLayerWidth, height = buttonHeight }, text: reportIssueButton.text, url: reportIssueButton.url, title: reportIssueButton.title, message: reportIssueButton.message, ok: reportIssueButton.ok, cancel: reportIssueButton.cancel, style: HotReloadWindowStyles.HelpTabButton);
OpenURLButton.RenderRaw(new Rect { x = secondLayerX, y = secondLayerY, width = secondLayerWidth, height = buttonHeight }, discordButton.text, discordButton.url, HotReloadWindowStyles.HelpTabButton);
}
}
}
GUILayout.Space(80);
}
void RenderLogButtons() {
if (currentState.logsFodlerExists) {
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button(Translations.Common.ButtonOpenLogFile)) {
var mostRecentFile = LogsHelper.FindRecentLog(logsPath);
if (mostRecentFile == null) {
Log.Info(Translations.About.LogNoLogsFound);
} else {
try {
Process.Start($"\"{Path.Combine(logsPath, mostRecentFile)}\"");
} catch (Win32Exception e) {
// TODO: is this the same for chinese?
if (e.Message.Contains("Application not found")) {
try {
Process.Start("notepad.exe", $"\"{Path.Combine(logsPath, mostRecentFile)}\"");
} catch {
// Fallback to opening folder with all logs
Process.Start($"\"{logsPath}\"");
Log.Info(Translations.About.LogFailedOpeningLogFile);
}
}
} catch {
// Fallback to opening folder with all logs
Process.Start($"\"{logsPath}\"");
Log.Info(Translations.About.LogFailedOpeningLogFile);
}
}
}
if (GUILayout.Button(Translations.Common.ButtonBrowseAllLogs)) {
Process.Start($"\"{logsPath}\"");
}
EditorGUILayout.EndHorizontal();
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7cf8e9ef1ab770249a4318e88e882a85
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadAboutTab.cs
uploadId: 870414
@@ -0,0 +1,50 @@
using UnityEditor;
using UnityEngine;
using SingularityGroup.HotReload.Editor.Localization;
namespace SingularityGroup.HotReload.Editor {
internal class HotReloadOptionsSection {
/// <remarks>
/// Opening options tab does not automatically create the settings asset file.
/// - The Options UI shows defaults if the object asset doesn't exist.
/// - When a build starts, we also ensure the asset file exists.
/// </remarks>
public void DrawGUI(SerializedObject so) {
so.Update(); // must update in-case asset was modified externally
foreach (var option in HotReloadSettingsTab.allOptions) {
GUILayout.Space(4f);
DrawOption(option, so);
}
// commit any changes to the underlying ScriptableObject
if (so.hasModifiedProperties) {
so.ApplyModifiedProperties();
// Ensure asset file exists on disk, because we initially create it in memory (to provide the default values)
// This does not save the asset, user has to do that by saving assets in Unity (e.g. press hotkey Ctrl + S)
var target = so.targetObject as HotReloadSettingsObject;
if (target == null) {
Log.Warning(Translations.Errors.WarningUnexpectedSaveProblem);
} else {
// when one of the project options changed then we ensure the asset file exists.
HotReloadSettingsEditor.EnsureSettingsCreated(target);
}
}
}
static void DrawOption(IOption option, SerializedObject so) {
EditorGUILayout.BeginVertical(HotReloadWindowStyles.BoxStyle);
var before = option.GetValue(so);
var after = EditorGUILayout.BeginToggleGroup(new GUIContent(" " + option.Summary), before);
if (after != before) {
option.SetValue(so, after);
}
option.InnerOnGUI(so);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.EndVertical();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 24379a407eff8494eac0f7841b70e574
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadOptionsSection.cs
uploadId: 870414
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 38d0877009d34a9458f7d169d7f1b6a7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadRunTab.cs
uploadId: 870414
@@ -0,0 +1,945 @@
using System;
using System.Diagnostics.CodeAnalysis;
using SingularityGroup.HotReload.DTO;
using SingularityGroup.HotReload.Editor.Cli;
using SingularityGroup.HotReload.Editor.Localization;
using RuntimeLocalization = SingularityGroup.HotReload.Localization;
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,
Translations.Settings.SettingsTitle,
"_Popup",
Translations.OnDevice.OnDeviceHeadline) {
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, Translations.Settings.SettingsConfiguration, 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();
RenderAutoRecompileInspectorFieldEdits();
RenderAutoRecompilePartiallyUnsupportedChanges();
RenderDisplayNewMonobehaviourMethodsAsPartiallySupported();
RenderAutoRecompileUnsupportedChangesInEditMode();
}
}
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) }))) {
RenderAutoClearTimeline();
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, Translations.Settings.SettingsAdvanced, true, HotReloadWindowStyles.FoldoutStyle);
if (HotReloadPrefs.ShowAdvanced) {
EditorGUILayout.Space();
DeactivateHotReload();
DisableDetailedErrorReporting();
PauseHotReloadInEditMode();
#if UNITY_EDITOR_WIN
if (PackageConst.DefaultLocale == RuntimeLocalization.Locale.English) {
UseWatchman();
}
#endif
}
}
}
}
}
}
}
void RenderUnityAutoRefresh() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleManageAutoRefresh), HotReloadPrefs.AllowDisableUnityAutoRefresh);
if (newSettings != HotReloadPrefs.AllowDisableUnityAutoRefresh) {
HotReloadPrefs.AllowDisableUnityAutoRefresh = newSettings;
}
string toggleDescription;
if (HotReloadPrefs.AllowDisableUnityAutoRefresh) {
toggleDescription = Translations.Settings.SettingsManageAutoRefreshOn;
} else {
toggleDescription = Translations.Settings.SettingsManageAutoRefreshOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
void RenderAssetRefresh() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleAssetRefresh), HotReloadPrefs.AllAssetChanges);
if (newSettings != HotReloadPrefs.AllAssetChanges) {
HotReloadPrefs.AllAssetChanges = newSettings;
// restart when setting changes
if (ServerHealthCheck.I.IsServerHealthy) {
var restartServer = EditorUtility.DisplayDialog(Translations.Dialogs.DialogTitleRestartServer,
Translations.Dialogs.DialogMessageRestartAssetRefresh,
Translations.Dialogs.DialogButtonRestartHotReload, Translations.Dialogs.DialogButtonDontRestart);
if (restartServer) {
EditorCodePatcher.RestartCodePatcher().Forget();
}
}
}
string toggleDescription;
if (HotReloadPrefs.AllAssetChanges) {
toggleDescription = Translations.Settings.SettingsAssetRefreshOn;
} else {
toggleDescription = Translations.Settings.SettingsAssetRefreshOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
void RenderDebuggerCompatibility() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleDebuggerCompatibility), HotReloadPrefs.AutoDisableHotReloadWithDebugger);
if (newSettings != HotReloadPrefs.AutoDisableHotReloadWithDebugger) {
HotReloadPrefs.AutoDisableHotReloadWithDebugger = newSettings;
CodePatcher.I.debuggerCompatibilityEnabled = !HotReloadPrefs.AutoDisableHotReloadWithDebugger;
}
string toggleDescription;
if (HotReloadPrefs.AutoDisableHotReloadWithDebugger) {
toggleDescription = Translations.Settings.SettingsDebuggerCompatibilityOn;
} else {
toggleDescription = Translations.Settings.SettingsDebuggerCompatibilityOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
void RenderIncludeShaderChanges() {
HotReloadPrefs.IncludeShaderChanges = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleRefreshShaders), HotReloadPrefs.IncludeShaderChanges);
string toggleDescription;
if (HotReloadPrefs.IncludeShaderChanges) {
toggleDescription = Translations.Settings.SettingsRefreshShadersOn;
} else {
toggleDescription = Translations.Settings.SettingsRefreshShadersOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderConsoleWindow() {
if (!HotReloadCli.CanOpenInBackground) {
return;
}
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleHideConsole), HotReloadPrefs.DisableConsoleWindow);
if (newSettings != HotReloadPrefs.DisableConsoleWindow) {
HotReloadPrefs.DisableConsoleWindow = newSettings;
// restart when setting changes
if (ServerHealthCheck.I.IsServerHealthy) {
var restartServer = EditorUtility.DisplayDialog(Translations.Dialogs.DialogTitleRestartServer,
Translations.Dialogs.DialogMessageRestartConsoleWindow,
Translations.Dialogs.DialogButtonRestartServer, Translations.Dialogs.DialogButtonDontRestart);
if (restartServer) {
EditorCodePatcher.RestartCodePatcher().Forget();
}
}
}
string toggleDescription;
if (HotReloadPrefs.DisableConsoleWindow) {
toggleDescription = Translations.Settings.SettingsHideConsoleOn;
} else {
toggleDescription = Translations.Settings.SettingsHideConsoleOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
void DeactivateHotReload() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleDeactivate), HotReloadPrefs.DeactivateHotReload);
if (newSettings != HotReloadPrefs.DeactivateHotReload) {
DeactivateHotReloadInner(newSettings);
}
string toggleDescription;
if (HotReloadPrefs.DeactivateHotReload) {
toggleDescription = Translations.Settings.SettingsDeactivatedOn;
} else {
toggleDescription = Translations.Settings.SettingsDeactivatedOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
void DisableDetailedErrorReporting() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleDisableErrorReporting), HotReloadPrefs.DisableDetailedErrorReporting);
DisableDetailedErrorReportingInner(newSettings);
string toggleDescription;
if (HotReloadPrefs.DisableDetailedErrorReporting) {
toggleDescription = Translations.Settings.SettingsDisableErrorReportingOn;
} else {
toggleDescription = Translations.Settings.SettingsDisableErrorReportingOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
void PauseHotReloadInEditMode() {
HotReloadPrefs.PauseHotReloadInEditMode = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.TogglePauseEditMode), HotReloadPrefs.PauseHotReloadInEditMode);
string toggleDescription;
if (HotReloadPrefs.PauseHotReloadInEditMode) {
toggleDescription = Translations.Settings.SettingsPauseEditModeOn;
} else {
toggleDescription = Translations.Settings.SettingsPauseEditModeOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
#if UNITY_EDITOR_WIN
void UseWatchman() {
HotReloadPrefs.UseWatchman = EditorGUILayout.BeginToggleGroup(new GUIContent("Use watchman"), HotReloadPrefs.UseWatchman);
string toggleDescription;
if (HotReloadPrefs.UseWatchman) {
toggleDescription = "Use watchman file watcher";
} else {
toggleDescription = "Use default file watcher";
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space(6f);
}
#endif
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(Translations.Dialogs.DialogTitleRestartServer,
Translations.Dialogs.DialogMessageRestartErrorReporting,
Translations.Dialogs.DialogButtonRestartServer, Translations.Dialogs.DialogButtonDontRestart);
if (restartServer) {
EditorCodePatcher.RestartCodePatcher().Forget();
}
}
}
static void DeactivateHotReloadInner(bool deactivate) {
var confirmed = !deactivate || EditorUtility.DisplayDialog(Translations.Dialogs.DialogTitleDeactivate,
Translations.Dialogs.DialogMessageDeactivate,
Translations.Dialogs.DialogButtonDeactivate, Translations.Common.ButtonCancel);
if (confirmed) {
HotReloadPrefs.DeactivateHotReload = deactivate;
if (deactivate) {
EditorCodePatcher.StopCodePatcher(recompileOnDone: true).Forget();
} else {
HotReloadRunTab.Recompile();
}
}
}
void RenderAutoClearTimeline() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleAutoClearTimeline), HotReloadPrefs.AutoClearTimeline);
if (newSettings != HotReloadPrefs.AutoClearTimeline) {
HotReloadPrefs.AutoClearTimeline = newSettings;
}
string toggleDescription;
if (HotReloadPrefs.AutoClearTimeline) {
toggleDescription = Translations.Settings.SettingsAutoClearTimelineOn;
} else {
toggleDescription = Translations.Settings.SettingsAutoClearTimelineOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space();
}
void RenderAutostart() {
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleAutostart), HotReloadPrefs.LaunchOnEditorStart);
if (newSettings != HotReloadPrefs.LaunchOnEditorStart) {
HotReloadPrefs.LaunchOnEditorStart = newSettings;
}
string toggleDescription;
if (HotReloadPrefs.LaunchOnEditorStart) {
toggleDescription = Translations.Settings.SettingsAutostartOn;
} else {
toggleDescription = Translations.Settings.SettingsAutostartOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space();
}
void RenderShowNotifications() {
EditorGUILayout.Space(10f);
GUILayout.Label(Translations.Settings.SettingsVisualFeedback, HotReloadWindowStyles.NotificationsTitleStyle);
EditorGUILayout.Space(10f);
if (!EditorWindowHelper.supportsNotifications && !UnitySettingsHelper.I.playmodeTintSupported) {
var toggleDescription = Translations.Settings.SettingsIndicationsUnsupported;
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
}
}
// void RenderShowFields() {
// EditorGUILayout.Space(14f);
// GUILayout.Label("Fields", HotReloadWindowStyles.NotificationsTitleStyle);
// }
void RenderMiscHeader() {
EditorGUILayout.Space(10f);
GUILayout.Label(Translations.Settings.SettingsMisc, HotReloadWindowStyles.NotificationsTitleStyle);
EditorGUILayout.Space(10f);
}
void RenderShowPatchingNotifications() {
HotReloadPrefs.ShowPatchingNotifications = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.TogglePatchingIndication), HotReloadPrefs.ShowPatchingNotifications);
string toggleDescription;
if (!EditorWindowHelper.supportsNotifications) {
toggleDescription = Translations.Settings.SettingsPatchingIndicationUnsupported;
} else if (!HotReloadPrefs.ShowPatchingNotifications) {
toggleDescription = Translations.Settings.SettingsPatchingIndicationOff;
} else {
toggleDescription = Translations.Settings.SettingsPatchingIndicationOn;
}
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(Translations.MenuItems.NotImplementedObsolete)]
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(Translations.Dialogs.DialogTitleRestartServer,
Translations.Dialogs.DialogMessageRestartFieldInitializer,
Translations.Dialogs.DialogButtonRestartServer, Translations.Dialogs.DialogButtonDontRestart);
if (restartServer) {
EditorCodePatcher.RestartCodePatcher().Forget();
}
}
}
}
void RenderShowCompilingUnsupportedNotifications() {
HotReloadPrefs.ShowCompilingUnsupportedNotifications = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleCompilingIndication), HotReloadPrefs.ShowCompilingUnsupportedNotifications);
string toggleDescription;
if (!EditorWindowHelper.supportsNotifications) {
toggleDescription = Translations.Settings.SettingsCompilingIndicationUnsupported;
} else if (!HotReloadPrefs.ShowCompilingUnsupportedNotifications) {
toggleDescription = Translations.Settings.SettingsCompilingIndicationOff;
} else {
toggleDescription = Translations.Settings.SettingsCompilingIndicationOn;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderAutoRecompileUnsupportedChanges() {
HotReloadPrefs.AutoRecompileUnsupportedChanges = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleAutoRecompile), HotReloadPrefs.AutoRecompileUnsupportedChanges && EditorCodePatcher.autoRecompileUnsupportedChangesSupported);
string toggleDescription;
if (!EditorCodePatcher.autoRecompileUnsupportedChangesSupported) {
toggleDescription = Translations.Settings.SettingsAutoRecompileUnsupported;
} else if (HotReloadPrefs.AutoRecompileUnsupportedChanges) {
toggleDescription = Translations.Settings.SettingsAutoRecompileOn;
} else {
toggleDescription = Translations.Settings.SettingsAutoRecompileOff;
}
if (!HotReloadPrefs.AutoRecompileUnsupportedChangesInEditMode && !HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode) {
HotReloadPrefs.AutoRecompileUnsupportedChangesInEditMode = true;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderAutoRecompileInspectorFieldEdits() {
HotReloadPrefs.AutoRecompileInspectorFieldsEdit = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleAutoRecompileInspector), HotReloadPrefs.AutoRecompileInspectorFieldsEdit);
string toggleDescription;
if (HotReloadPrefs.AutoRecompileInspectorFieldsEdit) {
toggleDescription = Translations.Settings.SettingsAutoRecompileInspectorOn;
} else {
toggleDescription = Translations.Settings.SettingsAutoRecompileInspectorOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderAutoRecompilePartiallyUnsupportedChanges() {
HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleAutoRecompilePartial), HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges);
string toggleDescription;
if (HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges) {
toggleDescription = Translations.Settings.SettingsAutoRecompilePartialOn;
} else {
toggleDescription = Translations.Settings.SettingsAutoRecompilePartialOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderDisplayNewMonobehaviourMethodsAsPartiallySupported() {
HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleDisplayMonobehaviour), HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported);
string toggleDescription;
if (HotReloadPrefs.DisplayNewMonobehaviourMethodsAsPartiallySupported) {
toggleDescription = Translations.Settings.SettingsDisplayMonobehaviourOn;
} else {
toggleDescription = Translations.Settings.SettingsDisplayMonobehaviourOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderAutoRecompileUnsupportedChangesImmediately() {
HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleRecompileImmediately), HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately);
string toggleDescription;
if (HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately) {
toggleDescription = Translations.Settings.SettingsRecompileImmediatelyOn;
} else {
toggleDescription = Translations.Settings.SettingsRecompileImmediatelyOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderAutoRecompileUnsupportedChangesInPlayMode() {
HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleRecompilePlayMode), HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode);
string toggleDescription;
if (HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode) {
toggleDescription = Translations.Settings.SettingsRecompilePlayModeOn;
} else {
toggleDescription = Translations.Settings.SettingsRecompilePlayModeOff;
}
if (!HotReloadPrefs.AutoRecompileUnsupportedChangesInEditMode && !HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode) {
HotReloadPrefs.AutoRecompileUnsupportedChanges = false;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderAutoRecompileUnsupportedChangesInEditMode() {
HotReloadPrefs.AutoRecompileUnsupportedChangesInEditMode = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleRecompileEditMode), HotReloadPrefs.AutoRecompileUnsupportedChangesInEditMode);
string toggleDescription;
if (HotReloadPrefs.AutoRecompileUnsupportedChangesInEditMode) {
toggleDescription = Translations.Settings.SettingsRecompileEditModeOn;
} else {
toggleDescription = Translations.Settings.SettingsRecompileEditModeOff;
}
if (!HotReloadPrefs.AutoRecompileUnsupportedChangesInEditMode && !HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode) {
HotReloadPrefs.AutoRecompileUnsupportedChanges = false;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderAutoRecompileUnsupportedChangesOnExitPlayMode() {
HotReloadPrefs.AutoRecompileUnsupportedChangesOnExitPlayMode = EditorGUILayout.BeginToggleGroup(new GUIContent(Translations.Settings.ToggleRecompileExitPlayMode), HotReloadPrefs.AutoRecompileUnsupportedChangesOnExitPlayMode);
string toggleDescription;
if (HotReloadPrefs.AutoRecompileUnsupportedChangesOnExitPlayMode) {
toggleDescription = Translations.Settings.SettingsRecompileExitPlayModeOn;
} else {
toggleDescription = Translations.Settings.SettingsRecompileExitPlayModeOff;
}
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
EditorGUILayout.EndToggleGroup();
}
void RenderOnDevice() {
HotReloadPrefs.ShowOnDevice = EditorGUILayout.Foldout(HotReloadPrefs.ShowOnDevice, Translations.Settings.SettingsOnDevice, 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(Translations.OnDevice.OnDeviceHeadline,
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(Translations.Settings.SettingsManualConnect, 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 = string.Format(Translations.OnDevice.OnDeviceManualConnectFormat, RequestHelper.port);
} else {
text = string.Format(Translations.OnDevice.OnDeviceManualConnectWithIP, ip, RequestHelper.port);
}
GUILayout.Label(text, HotReloadWindowStyles.H3TitleWrapStyle);
if (!currentState.isServerHealthy) {
DrawHorizontalCheck(ServerHealthCheck.I.IsServerHealthy,
Translations.OnDevice.OnDeviceCheckHotReloadRunning,
Translations.OnDevice.OnDeviceCheckHotReloadNotRunning,
hasFix: false);
}
if (!HotReloadPrefs.ExposeServerToLocalNetwork) {
var summary = string.Format(Translations.OnDevice.OnDeviceCheckEnableExposeServer, 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(Translations.Settings.SettingsBuildSettingsChecklist, HotReloadWindowStyles.H3TitleStyle);
EditorGUI.BeginDisabledGroup(isSupported);
// One-click to change each setting to the supported value
if (GUILayout.Button(Translations.Common.ButtonFixAll, 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(Translations.Settings.SettingsOptions, 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:Translations.Common.ButtonActivateLicense,
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(Translations.Dialogs.DialogTitleSwitchBuildTarget,
Translations.Dialogs.DialogMessageSwitchBuildTarget,
Translations.Dialogs.DialogButtonSwitchToStandalone, Translations.Common.ButtonCancel);
if (confirmed) {
EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.Standalone, buildTarget);
Log.Info(Translations.About.LogBuildTargetSwitching, 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,
string.Format(Translations.OnDevice.OnDeviceCheckPlatformSelected, selectedPlatform.ToString()),
string.Format(Translations.OnDevice.OnDeviceCheckPlatformNotSupported, selectedPlatform.ToString()));
using (new EditorGUI.DisabledScope(!isCurrentBuildTargetSupported.Value)) {
foreach (var option in allOptions) {
DrawHorizontalCheck(option.GetValue(so),
string.Format(Translations.OnDevice.OnDeviceCheckEnableExposeServer, option.ShortSummary),
string.Format(Translations.OnDevice.OnDeviceCheckEnableExposeServer, option.ShortSummary));
}
DrawHorizontalCheck(EditorUserBuildSettings.development,
Translations.OnDevice.OnDeviceCheckDevelopmentEnabled,
Translations.OnDevice.OnDeviceCheckEnableDevelopment);
DrawHorizontalCheck(ScriptingBackend == ScriptingImplementation.Mono2x,
Translations.OnDevice.OnDeviceCheckMonoBackend,
Translations.OnDevice.OnDeviceCheckSetMonoBackend);
DrawHorizontalCheck(StrippingLevel == ManagedStrippingLevel.Disabled,
string.Format(Translations.OnDevice.OnDeviceCheckStrippingLevel, StrippingLevel),
string.Format(Translations.OnDevice.OnDeviceCheckStrippingLevel, StrippingLevel),
suggestedSolutionText: Translations.OnDevice.OnDeviceCheckStrippingSolution
);
}
}
/// <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);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: fff71bd159424bf2978e2e99eacba9b4
timeCreated: 1674057842
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/GUI/Tabs/HotReloadSettingsTab.cs
uploadId: 870414
@@ -0,0 +1,393 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading;
using SingularityGroup.HotReload.DTO;
using SingularityGroup.HotReload.Editor.Cli;
using SingularityGroup.HotReload.Editor.Semver;
using UnityEditor;
using UnityEditor.Compilation;
using SingularityGroup.HotReload.Editor.Localization;
using UnityEngine;
[assembly: InternalsVisibleTo("SingularityGroup.HotReload.EditorSamples")]
namespace SingularityGroup.HotReload.Editor {
class HotReloadWindow : EditorWindow {
public static HotReloadWindow Current { get; private set; }
List<HotReloadTabBase> tabs;
List<HotReloadTabBase> Tabs => tabs ?? (tabs = new List<HotReloadTabBase> {
RunTab,
SettingsTab,
AboutTab,
});
int selectedTab;
internal static Vector2 scrollPos;
static Timer timer;
HotReloadRunTab runTab;
internal HotReloadRunTab RunTab => runTab ?? (runTab = new HotReloadRunTab(this));
HotReloadSettingsTab settingsTab;
internal HotReloadSettingsTab SettingsTab => settingsTab ?? (settingsTab = new HotReloadSettingsTab(this));
HotReloadAboutTab aboutTab;
internal HotReloadAboutTab AboutTab => aboutTab ?? (aboutTab = new HotReloadAboutTab(this));
static ShowOnStartupEnum _showOnStartupOption;
/// <summary>
/// This token is cancelled when the EditorWindow is disabled.
/// </summary>
/// <remarks>
/// Use it for all tasks.
/// When token is cancelled, scripts are about to be recompiled and this will cause tasks to fail for weird reasons.
/// </remarks>
public CancellationToken cancelToken;
CancellationTokenSource cancelTokenSource;
static readonly PackageUpdateChecker packageUpdateChecker = new PackageUpdateChecker();
[MenuItem(Translations.MenuItems.OpenHotReload)]
internal static void Open() {
// Don't open Hot Reload window inside Virtual Player folder
if (MultiplayerPlaymodeHelper.IsClone) {
Log.Info("Virtual Player instances use the same Hot Reload server instance as the Main Editor. Use Hot Reload window in the Main Editor.");
return;
}
// opening the window on CI systems was keeping Unity open indefinitely
if (EditorWindowHelper.IsHumanControllingUs()) {
if (Current) {
Current.Show();
Current.Focus();
} else {
Current = GetWindow<HotReloadWindow>();
}
}
}
[MenuItem(Translations.MenuItems.RecompileHotReload)]
internal static void Recompile() {
HotReloadRunTab.Recompile();
}
void OnInterval(object o) {
HotReloadRunTab.RepaintInstant();
}
void OnEnable() {
if (timer == null) {
timer = new Timer(OnInterval, null, 20 * 1000, 20 * 1000);
}
Current = this;
if (cancelTokenSource != null) {
cancelTokenSource.Cancel();
}
// Set min size initially so that full UI is visible
if (!HotReloadPrefs.OpenedWindowAtLeastOnce) {
this.minSize = new Vector2(Constants.RecompileButtonTextHideWidth + 1, Constants.EventsListHideHeight + 70);
HotReloadPrefs.OpenedWindowAtLeastOnce = true;
}
cancelTokenSource = new CancellationTokenSource();
cancelToken = cancelTokenSource.Token;
this.titleContent = new GUIContent(" Hot Reload", GUIHelper.GetInvertibleIcon(InvertibleIcon.Logo));
_showOnStartupOption = HotReloadPrefs.ShowOnStartup;
packageUpdateChecker.StartCheckingForNewVersion();
}
void Update() {
foreach (var tab in Tabs) {
tab.Update();
}
}
void OnDisable() {
if (cancelTokenSource != null) {
cancelTokenSource.Cancel();
cancelTokenSource = null;
}
if (Current == this) {
Current = null;
}
timer.Dispose();
timer = null;
}
internal void SelectTab(Type tabType) {
selectedTab = Tabs.FindIndex(x => x.GetType() == tabType);
}
public HotReloadRunTabState RunTabState { get; private set; }
void OnGUI() {
// TabState 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) {
RunTabState = HotReloadRunTabState.Current;
}
using(var scope = new EditorGUILayout.ScrollViewScope(scrollPos, false, false)) {
scrollPos = scope.scrollPosition;
// RenderDebug();
RenderTabs();
}
GUILayout.FlexibleSpace(); // GUI below will be rendered on the bottom
if (HotReloadWindowStyles.windowScreenHeight > 90)
RenderBottomBar();
}
void RenderDebug() {
if (GUILayout.Button("RESET WINDOW")) {
OnDisable();
RequestHelper.RequestLogin("test", "test", 1).Forget();
HotReloadPrefs.LicenseEmail = null;
HotReloadPrefs.ExposeServerToLocalNetwork = true;
HotReloadPrefs.LicensePassword = null;
HotReloadPrefs.LoggedBurstHint = false;
HotReloadPrefs.DontShowPromptForDownload = false;
HotReloadPrefs.RateAppShown = false;
HotReloadPrefs.ActiveDays = string.Empty;
HotReloadPrefs.LaunchOnEditorStart = false;
HotReloadPrefs.ShowUnsupportedChanges = true;
HotReloadPrefs.RedeemLicenseEmail = null;
HotReloadPrefs.RedeemLicenseInvoice = null;
OnEnable();
File.Delete(EditorCodePatcher.serverDownloader.GetExecutablePath(HotReloadCli.controller));
InstallUtility.DebugClearInstallState();
InstallUtility.CheckForNewInstall();
EditorPrefs.DeleteKey(Attribution.LastLoginKey);
File.Delete(RedeemLicenseHelper.registerOutcomePath);
CompileMethodDetourer.Reset();
AssetDatabase.Refresh();
}
}
internal static void RenderLogo(int width = 243) {
var isDarkMode = HotReloadWindowStyles.IsDarkMode;
var tex = Resources.Load<Texture>(isDarkMode ? "Logo_HotReload_DarkMode" : "Logo_HotReload_LightMode");
//Can happen during player builds where Editor Resources are unavailable
if(tex == null) {
return;
}
var targetWidth = width;
var targetHeight = 44;
GUILayout.Space(4f);
// background padding top and bottom
float padding = 5f;
// reserve layout space for the texture
var backgroundRect = GUILayoutUtility.GetRect(targetWidth + padding, targetHeight + padding, HotReloadWindowStyles.LogoStyle);
// draw the texture into that reserved space. First the bg then the logo.
if (isDarkMode) {
GUI.DrawTexture(backgroundRect, EditorTextures.DarkGray17, ScaleMode.StretchToFill);
} else {
GUI.DrawTexture(backgroundRect, EditorTextures.LightGray238, ScaleMode.StretchToFill);
}
var foregroundRect = backgroundRect;
foregroundRect.yMin += padding;
foregroundRect.yMax -= padding;
// during player build (EditorWindow still visible), Resources.Load returns null
if (tex) {
GUI.DrawTexture(foregroundRect, tex, ScaleMode.ScaleToFit);
}
}
int? collapsedTab;
void RenderTabs() {
using(new EditorGUILayout.VerticalScope(HotReloadWindowStyles.BoxStyle)) {
if (HotReloadWindowStyles.windowScreenHeight > 210 && HotReloadWindowStyles.windowScreenWidth > 375) {
selectedTab = GUILayout.Toolbar(
selectedTab,
Tabs.Select(t =>
new GUIContent(t.Title.StartsWith(" ", StringComparison.Ordinal) ? t.Title : " " + t.Title,
t.Icon, t.Tooltip)).ToArray(),
GUILayout.Height(22f) // required, otherwise largest icon height determines toolbar height
);
if (collapsedTab != null) {
selectedTab = collapsedTab.Value;
collapsedTab = null;
}
} else {
if (collapsedTab == null) {
collapsedTab = selectedTab;
}
// When window is super small, we pretty much can only show run tab
SelectTab(typeof(HotReloadRunTab));
}
if (HotReloadWindowStyles.windowScreenHeight > 250 && HotReloadWindowStyles.windowScreenWidth > 275) {
RenderLogo();
}
Tabs[selectedTab].OnGUI();
}
}
void RenderBottomBar() {
SemVersion newVersion;
var updateAvailable = packageUpdateChecker.TryGetNewVersion(out newVersion);
if (HotReloadWindowStyles.windowScreenWidth > Constants.RateAppHideWidth
&& HotReloadWindowStyles.windowScreenHeight > Constants.RateAppHideHeight
) {
RenderRateApp();
}
if (updateAvailable) {
RenderUpdateButton(newVersion);
}
using(new EditorGUILayout.HorizontalScope("ProjectBrowserBottomBarBg", GUILayout.ExpandWidth(true), GUILayout.Height(25f))) {
RenderBottomBarCore();
}
}
static GUIStyle _renderAppBoxStyle;
static GUIStyle renderAppBoxStyle => _renderAppBoxStyle ?? (_renderAppBoxStyle = new GUIStyle(GUI.skin.box) {
padding = new RectOffset(10, 10, 0, 0)
});
static GUILayoutOption[] _nonExpandable;
public static GUILayoutOption[] NonExpandableLayout => _nonExpandable ?? (_nonExpandable = new [] {GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true)});
internal static void RenderRateApp() {
if (!ShouldShowRateApp()) {
return;
}
using (new EditorGUILayout.VerticalScope(renderAppBoxStyle)) {
using (new EditorGUILayout.HorizontalScope()) {
HotReloadGUIHelper.HelpBox(Translations.Miscellaneous.RateAppQuestion, MessageType.Info, 11);
if (GUILayout.Button(Translations.Common.ButtonHide, NonExpandableLayout)) {
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.RateApp), new EditorExtraData { { "dismissed", true } }).Forget();
HotReloadPrefs.RateAppShown = true;
}
}
using (new EditorGUILayout.HorizontalScope()) {
if (GUILayout.Button(Translations.Common.ButtonYes)) {
var openedUrl = PackageConst.IsAssetStoreBuild && EditorUtility.DisplayDialog(Translations.Dialogs.DialogTitleRateApp, Translations.Dialogs.DialogMessageRateApp, Translations.Common.ButtonOpenInBrowser, Translations.Common.ButtonCancel);
if (openedUrl) {
Application.OpenURL(Constants.UnityStoreRateAppURL);
}
HotReloadPrefs.RateAppShown = true;
var data = new EditorExtraData();
if (PackageConst.IsAssetStoreBuild) {
data.Add("opened_url", openedUrl);
}
data.Add("enjoy_app", true);
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.RateApp), data).Forget();
}
if (GUILayout.Button(Translations.Common.ButtonNo)) {
HotReloadPrefs.RateAppShown = true;
var data = new EditorExtraData();
data.Add("enjoy_app", false);
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.RateApp), data).Forget();
}
}
}
}
internal static bool ShouldShowRateApp() {
if (HotReloadPrefs.RateAppShown) {
return false;
}
var activeDays = EditorCodePatcher.GetActiveDaysForRateApp();
if (activeDays.Count < Constants.DaysToRateApp) {
return false;
}
return true;
}
void RenderUpdateButton(SemVersion newVersion) {
if (GUILayout.Button(string.Format(Translations.Miscellaneous.ButtonUpdateToVersionFormat, newVersion), HotReloadWindowStyles.UpgradeButtonStyle)) {
packageUpdateChecker.UpdatePackageAsync(newVersion).Forget(CancellationToken.None);
}
}
internal static void RenderShowOnStartup() {
var prevLabelWidth = EditorGUIUtility.labelWidth;
try {
EditorGUIUtility.labelWidth = 105f;
using (new GUILayout.VerticalScope()) {
using (new GUILayout.HorizontalScope()) {
GUILayout.Label(Translations.Common.LabelShowOnStartup);
Rect buttonRect = GUILayoutUtility.GetLastRect();
if (EditorGUILayout.DropdownButton(new GUIContent(Regex.Replace(_showOnStartupOption.ToString(), "([a-z])([A-Z])", "$1 $2")), FocusType.Passive, GUILayout.Width(110f))) {
GenericMenu menu = new GenericMenu();
foreach (ShowOnStartupEnum option in Enum.GetValues(typeof(ShowOnStartupEnum))) {
menu.AddItem(new GUIContent(Regex.Replace(option.ToString(), "([a-z])([A-Z])", "$1 $2")), false, () => {
if (_showOnStartupOption != option) {
_showOnStartupOption = option;
HotReloadPrefs.ShowOnStartup = _showOnStartupOption;
}
});
}
menu.DropDown(new Rect(buttonRect.x, buttonRect.y, 100, 0));
}
}
}
} finally {
EditorGUIUtility.labelWidth = prevLabelWidth;
}
}
void RenderBottomBarCore() {
bool troubleshootingShown = EditorCodePatcher.Started && HotReloadWindowStyles.windowScreenWidth >= 400;
bool alertsShown = EditorCodePatcher.Started && HotReloadWindowStyles.windowScreenWidth > Constants.EventFiltersShownHideWidth;
using (new EditorGUILayout.VerticalScope()) {
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.FooterStyle)) {
if (!troubleshootingShown) {
GUILayout.FlexibleSpace();
if (alertsShown) {
GUILayout.Space(-20);
}
} else {
GUILayout.Space(21);
}
GUILayout.Space(0);
var lastRect = GUILayoutUtility.GetLastRect();
// show events button when scrolls are hidden
if (!HotReloadRunTab.CanRenderBars(RunTabState) && !RunTabState.starting) {
using (new EditorGUILayout.VerticalScope()) {
GUILayout.FlexibleSpace();
var icon = HotReloadState.ShowingRedDot ? InvertibleIcon.EventsNew : InvertibleIcon.Events;
if (GUILayout.Button(new GUIContent("", GUIHelper.GetInvertibleIcon(icon)))) {
PopupWindow.Show(new Rect(lastRect.x, lastRect.y, 0, 0), HotReloadEventPopup.I);
}
GUILayout.FlexibleSpace();
}
GUILayout.Space(3f);
}
if (alertsShown) {
using (new EditorGUILayout.VerticalScope()) {
GUILayout.FlexibleSpace();
HotReloadTimelineHelper.RenderAlertFilters();
GUILayout.FlexibleSpace();
}
}
GUILayout.FlexibleSpace();
if (troubleshootingShown) {
using (new EditorGUILayout.VerticalScope()) {
GUILayout.FlexibleSpace();
OpenURLButton.Render(Translations.Miscellaneous.ButtonTroubleshooting, Constants.TroubleshootingURL);
GUILayout.FlexibleSpace();
}
GUILayout.Space(21);
}
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f62a84c0b148b0a4582bdd9f1a69e6d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/HotReloadWindow.cs
uploadId: 870414
@@ -0,0 +1,7 @@
namespace SingularityGroup.HotReload.Editor {
enum ShowOnStartupEnum {
Always,
OnNewVersion,
Never,
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 809f47245f717ad41996974be2443feb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/ShowOnStartupEnum.cs
uploadId: 870414
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 83e25ceea0bb7cd4ebf04b724bb0584c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,777 @@
using UnityEditor;
using UnityEngine;
using System.Reflection;
namespace SingularityGroup.HotReload.Editor {
internal static class HotReloadWindowStyles {
private static GUIStyle h1TitleStyle;
private static GUIStyle h1TitleCenteredStyle;
private static GUIStyle h2TitleStyle;
private static GUIStyle h3TitleStyle;
private static GUIStyle h3TitleWrapStyle;
private static GUIStyle h4TitleStyle;
private static GUIStyle h5TitleStyle;
private static GUIStyle boxStyle;
private static GUIStyle wrapStyle;
private static GUIStyle noPaddingMiddleLeftStyle;
private static GUIStyle middleLeftStyle;
private static GUIStyle middleCenterStyle;
private static GUIStyle mediumMiddleCenterStyle;
private static GUIStyle textFieldWrapStyle;
private static GUIStyle foldoutStyle;
private static GUIStyle h3CenterTitleStyle;
private static GUIStyle logoStyle;
private static GUIStyle changelogPointersStyle;
private static GUIStyle recompileButtonStyle;
private static GUIStyle indicationIconStyle;
private static GUIStyle indicationAlertIconStyle;
private static GUIStyle startButtonStyle;
private static GUIStyle stopButtonStyle;
private static GUIStyle eventFilters;
private static GUIStyle sectionOuterBoxCompactStyle;
private static GUIStyle sectionInnerBoxStyle;
private static GUIStyle sectionInnerBoxWideStyle;
private static GUIStyle changelogSectionInnerBoxStyle;
private static GUIStyle indicationBoxStyle;
private static GUIStyle linkStyle;
private static GUIStyle labelStyle;
private static GUIStyle progressBarBarStyle;
private static GUIStyle section;
private static GUIStyle scroll;
private static GUIStyle barStyle;
private static GUIStyle barBgStyle;
private static GUIStyle barChildStyle;
private static GUIStyle barFoldoutStyle;
private static GUIStyle timestampStyle;
private static GUIStyle clickableLabelBoldStyle;
private static GUIStyle _footerStyle;
private static GUIStyle _emptyListText;
private static GUIStyle _stacktraceTextAreaStyle;
private static GUIStyle _customFoldoutStyle;
private static GUIStyle _entryBoxStyle;
private static GUIStyle _childEntryBoxStyle;
private static GUIStyle _removeIconStyle;
private static GUIStyle upgradeLicenseButtonStyle;
private static GUIStyle upgradeLicenseButtonOverlayStyle;
private static GUIStyle upgradeButtonStyle;
private static GUIStyle hideButtonStyle;
private static GUIStyle dynamicSection;
private static GUIStyle dynamicSectionHelpTab;
private static GUIStyle helpTabButton;
private static GUIStyle indicationHelpBox;
private static GUIStyle notificationsTitleStyle;
private static Color32? darkModeLinkColor;
private static Color32? lightModeModeLinkColor;
public static bool IsDarkMode => EditorGUIUtility.isProSkin;
public static int windowScreenWidth => HotReloadWindow.Current ? (int)HotReloadWindow.Current.position.width : Screen.width;
public static int windowScreenHeight => HotReloadWindow.Current ? (int)HotReloadWindow.Current.position.height : Screen.height;
public static GUIStyle H1TitleStyle {
get {
if (h1TitleStyle == null) {
h1TitleStyle = new GUIStyle(EditorStyles.label);
h1TitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
h1TitleStyle.fontStyle = FontStyle.Bold;
h1TitleStyle.fontSize = 16;
h1TitleStyle.padding.top = 5;
h1TitleStyle.padding.bottom = 5;
}
return h1TitleStyle;
}
}
public static GUIStyle FooterStyle {
get {
if (_footerStyle == null) {
_footerStyle = new GUIStyle();
_footerStyle.fixedHeight = 28;
}
return _footerStyle;
}
}
public static GUIStyle H1TitleCenteredStyle {
get {
if (h1TitleCenteredStyle == null) {
h1TitleCenteredStyle = new GUIStyle(H1TitleStyle);
h1TitleCenteredStyle.alignment = TextAnchor.MiddleCenter;
}
return h1TitleCenteredStyle;
}
}
public static GUIStyle H2TitleStyle {
get {
if (h2TitleStyle == null) {
h2TitleStyle = new GUIStyle(EditorStyles.label);
h2TitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
h2TitleStyle.fontStyle = FontStyle.Bold;
h2TitleStyle.fontSize = 14;
h2TitleStyle.padding.top = 5;
h2TitleStyle.padding.bottom = 5;
}
return h2TitleStyle;
}
}
public static GUIStyle H3TitleStyle {
get {
if (h3TitleStyle == null) {
h3TitleStyle = new GUIStyle(EditorStyles.label);
h3TitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
h3TitleStyle.fontStyle = FontStyle.Bold;
h3TitleStyle.fontSize = 12;
h3TitleStyle.padding.top = 5;
h3TitleStyle.padding.bottom = 5;
}
return h3TitleStyle;
}
}
public static GUIStyle NotificationsTitleStyle {
get {
if (notificationsTitleStyle == null) {
notificationsTitleStyle = new GUIStyle(HotReloadWindowStyles.H3TitleStyle);
notificationsTitleStyle.padding.bottom = 0;
notificationsTitleStyle.padding.top = 0;
}
return notificationsTitleStyle;
}
}
public static GUIStyle H3TitleWrapStyle {
get {
if (h3TitleWrapStyle == null) {
h3TitleWrapStyle = new GUIStyle(H3TitleStyle);
h3TitleWrapStyle.wordWrap = true;
}
return h3TitleWrapStyle;
}
}
public static GUIStyle H3CenteredTitleStyle {
get {
if (h3CenterTitleStyle == null) {
h3CenterTitleStyle = new GUIStyle(EditorStyles.label);
h3CenterTitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
h3CenterTitleStyle.fontStyle = FontStyle.Bold;
h3CenterTitleStyle.alignment = TextAnchor.MiddleCenter;
h3CenterTitleStyle.fontSize = 12;
}
return h3CenterTitleStyle;
}
}
public static GUIStyle H4TitleStyle {
get {
if (h4TitleStyle == null) {
h4TitleStyle = new GUIStyle(EditorStyles.label);
h4TitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
h4TitleStyle.fontStyle = FontStyle.Bold;
h4TitleStyle.fontSize = 11;
}
return h4TitleStyle;
}
}
public static GUIStyle H5TitleStyle {
get {
if (h5TitleStyle == null) {
h5TitleStyle = new GUIStyle(EditorStyles.label);
h5TitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
h5TitleStyle.fontStyle = FontStyle.Bold;
h5TitleStyle.fontSize = 10;
}
return h5TitleStyle;
}
}
public static GUIStyle LabelStyle {
get {
if (labelStyle == null) {
labelStyle = new GUIStyle(EditorStyles.label);
labelStyle.fontSize = 12;
labelStyle.clipping = TextClipping.Clip;
labelStyle.wordWrap = true;
}
return labelStyle;
}
}
public static GUIStyle BoxStyle {
get {
if (boxStyle == null) {
boxStyle = new GUIStyle(EditorStyles.helpBox);
boxStyle.normal.textColor = GUI.skin.label.normal.textColor;
boxStyle.fontStyle = FontStyle.Bold;
boxStyle.alignment = TextAnchor.UpperLeft;
}
if (!IsDarkMode) {
boxStyle.normal.background = Texture2D.blackTexture;
}
return boxStyle;
}
}
public static GUIStyle WrapStyle {
get {
if (wrapStyle == null) {
wrapStyle = new GUIStyle(EditorStyles.label);
wrapStyle.fontStyle = FontStyle.Normal;
wrapStyle.wordWrap = true;
}
return wrapStyle;
}
}
public static GUIStyle NoPaddingMiddleLeftStyle {
get {
if (noPaddingMiddleLeftStyle == null) {
noPaddingMiddleLeftStyle = new GUIStyle(EditorStyles.label);
noPaddingMiddleLeftStyle.normal.textColor = GUI.skin.label.normal.textColor;
noPaddingMiddleLeftStyle.padding = new RectOffset();
noPaddingMiddleLeftStyle.margin = new RectOffset();
noPaddingMiddleLeftStyle.alignment = TextAnchor.MiddleLeft;
}
return noPaddingMiddleLeftStyle;
}
}
public static GUIStyle MiddleLeftStyle {
get {
if (middleLeftStyle == null) {
middleLeftStyle = new GUIStyle(EditorStyles.label);
middleLeftStyle.fontStyle = FontStyle.Normal;
middleLeftStyle.alignment = TextAnchor.MiddleLeft;
}
return middleLeftStyle;
}
}
public static GUIStyle MiddleCenterStyle {
get {
if (middleCenterStyle == null) {
middleCenterStyle = new GUIStyle(EditorStyles.label);
middleCenterStyle.fontStyle = FontStyle.Normal;
middleCenterStyle.alignment = TextAnchor.MiddleCenter;
}
return middleCenterStyle;
}
}
public static GUIStyle MediumMiddleCenterStyle {
get {
if (mediumMiddleCenterStyle == null) {
mediumMiddleCenterStyle = new GUIStyle(EditorStyles.label);
mediumMiddleCenterStyle.fontStyle = FontStyle.Normal;
mediumMiddleCenterStyle.fontSize = 12;
mediumMiddleCenterStyle.alignment = TextAnchor.MiddleCenter;
}
return mediumMiddleCenterStyle;
}
}
public static GUIStyle TextFieldWrapStyle {
get {
if (textFieldWrapStyle == null) {
textFieldWrapStyle = new GUIStyle(EditorStyles.textField);
textFieldWrapStyle.wordWrap = true;
}
return textFieldWrapStyle;
}
}
public static GUIStyle FoldoutStyle {
get {
if (foldoutStyle == null) {
foldoutStyle = new GUIStyle(EditorStyles.foldout);
foldoutStyle.normal.textColor = GUI.skin.label.normal.textColor;
foldoutStyle.alignment = TextAnchor.MiddleLeft;
foldoutStyle.fontStyle = FontStyle.Bold;
foldoutStyle.fontSize = 12;
}
return foldoutStyle;
}
}
public static GUIStyle LogoStyle {
get {
if (logoStyle == null) {
logoStyle = new GUIStyle();
logoStyle.margin = new RectOffset(6, 6, 0, 0);
logoStyle.padding = new RectOffset(16, 16, 0, 0);
}
return logoStyle;
}
}
public static GUIStyle ChangelogPointerStyle {
get {
if (changelogPointersStyle == null) {
changelogPointersStyle = new GUIStyle(EditorStyles.label);
changelogPointersStyle.wordWrap = true;
changelogPointersStyle.fontSize = 12;
changelogPointersStyle.padding.left = 20;
}
return changelogPointersStyle;
}
}
public static GUIStyle IndicationIcon {
get {
if (indicationIconStyle == null) {
indicationIconStyle = new GUIStyle(H2TitleStyle);
indicationIconStyle.fixedHeight = 20;
}
indicationIconStyle.padding = new RectOffset(left: windowScreenWidth > Constants.IndicationTextHideWidth ? 7 : 5, right: windowScreenWidth > Constants.IndicationTextHideWidth ? 0 : -10, top: 1, bottom: 1);
return indicationIconStyle;
}
}
public static GUIStyle IndicationAlertIcon {
get {
if (indicationAlertIconStyle == null) {
indicationAlertIconStyle = new GUIStyle(H2TitleStyle);
indicationAlertIconStyle.padding = new RectOffset(left: 5, right: -7, top: 1, bottom: 1);
indicationAlertIconStyle.fixedHeight = 20;
}
return indicationAlertIconStyle;
}
}
public static GUIStyle RecompileButton {
get {
if (recompileButtonStyle == null) {
recompileButtonStyle = new GUIStyle(EditorStyles.miniButton);
recompileButtonStyle.margin.top = 17;
recompileButtonStyle.fixedHeight = 25;
recompileButtonStyle.margin.right = 5;
}
recompileButtonStyle.fixedWidth = windowScreenWidth > Constants.RecompileButtonTextHideWidth ? 95 : 30;
return recompileButtonStyle;
}
}
public static GUIStyle StartButton {
get {
if (startButtonStyle == null) {
startButtonStyle = new GUIStyle(EditorStyles.miniButton);
startButtonStyle.fixedHeight = 25;
startButtonStyle.padding.top = 6;
startButtonStyle.padding.bottom = 6;
startButtonStyle.margin.top = 17;
}
startButtonStyle.fixedWidth = windowScreenWidth > Constants.StartButtonTextHideWidth ? 70 : 30;
return startButtonStyle;
}
}
public static GUIStyle StopButton {
get {
if (stopButtonStyle == null) {
stopButtonStyle = new GUIStyle(EditorStyles.miniButton);
stopButtonStyle.fixedHeight = 25;
stopButtonStyle.margin.top = 17;
}
stopButtonStyle.fixedWidth = HotReloadWindowStyles.windowScreenWidth > Constants.StartButtonTextHideWidth ? 70 : 30;
return stopButtonStyle;
}
}
internal static GUIStyle EventFiltersStyle {
get {
if (eventFilters == null) {
eventFilters = new GUIStyle(EditorStyles.toolbarButton);
eventFilters.fontSize = 13;
// gets overwritten to content size
eventFilters.fixedHeight = 26;
eventFilters.fixedWidth = 50;
eventFilters.margin = new RectOffset(0, 0, 0, 0);
eventFilters.padding = new RectOffset(0, 0, 6, 6);
}
return eventFilters;
}
}
private static Texture2D _clearBackground;
private static Texture2D clearBackground {
get {
if (_clearBackground == null) {
_clearBackground = new Texture2D(1, 1);
_clearBackground.SetPixel(0, 0, Color.clear);
_clearBackground.Apply();
}
return _clearBackground;
}
}
public static GUIStyle SectionOuterBoxCompact {
get {
if (sectionOuterBoxCompactStyle == null) {
sectionOuterBoxCompactStyle = new GUIStyle();
sectionOuterBoxCompactStyle.padding.top = 10;
sectionOuterBoxCompactStyle.padding.bottom = 10;
}
// Looks better without a background
sectionOuterBoxCompactStyle.normal.background = clearBackground;
return sectionOuterBoxCompactStyle;
}
}
public static GUIStyle SectionInnerBox {
get {
if (sectionInnerBoxStyle == null) {
sectionInnerBoxStyle = new GUIStyle();
}
sectionInnerBoxStyle.padding = new RectOffset(left: 0, right: 0, top: 15, bottom: 0);
return sectionInnerBoxStyle;
}
}
public static GUIStyle SectionInnerBoxWide {
get {
if (sectionInnerBoxWideStyle == null) {
sectionInnerBoxWideStyle = new GUIStyle(EditorStyles.helpBox);
sectionInnerBoxWideStyle.padding.top = 15;
sectionInnerBoxWideStyle.padding.bottom = 15;
sectionInnerBoxWideStyle.padding.left = 10;
sectionInnerBoxWideStyle.padding.right = 10;
}
return sectionInnerBoxWideStyle;
}
}
public static GUIStyle DynamiSection {
get {
if (dynamicSection == null) {
dynamicSection = new GUIStyle();
}
var defaultPadding = 13;
if (windowScreenWidth > 600) {
var dynamicPadding = (windowScreenWidth - 600) / 2;
dynamicSection.padding.left = defaultPadding + dynamicPadding;
dynamicSection.padding.right = defaultPadding + dynamicPadding;
} else if (windowScreenWidth < Constants.IndicationTextHideWidth) {
dynamicSection.padding.left = 0;
dynamicSection.padding.right = 0;
} else {
dynamicSection.padding.left = 13;
dynamicSection.padding.right = 13;
}
return dynamicSection;
}
}
public static GUIStyle DynamicSectionHelpTab {
get {
if (dynamicSectionHelpTab == null) {
dynamicSectionHelpTab = new GUIStyle(DynamiSection);
}
dynamicSectionHelpTab.padding.left = DynamiSection.padding.left - 3;
dynamicSectionHelpTab.padding.right = DynamiSection.padding.right - 3;
return dynamicSectionHelpTab;
}
}
public static GUIStyle ChangelogSectionInnerBox {
get {
if (changelogSectionInnerBoxStyle == null) {
changelogSectionInnerBoxStyle = new GUIStyle(EditorStyles.helpBox);
changelogSectionInnerBoxStyle.margin.bottom = 10;
changelogSectionInnerBoxStyle.margin.top = 10;
}
return changelogSectionInnerBoxStyle;
}
}
public static GUIStyle IndicationBox {
get {
if (indicationBoxStyle == null) {
indicationBoxStyle = new GUIStyle();
}
indicationBoxStyle.margin.bottom = windowScreenWidth < 141 ? 0 : 10;
return indicationBoxStyle;
}
}
public static GUIStyle LinkStyle {
get {
if (linkStyle == null) {
linkStyle = new GUIStyle(EditorStyles.label);
linkStyle.fontStyle = FontStyle.Bold;
}
var color = IsDarkMode ? DarkModeLinkColor : LightModeModeLinkColor;
linkStyle.normal.textColor = color;
return linkStyle;
}
}
private static Color32 DarkModeLinkColor {
get {
if (darkModeLinkColor == null) {
darkModeLinkColor = new Color32(0x3F, 0x9F, 0xFF, 0xFF);
}
return darkModeLinkColor.Value;
}
}
private static Color32 LightModeModeLinkColor {
get {
if (lightModeModeLinkColor == null) {
lightModeModeLinkColor = new Color32(0x0F, 0x52, 0xD7, 0xFF);
}
return lightModeModeLinkColor.Value;
}
}
public static GUIStyle ProgressBarBarStyle {
get {
if (progressBarBarStyle != null) {
return progressBarBarStyle;
}
var styles = (EditorStyles)typeof(EditorStyles)
.GetField("s_Current", BindingFlags.Static | BindingFlags.NonPublic)
?.GetValue(null);
var style = styles?.GetType()
.GetField("m_ProgressBarBar", BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(styles);
progressBarBarStyle = style != null ? (GUIStyle)style : GUIStyle.none;
return progressBarBarStyle;
}
}
internal static GUIStyle Section {
get {
if (section == null) {
section = new GUIStyle(EditorStyles.helpBox);
section.padding = new RectOffset(left: 10, right: 10, top: 10, bottom: 10);
section.margin = new RectOffset(left: 0, right: 0, top: 0, bottom: 0);
}
return section;
}
}
internal static GUIStyle Scroll {
get {
if (scroll == null) {
scroll = new GUIStyle(EditorStyles.helpBox);
}
if (IsDarkMode) {
scroll.normal.background = GUIHelper.ConvertTextureToColor(new Color(0,0,0,0.05f));
} else {
scroll.normal.background = GUIHelper.ConvertTextureToColor(new Color(0,0,0,0.03f));
}
return scroll;
}
}
internal static GUIStyle BarStyle {
get {
if (barStyle == null) {
barStyle = new GUIStyle(GUI.skin.label);
barStyle.fontSize = 12;
barStyle.alignment = TextAnchor.MiddleLeft;
barStyle.fixedHeight = 20;
barStyle.padding = new RectOffset(10, 5, 2, 2);
}
return barStyle;
}
}
internal static GUIStyle BarBackgroundStyle {
get {
if (barBgStyle == null) {
barBgStyle = new GUIStyle();
}
barBgStyle.normal.background = GUIHelper.ConvertTextureToColor(Color.clear);
barBgStyle.hover.background = GUIHelper.ConvertTextureToColor(new Color(0, 0, 0, 0.1f));
barBgStyle.focused.background = GUIHelper.ConvertTextureToColor(Color.clear);
barBgStyle.active.background = null;
return barBgStyle;
}
}
internal static GUIStyle ChildBarStyle {
get {
if (barChildStyle == null) {
barChildStyle = new GUIStyle(BarStyle);
barChildStyle.padding = new RectOffset(43, barChildStyle.padding.right, barChildStyle.padding.top, barChildStyle.padding.bottom);
}
return barChildStyle;
}
}
internal static GUIStyle FoldoutBarStyle {
get {
if (barFoldoutStyle == null) {
barFoldoutStyle = new GUIStyle(BarStyle);
barFoldoutStyle.padding = new RectOffset(23, barFoldoutStyle.padding.right, barFoldoutStyle.padding.top, barFoldoutStyle.padding.bottom);
}
return barFoldoutStyle;
}
}
public static GUIStyle TimestampStyle {
get {
if (timestampStyle == null) {
timestampStyle = new GUIStyle(GUI.skin.label);
}
if (IsDarkMode) {
timestampStyle.normal.textColor = new Color(0.5f, 0.5f, 0.5f);
} else {
timestampStyle.normal.textColor = new Color(0.5f, 0.5f, 0.5f);
}
timestampStyle.hover = timestampStyle.normal;
return timestampStyle;
}
}
internal static GUIStyle ClickableLabelBoldStyle {
get {
if (clickableLabelBoldStyle == null) {
clickableLabelBoldStyle = new GUIStyle(LabelStyle);
clickableLabelBoldStyle.fontStyle = FontStyle.Bold;
clickableLabelBoldStyle.fontSize = 14;
clickableLabelBoldStyle.margin.left = 17;
clickableLabelBoldStyle.active.textColor = clickableLabelBoldStyle.normal.textColor;
}
return clickableLabelBoldStyle;
}
}
internal static GUIStyle EmptyListText {
get {
if (_emptyListText == null) {
_emptyListText = new GUIStyle();
_emptyListText.fontSize = 11;
_emptyListText.padding.left = 15;
_emptyListText.padding.top = 10;
_emptyListText.alignment = TextAnchor.MiddleCenter;
_emptyListText.normal.textColor = Color.gray;
}
return _emptyListText;
}
}
internal static GUIStyle StacktraceTextAreaStyle {
get {
if (_stacktraceTextAreaStyle == null) {
_stacktraceTextAreaStyle = new GUIStyle(EditorStyles.textArea);
_stacktraceTextAreaStyle.border = new RectOffset(0, 0, 0, 0);
}
return _stacktraceTextAreaStyle;
}
}
internal static GUIStyle EntryBoxStyle {
get {
if (_entryBoxStyle == null) {
_entryBoxStyle = new GUIStyle();
_entryBoxStyle.margin.left = 30;
}
return _entryBoxStyle;
}
}
internal static GUIStyle ChildEntryBoxStyle {
get {
if (_childEntryBoxStyle == null) {
_childEntryBoxStyle = new GUIStyle();
_childEntryBoxStyle.margin.left = 45;
}
return _childEntryBoxStyle;
}
}
internal static GUIStyle CustomFoldoutStyle {
get {
if (_customFoldoutStyle == null) {
_customFoldoutStyle = new GUIStyle(EditorStyles.foldout);
_customFoldoutStyle.margin.top = 4;
_customFoldoutStyle.margin.left = 0;
_customFoldoutStyle.padding.left = 0;
_customFoldoutStyle.fixedWidth = 100;
}
return _customFoldoutStyle;
}
}
internal static GUIStyle RemoveIconStyle {
get {
if (_removeIconStyle == null) {
_removeIconStyle = new GUIStyle();
_removeIconStyle.margin.top = 5;
_removeIconStyle.fixedWidth = 17;
_removeIconStyle.fixedHeight = 17;
}
return _removeIconStyle;
}
}
internal static GUIStyle UpgradeLicenseButtonStyle {
get {
if (upgradeLicenseButtonStyle == null) {
upgradeLicenseButtonStyle = new GUIStyle(GUI.skin.button);
upgradeLicenseButtonStyle.padding = new RectOffset(5, 5, 0, 0);
}
return upgradeLicenseButtonStyle;
}
}
internal static GUIStyle UpgradeLicenseButtonOverlayStyle {
get {
if (upgradeLicenseButtonOverlayStyle == null) {
upgradeLicenseButtonOverlayStyle = new GUIStyle(UpgradeLicenseButtonStyle);
}
return upgradeLicenseButtonOverlayStyle;
}
}
internal static GUIStyle UpgradeButtonStyle {
get {
if (upgradeButtonStyle == null) {
upgradeButtonStyle = new GUIStyle(EditorStyles.miniButton);
upgradeButtonStyle.fontStyle = FontStyle.Bold;
upgradeButtonStyle.fontSize = 14;
upgradeButtonStyle.fixedHeight = 24;
}
return upgradeButtonStyle;
}
}
internal static GUIStyle HideButtonStyle {
get {
if (hideButtonStyle == null) {
hideButtonStyle = new GUIStyle(GUI.skin.button);
}
return hideButtonStyle;
}
}
internal static GUIStyle HelpTabButton {
get {
if (helpTabButton == null) {
helpTabButton = new GUIStyle(GUI.skin.button);
helpTabButton.alignment = TextAnchor.MiddleLeft;
helpTabButton.padding.left = 10;
}
return helpTabButton;
}
}
internal static GUIStyle IndicationHelpBox {
get {
if (indicationHelpBox == null) {
indicationHelpBox = new GUIStyle(EditorStyles.helpBox);
indicationHelpBox.margin.right = 0;
indicationHelpBox.margin.left = 0;
}
return indicationHelpBox;
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c06a986e9e8c3874f9578f0002ff3a2d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Editor/Window/Styles/HotReloadWindowStyles.cs
uploadId: 870414