[Add] Hot Reload
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86f1446dfdbc2a94aac993437231aaa4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+42
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -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:
|
||||
+49
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -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:
|
||||
+57
@@ -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() { }
|
||||
}
|
||||
}
|
||||
+18
@@ -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
|
||||
+34
@@ -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; }
|
||||
}
|
||||
}
|
||||
+10
@@ -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
|
||||
+70
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -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
|
||||
+25
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -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:
|
||||
+33
@@ -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();
|
||||
}
|
||||
}
|
||||
+18
@@ -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
|
||||
+308
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -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
|
||||
+50
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
+18
@@ -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
|
||||
+945
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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
|
||||
Reference in New Issue
Block a user