first commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dddc1cae3f951f84da98305ec6228f25
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86f1446dfdbc2a94aac993437231aaa4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
internal class OpenDialogueButton : IGUIComponent {
|
||||
public readonly string text;
|
||||
public readonly string url;
|
||||
public readonly string title;
|
||||
public readonly string message;
|
||||
public readonly string ok;
|
||||
public readonly string cancel;
|
||||
|
||||
public OpenDialogueButton(string text, string url, string title, string message, string ok, string cancel) {
|
||||
this.text = text;
|
||||
this.url = url;
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.ok = ok;
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
public void OnGUI() {
|
||||
Render(text, url, title, message, ok, cancel);
|
||||
}
|
||||
|
||||
public static void Render(string text, string url, string title, string message, string ok, string cancel) {
|
||||
if (GUILayout.Button(new GUIContent(text.StartsWith(" ") ? text : " " + text))) {
|
||||
if (EditorUtility.DisplayDialog(title, message, ok, cancel)) {
|
||||
Application.OpenURL(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RenderRaw(Rect rect, string text, string url, string title, string message, string ok, string cancel, GUIStyle style = null) {
|
||||
if (GUI.Button(rect, new GUIContent(text.StartsWith(" ") ? text : " " + text), style ?? GUI.skin.button)) {
|
||||
if (EditorUtility.DisplayDialog(title, message, ok, cancel)) {
|
||||
Application.OpenURL(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97ca8174f0514e8e9ee5d4be26ed8078
|
||||
timeCreated: 1674416481
|
||||
@@ -0,0 +1,29 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
internal class OpenURLButton : IGUIComponent {
|
||||
public readonly string text;
|
||||
public readonly string url;
|
||||
public OpenURLButton(string text, string url) {
|
||||
this.text = text;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public void OnGUI() {
|
||||
Render(text, url);
|
||||
}
|
||||
|
||||
public static void Render(string text, string url) {
|
||||
if (GUILayout.Button(new GUIContent(text.StartsWith(" ") ? text : " " + text))) {
|
||||
Application.OpenURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RenderRaw(Rect rect, string text, string url, GUIStyle style = null) {
|
||||
if (GUI.Button(rect, new GUIContent(text.StartsWith(" ") ? text : " " + text), style ?? GUI.skin.button)) {
|
||||
Application.OpenURL(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef12252fc9d1f9f438cbd34cf8f7364b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9116854180be4f2b8fcc0422bcf570a5
|
||||
timeCreated: 1674127121
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
internal interface IGUIComponent {
|
||||
void OnGUI();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 893cb208871dab94488cb988920f0ebd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b3fa5ea1ed3545429de96b41801942f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
internal class AllowAndroidAppToMakeHttpRequestsOption : ProjectOptionBase {
|
||||
public override string ShortSummary {
|
||||
get {
|
||||
return "Allow app to make HTTP requests";
|
||||
}
|
||||
}
|
||||
|
||||
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 = "For Hot Reload to work on-device, please allow HTTP requests";
|
||||
EditorGUILayout.LabelField(description, HotReloadWindowStyles.WrapStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a7442cee510ab4498ca2a846e0c4e92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb8474c37f13d704d96b43e0f681680d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,57 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
/// <summary>
|
||||
/// An option stored inside the current Unity project.
|
||||
/// </summary>
|
||||
internal abstract class ProjectOptionBase : IOption, ISerializedProjectOption {
|
||||
public abstract string ShortSummary { get; }
|
||||
public abstract string Summary { get; }
|
||||
|
||||
public virtual bool GetValue(SerializedObject so) {
|
||||
return so.FindProperty(ObjectPropertyName).boolValue;
|
||||
}
|
||||
|
||||
protected SerializedProperty GetProperty(SerializedObject so) {
|
||||
return so.FindProperty(ObjectPropertyName);
|
||||
}
|
||||
|
||||
public virtual void SetValue(SerializedObject so, bool value) {
|
||||
so.FindProperty(ObjectPropertyName).boolValue = value;
|
||||
}
|
||||
|
||||
public virtual void InnerOnGUI(SerializedObject so) { }
|
||||
|
||||
public abstract string ObjectPropertyName { get; }
|
||||
|
||||
/// <remarks>
|
||||
/// Override this if your option is not needed for on-device Hot Reload to work.<br/>
|
||||
/// (by default, a project option must be true for Hot Reload to work)
|
||||
/// </remarks>
|
||||
public virtual bool IsRequiredForBuild() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An option that is stored on the user's computer (shared between Unity projects).
|
||||
/// </summary>
|
||||
internal abstract class ComputerOptionBase : IOption {
|
||||
public abstract string ShortSummary { get; }
|
||||
public abstract string Summary { get; }
|
||||
|
||||
public abstract bool GetValue();
|
||||
|
||||
/// Uses <see cref="HotReloadPrefs"/> for storing the value on the user's computer.
|
||||
public virtual void SetValue(bool value) { }
|
||||
|
||||
public bool GetValue(SerializedObject so) => GetValue();
|
||||
|
||||
public virtual void SetValue(SerializedObject so, bool value) => SetValue(value);
|
||||
|
||||
void IOption.InnerOnGUI(SerializedObject so) {
|
||||
InnerOnGUI();
|
||||
}
|
||||
public virtual void InnerOnGUI() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dab8ef53c2ee30a40ab6a7e4abd1260c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
public interface IOption {
|
||||
string ShortSummary { get; }
|
||||
string Summary { get; }
|
||||
|
||||
/// <param name="so">The <see cref="HotReloadSettingsObject"/> wrapped by SerializedObject</param>
|
||||
bool GetValue(SerializedObject so);
|
||||
|
||||
/// <summary>
|
||||
/// Handle the new value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note: caller must skip calling this if value same as GetValue!
|
||||
/// </remarks>
|
||||
/// <param name="so">The <see cref="HotReloadSettingsObject"/> wrapped by SerializedObject</param>
|
||||
/// <param name="value"></param>
|
||||
void SetValue(SerializedObject so, bool value);
|
||||
|
||||
/// <param name="so">The <see cref="HotReloadSettingsObject"/> wrapped by SerializedObject</param>
|
||||
void InnerOnGUI(SerializedObject so);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An option scoped to the current Unity project.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These options are intended to be shared with collaborators and used by Unity Player builds.
|
||||
/// </remarks>
|
||||
public interface ISerializedProjectOption {
|
||||
string ObjectPropertyName { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a626aa97160471f85de4646a634bdf1
|
||||
timeCreated: 1674574633
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using SingularityGroup.HotReload.Editor.Cli;
|
||||
using UnityEditor;
|
||||
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
internal sealed class ExposeServerOption : ComputerOptionBase {
|
||||
|
||||
public override string ShortSummary => "Allow Devices to Connect";
|
||||
public override string Summary => "Allow Devices to Connect (WiFi)";
|
||||
|
||||
public override void InnerOnGUI() {
|
||||
string description;
|
||||
if (GetValue()) {
|
||||
description = "The HotReload server is reachable from devices on the same Wifi network";
|
||||
} else {
|
||||
description = "The HotReload server is available to your computer only. Other devices cannot connect to it.";
|
||||
}
|
||||
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("Hot Reload",
|
||||
$"When changing '{Summary}', the Hot Reload server must be restarted for this to take effect." +
|
||||
"\nDo you want to restart it now?",
|
||||
"Restart server", "Don't restart");
|
||||
if (restartServer) {
|
||||
CodePatcher.I.ClearPatchedMethods();
|
||||
EditorCodePatcher.RestartCodePatcher().Forget();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void RunTask(Action action) {
|
||||
var token = HotReloadWindow.Current.cancelToken;
|
||||
Task.Run(() => {
|
||||
if (token.IsCancellationRequested) return;
|
||||
try {
|
||||
action();
|
||||
} catch (Exception ex) {
|
||||
ThreadUtility.LogException(ex, token);
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
|
||||
void RunOnMainThreadSync(Action action) {
|
||||
ThreadUtility.RunOnMainThread(action, HotReloadWindow.Current.cancelToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ab0973d3ae1275469237480381842c0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
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 => "Include Hot Reload in player builds";
|
||||
public override string Summary => ShortSummary;
|
||||
|
||||
public override string ObjectPropertyName =>
|
||||
nameof(HotReloadSettingsObject.IncludeInBuild);
|
||||
|
||||
public override void InnerOnGUI(SerializedObject so) {
|
||||
string description;
|
||||
if (GetValue(so)) {
|
||||
description = "The Hot Reload runtime is included in development builds that use the Mono scripting backend.";
|
||||
} else {
|
||||
description = "The Hot Reload runtime will not be included in any build. Use this option to disable HotReload without removing it from your project.";
|
||||
}
|
||||
description += " This option does not affect Hot Reload usage in Playmode";
|
||||
EditorGUILayout.LabelField(description, HotReloadWindowStyles.WrapStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39ed4f822bcd81340bdf7189b3bc5016
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c0f7811020465d46bcd0305e2f83e8a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58d14712b7ef14540ba4817a5ef873a6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
internal abstract class HotReloadTabBase : IGUIComponent {
|
||||
protected readonly HotReloadWindow _window;
|
||||
|
||||
public string Title { get; }
|
||||
public Texture Icon { get; }
|
||||
public string Tooltip { get; }
|
||||
|
||||
public HotReloadTabBase(HotReloadWindow window, string title, Texture iconImage, string tooltip) {
|
||||
_window = window;
|
||||
|
||||
Title = title;
|
||||
Icon = iconImage;
|
||||
Tooltip = tooltip;
|
||||
}
|
||||
|
||||
public HotReloadTabBase(HotReloadWindow window, string title, string iconName, string tooltip) :
|
||||
this(window, title, EditorGUIUtility.IconContent(iconName).image, tooltip) {
|
||||
}
|
||||
|
||||
protected void Repaint() {
|
||||
_window.Repaint();
|
||||
}
|
||||
|
||||
public virtual void Update() { }
|
||||
|
||||
public abstract void OnGUI();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2c79b82bd9636d499449f91f93fae2a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a089a7225d904b00b2893a34b514ad28
|
||||
timeCreated: 1689791626
|
||||
@@ -0,0 +1,308 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SingularityGroup.HotReload.DTO;
|
||||
using SingularityGroup.HotReload.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 const string registerFlagPath = PackageConst.LibraryCachePath + "/registerFlag.txt";
|
||||
public const 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";
|
||||
const string unknownError = "We apologize, an error happened while redeeming your license. Please reach out to customer support for assistance.";
|
||||
|
||||
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($"Failed determining registration outcome with {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
|
||||
? "Unity Pro users are required to obtain an additional license. You are eligible to redeem one if your company has ten or fewer employees. Please enter your company details below."
|
||||
: "The licensing model for Unity Pro users varies depending on the number of employees in your company. Please enter your company details below.";
|
||||
if (error != null) {
|
||||
EditorGUILayout.HelpBox(error, MessageType.Warning);
|
||||
} else {
|
||||
EditorGUILayout.HelpBox(message, MessageType.Info);
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("Comany size (number of employees)");
|
||||
GUI.SetNextControlName("company_size");
|
||||
_pendingCompanySize = EditorGUILayout.TextField(_pendingCompanySize)?.Trim();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (GUILayout.Button("Proceed")) {
|
||||
int companySize;
|
||||
if (!int.TryParse(_pendingCompanySize, out companySize)) {
|
||||
error = "Please enter a number.";
|
||||
} 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("To enable us to verify your purchase, please enter your invoice number/order ID. Additionally, provide the email address that you intend to use for managing your credentials.", MessageType.Info);
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("Invoice number/Order ID");
|
||||
GUI.SetNextControlName("invoice_number");
|
||||
_pendingInvoiceNumber = EditorGUILayout.TextField(_pendingInvoiceNumber ?? HotReloadPrefs.RedeemLicenseInvoice)?.Trim();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("Email");
|
||||
GUI.SetNextControlName("email_redeem");
|
||||
_pendingRedeemEmail = EditorGUILayout.TextField(_pendingRedeemEmail ?? HotReloadPrefs.RedeemLicenseEmail);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
using (new EditorGUI.DisabledScope(requestingRedeem)) {
|
||||
if (GUILayout.Button("Redeem", HotReloadRunTab.bigButtonHeight)) {
|
||||
RedeemLicense(email: _pendingRedeemEmail, invoiceNumber: _pendingInvoiceNumber).Forget();
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Skip", secondaryButtonLayoutOptions)) {
|
||||
SwitchToStage(RedeemStage.Login);
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
}
|
||||
|
||||
async Task RedeemLicense(string email, string invoiceNumber) {
|
||||
string validationError;
|
||||
if (string.IsNullOrEmpty(invoiceNumber)) {
|
||||
validationError = "Please enter invoice number / order ID.";
|
||||
} 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("Redeeming license failed: unknown status received");
|
||||
error = unknownError;
|
||||
} 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($"Redeeming a license failed with error: {resp.error}");
|
||||
error = GetPrettyError(resp);
|
||||
} else {
|
||||
Log.Warning("Redeeming a license failed: uknown error encountered");
|
||||
error = unknownError;
|
||||
}
|
||||
}
|
||||
|
||||
string GetPrettyError(RedeemResponse response) {
|
||||
var err = response?.error;
|
||||
if (err == null) {
|
||||
return unknownError;
|
||||
}
|
||||
if (err.Contains("Invalid email")) {
|
||||
return "Please enter a valid email address.";
|
||||
} else if (err.Contains("License invoice already redeemed")) {
|
||||
return "The invoice number/order ID you're trying to use has already been applied to redeem a license. Please enter a different invoice number/order ID. If you have already redeemed a license for another email, you may proceed to the next step.";
|
||||
} else if (err.Contains("Different license already redeemed by given email")) {
|
||||
return "The provided email has already been used to redeem a license. If you have previously redeemed a license, you can proceed to the next step and use your existing credentials. If not, please input a different email address.";
|
||||
} else if (err.Contains("Invoice not found")) {
|
||||
return "The invoice was not found. Please ensure that you've entered the correct invoice number/order ID.";
|
||||
} else if (err.Contains("Invoice refunded")) {
|
||||
return "The purchase has been refunded. Please enter a different invoice number/order ID.";
|
||||
} else {
|
||||
return unknownError;
|
||||
}
|
||||
}
|
||||
|
||||
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, $"Redeem request failed. Status code: {(int)resp.StatusCode}, reason: {resp.ReasonPhrase}");
|
||||
}
|
||||
var str = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
try {
|
||||
return JsonConvert.DeserializeObject<RedeemResponse>(str);
|
||||
} catch (Exception ex) {
|
||||
return new RedeemResponse(null, $"Failed deserializing redeem response with exception: {ex.GetType().Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
} catch (WebException ex) {
|
||||
return new RedeemResponse(null, $"Redeeming license failed: WebException encountered {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("Success! You will receive an email containing your license password shortly. Once you receive it, please enter the received password in the designated field below to complete your registration.", MessageType.Info);
|
||||
} else if (status == statusAlreadyClaimed) {
|
||||
EditorGUILayout.HelpBox("Your license has already been redeemed. Please enter your existing password below.", MessageType.Info);
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
HotReloadRunTab.RenderLicenseInnerPanel(state, renderLogout: false);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Go Back", secondaryButtonLayoutOptions)) {
|
||||
SwitchToStage(RedeemStage.Redeem);
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
}
|
||||
|
||||
public void StartRegistration() {
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(registerFlagPath));
|
||||
using (File.Create(registerFlagPath)) {
|
||||
}
|
||||
RedeemStage = RedeemStage.Registration;
|
||||
RegistrationOutcome = RegistrationOutcome.None;
|
||||
}
|
||||
|
||||
public void FinishRegistration(RegistrationOutcome outcome) {
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(registerFlagPath));
|
||||
File.WriteAllText(registerOutcomePath, outcome.ToString());
|
||||
File.Delete(registerFlagPath);
|
||||
RegistrationOutcome = outcome;
|
||||
SwitchToStage(RedeemStage.None);
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void SwitchToStage(RedeemStage stage) {
|
||||
// remove focus so that the input field re-renders
|
||||
GUI.FocusControl(null);
|
||||
RedeemStage = stage;
|
||||
}
|
||||
|
||||
void Cleanup() {
|
||||
redeemClient?.Dispose();
|
||||
redeemClient = null;
|
||||
_pendingCompanySize = null;
|
||||
_pendingInvoiceNumber = null;
|
||||
_pendingRedeemEmail = null;
|
||||
status = null;
|
||||
error = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad73f74d3c494c02aae937e2dfa305a2
|
||||
timeCreated: 1689791373
|
||||
@@ -0,0 +1,310 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
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("See More", Constants.ChangelogURL);
|
||||
internal static readonly OpenDialogueButton manageLicenseButton = new OpenDialogueButton("Manage License", Constants.ManageLicenseURL, "Manage License", "Upgrade/downgrade/edit your subscription and edit payment info.", "Open in browser", "Cancel");
|
||||
internal static readonly OpenDialogueButton manageAccountButton = new OpenDialogueButton("Manage Account", Constants.ManageAccountURL, "Manage Account", "Login with company code 'naughtycult'. Use the email you signed up with. Your initial password was sent to you by email.", "Open in browser", "Cancel");
|
||||
internal static readonly OpenURLButton contactButton = new OpenURLButton("Contact", Constants.ContactURL);
|
||||
internal static readonly OpenURLButton discordButton = new OpenURLButton("Join Discord", Constants.DiscordInviteUrl);
|
||||
internal static readonly OpenDialogueButton reportIssueButton = new OpenDialogueButton("Report issue", Constants.ReportIssueURL, "Report issue", "Report issue in our public issue tracker. Requires gitlab.com account (if you don't have one and are not willing to make it, please contact us by other means such as our website).", "Open in browser", "Cancel");
|
||||
|
||||
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, "Help", "_Help", "Info and support for Hot Reload for Unity.") { }
|
||||
|
||||
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 "Today";
|
||||
|
||||
if (delta < 48 * hour)
|
||||
return "Yesterday";
|
||||
|
||||
if (delta < 30 * day)
|
||||
return ts.Days + " days ago";
|
||||
|
||||
if (delta < 12 * month) {
|
||||
var months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
|
||||
return months <= 1 ? "one month ago" : months + " months ago";
|
||||
}
|
||||
var years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
|
||||
return years <= 1 ? "one year ago" : years + " years ago";
|
||||
}
|
||||
|
||||
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("Features:", HotReloadWindowStyles.H2TitleStyle);
|
||||
tempTextString = "";
|
||||
foreach (var feature in version.features) {
|
||||
tempTextString += "• " + feature + "\n";
|
||||
}
|
||||
EditorGUILayout.TextArea(tempTextString, HotReloadWindowStyles.ChangelogPointerStyle);
|
||||
}
|
||||
|
||||
//improvements
|
||||
if (version.improvements != null) {
|
||||
EditorGUILayout.TextArea("Improvements:", HotReloadWindowStyles.H2TitleStyle);
|
||||
tempTextString = "";
|
||||
foreach (var improvement in version.improvements) {
|
||||
tempTextString += "• " + improvement + "\n";
|
||||
}
|
||||
EditorGUILayout.TextArea(tempTextString, HotReloadWindowStyles.ChangelogPointerStyle);
|
||||
}
|
||||
|
||||
//fixes
|
||||
if (version.fixes != null) {
|
||||
EditorGUILayout.TextArea("Fixes:", 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, "Changelog", 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($" Hot Reload version {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, "Documentation", 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 }, "Unity Forum", 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("Open Log File")) {
|
||||
var mostRecentFile = LogsHelper.FindRecentLog(logsPath);
|
||||
if (mostRecentFile == null) {
|
||||
Log.Info("No logs found");
|
||||
} else {
|
||||
try {
|
||||
Process.Start($"\"{Path.Combine(logsPath, mostRecentFile)}\"");
|
||||
} catch (Win32Exception e) {
|
||||
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("Failed opening log file.");
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Fallback to opening folder with all logs
|
||||
Process.Start($"\"{logsPath}\"");
|
||||
Log.Info("Failed opening log file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("Browse all logs")) {
|
||||
Process.Start($"\"{logsPath}\"");
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cf8e9ef1ab770249a4318e88e882a85
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
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("Unexpected problem unable to save HotReloadSettingsObject");
|
||||
} else {
|
||||
// when one of the project options changed then we ensure the asset file exists.
|
||||
HotReloadSettingsEditor.EnsureSettingsCreated(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawOption(IOption option, SerializedObject so) {
|
||||
EditorGUILayout.BeginVertical(HotReloadWindowStyles.BoxStyle);
|
||||
|
||||
var before = option.GetValue(so);
|
||||
var after = EditorGUILayout.BeginToggleGroup(new GUIContent(" " + option.Summary), before);
|
||||
if (after != before) {
|
||||
option.SetValue(so, after);
|
||||
}
|
||||
|
||||
option.InnerOnGUI(so);
|
||||
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24379a407eff8494eac0f7841b70e574
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38d0877009d34a9458f7d169d7f1b6a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,697 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using SingularityGroup.HotReload.DTO;
|
||||
using SingularityGroup.HotReload.Editor.Cli;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using EditorGUI = UnityEditor.EditorGUI;
|
||||
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
internal struct HotReloadSettingsTabState {
|
||||
public readonly bool running;
|
||||
public readonly bool trialLicense;
|
||||
public readonly LoginStatusResponse loginStatus;
|
||||
public readonly bool isServerHealthy;
|
||||
public readonly bool registrationRequired;
|
||||
|
||||
public HotReloadSettingsTabState(
|
||||
bool running,
|
||||
bool trialLicense,
|
||||
LoginStatusResponse loginStatus,
|
||||
bool isServerHealthy,
|
||||
bool registrationRequired
|
||||
) {
|
||||
this.running = running;
|
||||
this.trialLicense = trialLicense;
|
||||
this.loginStatus = loginStatus;
|
||||
this.isServerHealthy = isServerHealthy;
|
||||
this.registrationRequired = registrationRequired;
|
||||
}
|
||||
}
|
||||
|
||||
internal class HotReloadSettingsTab : HotReloadTabBase {
|
||||
private readonly HotReloadOptionsSection optionsSection;
|
||||
|
||||
// cached because changing built target triggers C# domain reload
|
||||
// Also I suspect selectedBuildTargetGroup has chance to freeze Unity for several seconds (unconfirmed).
|
||||
private readonly Lazy<BuildTargetGroup> currentBuildTarget = new Lazy<BuildTargetGroup>(
|
||||
() => BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget));
|
||||
|
||||
private readonly Lazy<bool> isCurrentBuildTargetSupported = new Lazy<bool>(() => {
|
||||
var target = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
|
||||
return HotReloadBuildHelper.IsMonoSupported(target);
|
||||
});
|
||||
|
||||
// Resources.Load uses cache, so it's safe to call it every frame.
|
||||
// Retrying Load every time fixes an issue where you import the package and constructor runs, but resources aren't loadable yet.
|
||||
private Texture iconCheck => Resources.Load<Texture>("icon_check_circle");
|
||||
private Texture iconWarning => Resources.Load<Texture>("icon_warning_circle");
|
||||
|
||||
[SuppressMessage("ReSharper", "Unity.UnknownResource")] // Rider doesn't check packages
|
||||
public HotReloadSettingsTab(HotReloadWindow window) : base(window,
|
||||
"Settings",
|
||||
"_Popup",
|
||||
"Make changes to a build running on-device.") {
|
||||
optionsSection = new HotReloadOptionsSection();
|
||||
}
|
||||
|
||||
private GUIStyle headlineStyle;
|
||||
private GUIStyle paddedStyle;
|
||||
|
||||
private Vector2 _settingsTabScrollPos;
|
||||
|
||||
HotReloadSettingsTabState currentState;
|
||||
public override void OnGUI() {
|
||||
// HotReloadAboutTabState ensures rendering is consistent between Layout and Repaint calls
|
||||
// Without it errors like this happen:
|
||||
// ArgumentException: Getting control 2's position in a group with only 2 controls when doing repaint
|
||||
// See thread for more context: https://answers.unity.com/questions/17718/argumentexception-getting-control-2s-position-in-a.html
|
||||
if (Event.current.type == EventType.Layout) {
|
||||
currentState = new HotReloadSettingsTabState(
|
||||
running: EditorCodePatcher.Running,
|
||||
trialLicense: EditorCodePatcher.Status != null && (EditorCodePatcher.Status?.isTrial == true),
|
||||
loginStatus: EditorCodePatcher.Status,
|
||||
isServerHealthy: ServerHealthCheck.I.IsServerHealthy,
|
||||
registrationRequired: RedeemLicenseHelper.I.RegistrationRequired
|
||||
);
|
||||
}
|
||||
using (var scope = new EditorGUILayout.ScrollViewScope(_settingsTabScrollPos, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUILayout.MaxHeight(Math.Max(HotReloadWindowStyles.windowScreenHeight, 800)), GUILayout.MaxWidth(Math.Max(HotReloadWindowStyles.windowScreenWidth, 800)))) {
|
||||
_settingsTabScrollPos.x = scope.scrollPosition.x;
|
||||
_settingsTabScrollPos.y = scope.scrollPosition.y;
|
||||
using (new EditorGUILayout.VerticalScope(HotReloadWindowStyles.DynamicSectionHelpTab)) {
|
||||
GUILayout.Space(10);
|
||||
if (!EditorCodePatcher.LoginNotRequired
|
||||
&& !currentState.registrationRequired
|
||||
// Delay showing login in settings to not confuse users that they need to login to use Free trial
|
||||
&& (HotReloadPrefs.RateAppShown
|
||||
|| PackageConst.IsAssetStoreBuild)
|
||||
) {
|
||||
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
|
||||
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
|
||||
using (new EditorGUILayout.VerticalScope()) {
|
||||
RenderLicenseInfoSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionOuterBoxCompact)) {
|
||||
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
|
||||
using (new EditorGUILayout.VerticalScope()) {
|
||||
HotReloadPrefs.ShowConfiguration = EditorGUILayout.Foldout(HotReloadPrefs.ShowConfiguration, "Configuration", true, HotReloadWindowStyles.FoldoutStyle);
|
||||
if (HotReloadPrefs.ShowConfiguration) {
|
||||
EditorGUILayout.Space();
|
||||
RenderUnityAutoRefresh();
|
||||
RenderAssetRefresh();
|
||||
if (HotReloadPrefs.AllAssetChanges) {
|
||||
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
|
||||
RenderIncludeShaderChanges();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
using (new EditorGUI.DisabledScope(!EditorCodePatcher.autoRecompileUnsupportedChangesSupported)) {
|
||||
RenderAutoRecompileUnsupportedChanges();
|
||||
if (HotReloadPrefs.AutoRecompileUnsupportedChanges && EditorCodePatcher.autoRecompileUnsupportedChangesSupported) {
|
||||
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
|
||||
RenderAutoRecompileUnsupportedChangesImmediately();
|
||||
RenderAutoRecompileUnsupportedChangesOnExitPlayMode();
|
||||
RenderAutoRecompileUnsupportedChangesInPlayMode();
|
||||
RenderAutoRecompilePartiallyUnsupportedChanges();
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
RenderConsoleWindow();
|
||||
RenderAutostart();
|
||||
|
||||
if (EditorWindowHelper.supportsNotifications) {
|
||||
RenderShowNotifications();
|
||||
using (new EditorGUILayout.VerticalScope(paddedStyle ?? (paddedStyle = new GUIStyle { padding = new RectOffset(20, 0, 0, 0) }))) {
|
||||
RenderShowPatchingNotifications();
|
||||
RenderShowCompilingUnsupportedNotifications();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderUnityAutoRefresh() {
|
||||
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Allow to manage Unity's Auto Compile settings (recommended)"), HotReloadPrefs.AllowDisableUnityAutoRefresh);
|
||||
if (newSettings != HotReloadPrefs.AllowDisableUnityAutoRefresh) {
|
||||
HotReloadPrefs.AllowDisableUnityAutoRefresh = newSettings;
|
||||
}
|
||||
string toggleDescription;
|
||||
if (HotReloadPrefs.AllowDisableUnityAutoRefresh) {
|
||||
toggleDescription = "Hot Reload will manage Unity's Auto Refresh and Script Compilation settings when it's running. Previous settings will be restored when Hot Reload is stopped.";
|
||||
} else {
|
||||
toggleDescription = "Enable to allow Hot Reload to manage Unity's Auto Refresh and Script Compilation settings when it's running. If enabled, previous settings will be restored when Hot Reload is stopped.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
EditorGUILayout.Space(3f);
|
||||
}
|
||||
|
||||
void RenderAssetRefresh() {
|
||||
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Asset refresh (recommended)"), HotReloadPrefs.AllAssetChanges);
|
||||
if (newSettings != HotReloadPrefs.AllAssetChanges) {
|
||||
HotReloadPrefs.AllAssetChanges = newSettings;
|
||||
// restart when setting changes
|
||||
if (ServerHealthCheck.I.IsServerHealthy) {
|
||||
var restartServer = EditorUtility.DisplayDialog("Hot Reload",
|
||||
$"When changing 'Asset refresh', the Hot Reload server must be restarted for this to take effect." +
|
||||
"\nDo you want to restart it now?",
|
||||
"Restart server", "Don't restart");
|
||||
if (restartServer) {
|
||||
EditorCodePatcher.RestartCodePatcher().Forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
string toggleDescription;
|
||||
if (HotReloadPrefs.AllAssetChanges) {
|
||||
toggleDescription = "Hot Reload will refresh changed assets in the project.";
|
||||
} else {
|
||||
toggleDescription = "Enable to allow Hot Reload to refresh changed assets in the project. All asset types are supported including sprites, prefabs, shaders etc.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
EditorGUILayout.Space(3f);
|
||||
}
|
||||
|
||||
void RenderIncludeShaderChanges() {
|
||||
HotReloadPrefs.IncludeShaderChanges = EditorGUILayout.BeginToggleGroup(new GUIContent("Refresh shaders"), HotReloadPrefs.IncludeShaderChanges);
|
||||
string toggleDescription;
|
||||
if (HotReloadPrefs.IncludeShaderChanges) {
|
||||
toggleDescription = "Hot Reload will auto refresh shaders. Note that enabling this setting might impact performance.";
|
||||
} else {
|
||||
toggleDescription = "Enable to auto-refresh shaders. Note that enabling this setting might impact performance";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
}
|
||||
|
||||
void RenderConsoleWindow() {
|
||||
if (!HotReloadCli.CanOpenInBackground) {
|
||||
return;
|
||||
}
|
||||
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Hide console window on start"), HotReloadPrefs.DisableConsoleWindow);
|
||||
if (newSettings != HotReloadPrefs.DisableConsoleWindow) {
|
||||
HotReloadPrefs.DisableConsoleWindow = newSettings;
|
||||
// restart when setting changes
|
||||
if (ServerHealthCheck.I.IsServerHealthy) {
|
||||
var restartServer = EditorUtility.DisplayDialog("Hot Reload",
|
||||
$"When changing 'Hide console window on start', the Hot Reload server must be restarted for this to take effect." +
|
||||
"\nDo you want to restart it now?",
|
||||
"Restart server", "Don't restart");
|
||||
if (restartServer) {
|
||||
EditorCodePatcher.RestartCodePatcher().Forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
string toggleDescription;
|
||||
if (HotReloadPrefs.DisableConsoleWindow) {
|
||||
toggleDescription = "Hot Reload will start without creating a console window. Logs can be accessed through \"Help\" tab.";
|
||||
} else {
|
||||
toggleDescription = "Enable to start Hot Reload without creating a console window.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
EditorGUILayout.Space(3f);
|
||||
}
|
||||
|
||||
void RenderAutostart() {
|
||||
var newSettings = EditorGUILayout.BeginToggleGroup(new GUIContent("Autostart on Unity open"), HotReloadPrefs.LaunchOnEditorStart);
|
||||
if (newSettings != HotReloadPrefs.LaunchOnEditorStart) {
|
||||
HotReloadPrefs.LaunchOnEditorStart = newSettings;
|
||||
}
|
||||
string toggleDescription;
|
||||
if (HotReloadPrefs.LaunchOnEditorStart) {
|
||||
toggleDescription = "Hot Reload will be launched when Unity project opens.";
|
||||
} else {
|
||||
toggleDescription = "Enable to launch Hot Reload when Unity project opens.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
void RenderShowNotifications() {
|
||||
GUILayout.Label("Indications", HotReloadWindowStyles.NotificationsTitleStyle);
|
||||
|
||||
string toggleDescription;
|
||||
if (!EditorWindowHelper.supportsNotifications && !UnitySettingsHelper.I.playmodeTintSupported) {
|
||||
toggleDescription = "Indications are not supported in the Unity version you use.";
|
||||
} else {
|
||||
toggleDescription = "Chosen indications are enabled:";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
}
|
||||
|
||||
void RenderShowPatchingNotifications() {
|
||||
HotReloadPrefs.ShowPatchingNotifications = EditorGUILayout.BeginToggleGroup(new GUIContent("Patching Indication"), HotReloadPrefs.ShowPatchingNotifications);
|
||||
string toggleDescription;
|
||||
if (!EditorWindowHelper.supportsNotifications) {
|
||||
toggleDescription = "Patching Notification is not supported in the Unity version you use.";
|
||||
} else if (!HotReloadPrefs.ShowPatchingNotifications) {
|
||||
toggleDescription = "Enable to show GameView and SceneView indications when Patching.";
|
||||
} else {
|
||||
toggleDescription = "Indications will be shown in GameView and SceneView when Patching.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
}
|
||||
|
||||
void RenderShowCompilingUnsupportedNotifications() {
|
||||
HotReloadPrefs.ShowCompilingUnsupportedNotifications = EditorGUILayout.BeginToggleGroup(new GUIContent("Compiling Unsupported Changes Indication"), HotReloadPrefs.ShowCompilingUnsupportedNotifications);
|
||||
string toggleDescription;
|
||||
if (!EditorWindowHelper.supportsNotifications) {
|
||||
toggleDescription = "Compiling Unsupported Changes Notification is not supported in the Unity version you use.";
|
||||
} else if (!HotReloadPrefs.ShowCompilingUnsupportedNotifications) {
|
||||
toggleDescription = "Enable to show GameView and SceneView indications when compiling unsupported changes.";
|
||||
} else {
|
||||
toggleDescription = "Indications will be shown in GameView and SceneView when compiling unsupported changes.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
}
|
||||
|
||||
void RenderAutoRecompileUnsupportedChanges() {
|
||||
HotReloadPrefs.AutoRecompileUnsupportedChanges = EditorGUILayout.BeginToggleGroup(new GUIContent("Auto recompile unsupported changes (recommended)"), HotReloadPrefs.AutoRecompileUnsupportedChanges && EditorCodePatcher.autoRecompileUnsupportedChangesSupported);
|
||||
string toggleDescription;
|
||||
if (!EditorCodePatcher.autoRecompileUnsupportedChangesSupported) {
|
||||
toggleDescription = "Auto recompiling unsupported changes is not supported in the Unity version you use.";
|
||||
} else if (HotReloadPrefs.AutoRecompileUnsupportedChanges) {
|
||||
toggleDescription = "Hot Reload will recompile when unsupported changes are detected.";
|
||||
} else {
|
||||
toggleDescription = "Enable to recompile when unsupported changes are detected.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
}
|
||||
|
||||
void RenderAutoRecompilePartiallyUnsupportedChanges() {
|
||||
HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges = EditorGUILayout.BeginToggleGroup(new GUIContent("Include partially unsupported changes"), HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges);
|
||||
string toggleDescription;
|
||||
if (HotReloadPrefs.AutoRecompilePartiallyUnsupportedChanges) {
|
||||
toggleDescription = "Hot Reload will recompile partially unsupported changes.";
|
||||
} else {
|
||||
toggleDescription = "Enable to recompile partially unsupported changes.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
}
|
||||
|
||||
void RenderAutoRecompileUnsupportedChangesImmediately() {
|
||||
HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately = EditorGUILayout.BeginToggleGroup(new GUIContent("Recompile immediately"), HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately);
|
||||
string toggleDescription;
|
||||
if (HotReloadPrefs.AutoRecompileUnsupportedChangesImmediately) {
|
||||
toggleDescription = "Unsupported changes will be recompiled immediately.";
|
||||
} else {
|
||||
toggleDescription = "Unsupported changes will be recompiled when editor is focused. Enable to recompile immediately.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
}
|
||||
|
||||
void RenderAutoRecompileUnsupportedChangesInPlayMode() {
|
||||
HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode = EditorGUILayout.BeginToggleGroup(new GUIContent("Recompile in Play Mode"), HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode);
|
||||
string toggleDescription;
|
||||
if (HotReloadPrefs.AutoRecompileUnsupportedChangesInPlayMode) {
|
||||
toggleDescription = "Hot Reload will exit Play Mode to recompile unsupported changes.";
|
||||
} else {
|
||||
toggleDescription = "Enable to auto exit Play Mode to recompile unsupported changes.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
}
|
||||
|
||||
void RenderAutoRecompileUnsupportedChangesOnExitPlayMode() {
|
||||
HotReloadPrefs.AutoRecompileUnsupportedChangesOnExitPlayMode = EditorGUILayout.BeginToggleGroup(new GUIContent("Recompile on exit Play Mode"), HotReloadPrefs.AutoRecompileUnsupportedChangesOnExitPlayMode);
|
||||
string toggleDescription;
|
||||
if (HotReloadPrefs.AutoRecompileUnsupportedChangesOnExitPlayMode) {
|
||||
toggleDescription = "Hot Reload will recompile unsupported changes when exiting Play Mode.";
|
||||
} else {
|
||||
toggleDescription = "Enable to recompile unsupported changes when exiting Play Mode.";
|
||||
}
|
||||
EditorGUILayout.LabelField(toggleDescription, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUILayout.EndToggleGroup();
|
||||
}
|
||||
|
||||
void RenderOnDevice() {
|
||||
HotReloadPrefs.ShowOnDevice = EditorGUILayout.Foldout(HotReloadPrefs.ShowOnDevice, "On-Device", true, HotReloadWindowStyles.FoldoutStyle);
|
||||
if (!HotReloadPrefs.ShowOnDevice) {
|
||||
return;
|
||||
}
|
||||
// header with explainer image
|
||||
{
|
||||
if (headlineStyle == null) {
|
||||
// start with textArea for the background and border colors
|
||||
headlineStyle = new GUIStyle(GUI.skin.label) {
|
||||
fontStyle = FontStyle.Bold,
|
||||
alignment = TextAnchor.MiddleLeft
|
||||
};
|
||||
headlineStyle.normal.textColor = HotReloadWindowStyles.H2TitleStyle.normal.textColor;
|
||||
|
||||
// bg color
|
||||
if (HotReloadWindowStyles.IsDarkMode) {
|
||||
headlineStyle.normal.background = EditorTextures.DarkGray40;
|
||||
} else {
|
||||
headlineStyle.normal.background = EditorTextures.LightGray225;
|
||||
}
|
||||
// layout
|
||||
headlineStyle.padding = new RectOffset(8, 8, 0, 0);
|
||||
headlineStyle.margin = new RectOffset(6, 6, 6, 6);
|
||||
}
|
||||
GUILayout.Space(9f); // space between logo and headline
|
||||
|
||||
GUILayout.Label("Make changes to a build running on-device",
|
||||
headlineStyle, GUILayout.MinHeight(EditorGUIUtility.singleLineHeight * 1.4f));
|
||||
// image showing how Hot Reload works with a phone
|
||||
// var bannerBox = GUILayoutUtility.GetRect(flowchart.width * 0.6f, flowchart.height * 0.6f);
|
||||
// GUI.DrawTexture(bannerBox, flowchart, ScaleMode.ScaleToFit);
|
||||
}
|
||||
|
||||
GUILayout.Space(16f);
|
||||
|
||||
//ButtonToOpenBuildSettings();
|
||||
|
||||
{
|
||||
GUILayout.Label("Manual connect", HotReloadWindowStyles.H3TitleStyle);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
// indent all controls (this works with non-labels)
|
||||
GUILayout.Space(16f);
|
||||
GUILayout.BeginVertical();
|
||||
|
||||
string text;
|
||||
var ip = IpHelper.GetIpAddressCached();
|
||||
if (string.IsNullOrEmpty(ip)) {
|
||||
text = $"If auto-pair fails, find your local IP in OS settings, and use this format to connect: '{{ip}}:{RequestHelper.port}'";
|
||||
} else {
|
||||
text = $"If auto-pair fails, use this IP and port to connect: {ip}:{RequestHelper.port}" +
|
||||
"\nMake sure you are on the same LAN/WiFi network";
|
||||
}
|
||||
GUILayout.Label(text, HotReloadWindowStyles.H3TitleWrapStyle);
|
||||
|
||||
if (!currentState.isServerHealthy) {
|
||||
DrawHorizontalCheck(ServerHealthCheck.I.IsServerHealthy,
|
||||
"Hot Reload is running",
|
||||
"Hot Reload is not running",
|
||||
hasFix: false);
|
||||
}
|
||||
|
||||
if (!HotReloadPrefs.ExposeServerToLocalNetwork) {
|
||||
var summary = $"Enable '{new ExposeServerOption().ShortSummary}'";
|
||||
DrawHorizontalCheck(HotReloadPrefs.ExposeServerToLocalNetwork,
|
||||
summary,
|
||||
summary);
|
||||
}
|
||||
|
||||
// explainer image that shows phone needs same wifi to auto connect ?
|
||||
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.Space(16f);
|
||||
|
||||
// loading again is smooth, pretty sure AssetDatabase.LoadAssetAtPath is caching -Troy
|
||||
var settingsObject = HotReloadSettingsEditor.LoadSettingsOrDefault();
|
||||
var so = new SerializedObject(settingsObject);
|
||||
|
||||
// if you build for Android now, will Hot Reload work?
|
||||
{
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Build Settings Checklist", HotReloadWindowStyles.H3TitleStyle);
|
||||
EditorGUI.BeginDisabledGroup(isSupported);
|
||||
// One-click to change each setting to the supported value
|
||||
if (GUILayout.Button("Fix All", GUILayout.MaxWidth(90f))) {
|
||||
FixAllUnsupportedSettings(so);
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
|
||||
// NOTE: After user changed some build settings, window may not immediately repaint
|
||||
// (e.g. toggle Development Build in Build Settings window)
|
||||
// We could show a refresh button (to encourage the user to click the window which makes it repaint).
|
||||
DrawSectionCheckBuildSupport(so);
|
||||
}
|
||||
|
||||
|
||||
GUILayout.Space(16f);
|
||||
|
||||
// Settings checkboxes (Hot Reload options)
|
||||
{
|
||||
GUILayout.Label("Options", HotReloadWindowStyles.H3TitleStyle);
|
||||
if (settingsObject) {
|
||||
optionsSection.DrawGUI(so);
|
||||
}
|
||||
}
|
||||
GUILayout.FlexibleSpace(); // needed otherwise vertical scrollbar is appearing for no reason (Unity 2021 glitch perhaps)
|
||||
}
|
||||
|
||||
private void RenderLicenseInfoSection() {
|
||||
HotReloadRunTab.RenderLicenseInfo(
|
||||
_window.RunTabState,
|
||||
currentState.loginStatus,
|
||||
verbose: true,
|
||||
allowHide: false,
|
||||
overrideActionButton: "Activate License",
|
||||
showConsumptions: true
|
||||
);
|
||||
}
|
||||
|
||||
private void RenderPromoCodeSection() {
|
||||
_window.RunTab.RenderPromoCodes();
|
||||
}
|
||||
|
||||
public void FocusLicenseFoldout() {
|
||||
HotReloadPrefs.ShowLogin = true;
|
||||
}
|
||||
|
||||
// note: changing scripting backend does not force Unity to recreate the GUI, so need to check it when drawing.
|
||||
private ScriptingImplementation ScriptingBackend => HotReloadBuildHelper.GetCurrentScriptingBackend();
|
||||
private ManagedStrippingLevel StrippingLevel => HotReloadBuildHelper.GetCurrentStrippingLevel();
|
||||
public bool isSupported = true;
|
||||
|
||||
/// <summary>
|
||||
/// These options are drawn in the On-device tab
|
||||
/// </summary>
|
||||
// new on-device options should be added here
|
||||
public static readonly IOption[] allOptions = new IOption[] {
|
||||
new ExposeServerOption(),
|
||||
IncludeInBuildOption.I,
|
||||
new AllowAndroidAppToMakeHttpRequestsOption(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Change each setting to the value supported by Hot Reload
|
||||
/// </summary>
|
||||
private void FixAllUnsupportedSettings(SerializedObject so) {
|
||||
if (!isCurrentBuildTargetSupported.Value) {
|
||||
// try switch to Android platform
|
||||
// (we also support Standalone but HotReload on mobile is a better selling point)
|
||||
if (!TrySwitchToStandalone()) {
|
||||
// skip changing other options (user won't readthe gray text) - user has to click Fix All again
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var buildOption in allOptions) {
|
||||
if (!buildOption.GetValue(so)) {
|
||||
buildOption.SetValue(so, true);
|
||||
}
|
||||
}
|
||||
so.ApplyModifiedProperties();
|
||||
var settingsObject = so.targetObject as HotReloadSettingsObject;
|
||||
if (settingsObject) {
|
||||
// when you click fix all, make sure to save the settings, otherwise ui does not update
|
||||
HotReloadSettingsEditor.EnsureSettingsCreated(settingsObject);
|
||||
}
|
||||
|
||||
if (!EditorUserBuildSettings.development) {
|
||||
EditorUserBuildSettings.development = true;
|
||||
}
|
||||
|
||||
HotReloadBuildHelper.SetCurrentScriptingBackend(ScriptingImplementation.Mono2x);
|
||||
HotReloadBuildHelper.SetCurrentStrippingLevel(ManagedStrippingLevel.Disabled);
|
||||
}
|
||||
|
||||
public static bool TrySwitchToStandalone() {
|
||||
BuildTarget buildTarget;
|
||||
if (Application.platform == RuntimePlatform.LinuxEditor) {
|
||||
buildTarget = BuildTarget.StandaloneLinux64;
|
||||
} else if (Application.platform == RuntimePlatform.WindowsEditor) {
|
||||
buildTarget = BuildTarget.StandaloneWindows64;
|
||||
} else if (Application.platform == RuntimePlatform.OSXEditor) {
|
||||
buildTarget = BuildTarget.StandaloneOSX;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
var current = EditorUserBuildSettings.activeBuildTarget;
|
||||
if (current == buildTarget) {
|
||||
return true;
|
||||
}
|
||||
var confirmed = EditorUtility.DisplayDialog("Switch Build Target",
|
||||
"Switching the build target can take a while depending on project size.",
|
||||
$"Switch to Standalone", "Cancel");
|
||||
if (confirmed) {
|
||||
EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.Standalone, buildTarget);
|
||||
Log.Info($"Build target is switching to {buildTarget}.");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Section that user can check before making a Unity Player build.
|
||||
/// </summary>
|
||||
/// <param name="so"></param>
|
||||
/// <remarks>
|
||||
/// This section is for confirming your build will work with Hot Reload.<br/>
|
||||
/// Options that can be changed after the build is made should be drawn elsewhere.
|
||||
/// </remarks>
|
||||
public void DrawSectionCheckBuildSupport(SerializedObject so) {
|
||||
isSupported = true;
|
||||
var selectedPlatform = currentBuildTarget.Value;
|
||||
DrawHorizontalCheck(isCurrentBuildTargetSupported.Value,
|
||||
$"The {selectedPlatform.ToString()} platform is selected",
|
||||
$"The current platform is {selectedPlatform.ToString()} which is not supported");
|
||||
|
||||
using (new EditorGUI.DisabledScope(!isCurrentBuildTargetSupported.Value)) {
|
||||
foreach (var option in allOptions) {
|
||||
DrawHorizontalCheck(option.GetValue(so),
|
||||
$"Enable \"{option.ShortSummary}\"",
|
||||
$"Enable \"{option.ShortSummary}\"");
|
||||
}
|
||||
|
||||
DrawHorizontalCheck(EditorUserBuildSettings.development,
|
||||
"Development Build is enabled",
|
||||
"Enable \"Development Build\"");
|
||||
|
||||
DrawHorizontalCheck(ScriptingBackend == ScriptingImplementation.Mono2x,
|
||||
$"Scripting Backend is set to Mono",
|
||||
$"Set Scripting Backend to Mono");
|
||||
|
||||
DrawHorizontalCheck(StrippingLevel == ManagedStrippingLevel.Disabled,
|
||||
$"Stripping Level = {StrippingLevel}",
|
||||
$"Stripping Level = {StrippingLevel}",
|
||||
suggestedSolutionText: "Code stripping needs to be disabled to ensure that all methods are available for patching."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a box with a tick or warning icon on the left, with text describing the tick or warning
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition to check. True to show a tick icon, False to show a warning.</param>
|
||||
/// <param name="okText">Shown when condition is true</param>
|
||||
/// <param name="notOkText">Shown when condition is false</param>
|
||||
/// <param name="suggestedSolutionText">Shown when <paramref name="condition"/> is false</param>
|
||||
void DrawHorizontalCheck(bool condition, string okText, string notOkText = null, string suggestedSolutionText = null, bool hasFix = true) {
|
||||
if (okText == null) {
|
||||
throw new ArgumentNullException(nameof(okText));
|
||||
}
|
||||
if (notOkText == null) {
|
||||
notOkText = okText;
|
||||
}
|
||||
|
||||
// include some horizontal space around the icon
|
||||
var boxWidth = GUILayout.Width(EditorGUIUtility.singleLineHeight * 1.31f);
|
||||
var height = GUILayout.Height(EditorGUIUtility.singleLineHeight * 1.01f);
|
||||
GUILayout.BeginHorizontal(HotReloadWindowStyles.BoxStyle, height, GUILayout.ExpandWidth(true));
|
||||
var style = HotReloadWindowStyles.NoPaddingMiddleLeftStyle;
|
||||
var iconRect = GUILayoutUtility.GetRect(
|
||||
Mathf.Round(EditorGUIUtility.singleLineHeight * 1.31f),
|
||||
Mathf.Round(EditorGUIUtility.singleLineHeight * 1.01f),
|
||||
style, boxWidth, height, GUILayout.ExpandWidth(false));
|
||||
// rounded so we can have pixel perfect black circle bg
|
||||
iconRect.Set(Mathf.Round(iconRect.x), Mathf.Round(iconRect.y), Mathf.CeilToInt(iconRect.width),
|
||||
Mathf.CeilToInt(iconRect.height));
|
||||
var text = condition ? okText : notOkText;
|
||||
var icon = condition ? iconCheck : iconWarning;
|
||||
if (GUI.enabled) {
|
||||
DrawBlackCircle(iconRect);
|
||||
// resource can be null when building player (Editor Resources not available)
|
||||
if (icon) {
|
||||
GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit);
|
||||
}
|
||||
} else {
|
||||
// show something (instead of hiding) so that layout stays same size
|
||||
DrawDisabledCircle(iconRect);
|
||||
}
|
||||
GUILayout.Space(4f);
|
||||
GUILayout.Label(text, style, height);
|
||||
|
||||
if (!condition && hasFix) {
|
||||
isSupported = false;
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
if (!condition && !String.IsNullOrEmpty(suggestedSolutionText)) {
|
||||
// suggest to the user how they can resolve the issue
|
||||
EditorGUI.indentLevel++;
|
||||
GUILayout.Label(suggestedSolutionText, HotReloadWindowStyles.WrapStyle);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawDisabledCircle(Rect rect) => DrawCircleIcon(rect,
|
||||
Resources.Load<Texture>("icon_circle_gray"),
|
||||
Color.clear); // smaller circle draws less attention
|
||||
|
||||
void DrawBlackCircle(Rect rect) => DrawCircleIcon(rect,
|
||||
Resources.Load<Texture>("icon_circle_black"),
|
||||
new Color(0.14f, 0.14f, 0.14f)); // black is too dark in unity light theme
|
||||
|
||||
void DrawCircleIcon(Rect rect, Texture circleIcon, Color borderColor) {
|
||||
// Note: drawing texture from resources is pixelated on the edges, so it has some transperancy around the edges.
|
||||
// While building for Android, Resources.Load returns null for our editor Resources.
|
||||
if (circleIcon != null) {
|
||||
GUI.DrawTexture(rect, circleIcon, ScaleMode.ScaleToFit);
|
||||
}
|
||||
|
||||
// Draw smooth circle border
|
||||
const float borderWidth = 2f;
|
||||
GUI.DrawTexture(rect, EditorTextures.White, ScaleMode.ScaleToFit, true,
|
||||
0f,
|
||||
borderColor,
|
||||
new Vector4(borderWidth, borderWidth, borderWidth, borderWidth),
|
||||
Mathf.Min(rect.height, rect.width) / 2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fff71bd159424bf2978e2e99eacba9b4
|
||||
timeCreated: 1674057842
|
||||
@@ -0,0 +1,388 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using SingularityGroup.HotReload.DTO;
|
||||
using SingularityGroup.HotReload.Editor.Cli;
|
||||
using SingularityGroup.HotReload.Editor.Semver;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEngine;
|
||||
|
||||
[assembly: InternalsVisibleTo("SingularityGroup.HotReload.EditorSamples")]
|
||||
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
class HotReloadWindow : EditorWindow {
|
||||
public static HotReloadWindow Current { get; private set; }
|
||||
|
||||
List<HotReloadTabBase> tabs;
|
||||
List<HotReloadTabBase> Tabs => tabs ?? (tabs = new List<HotReloadTabBase> {
|
||||
RunTab,
|
||||
SettingsTab,
|
||||
AboutTab,
|
||||
});
|
||||
int selectedTab;
|
||||
|
||||
internal static Vector2 scrollPos;
|
||||
|
||||
static Timer timer;
|
||||
|
||||
|
||||
HotReloadRunTab runTab;
|
||||
internal HotReloadRunTab RunTab => runTab ?? (runTab = new HotReloadRunTab(this));
|
||||
HotReloadSettingsTab settingsTab;
|
||||
internal HotReloadSettingsTab SettingsTab => settingsTab ?? (settingsTab = new HotReloadSettingsTab(this));
|
||||
HotReloadAboutTab aboutTab;
|
||||
internal HotReloadAboutTab AboutTab => aboutTab ?? (aboutTab = new HotReloadAboutTab(this));
|
||||
|
||||
static ShowOnStartupEnum _showOnStartupOption;
|
||||
|
||||
/// <summary>
|
||||
/// This token is cancelled when the EditorWindow is disabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use it for all tasks.
|
||||
/// When token is cancelled, scripts are about to be recompiled and this will cause tasks to fail for weird reasons.
|
||||
/// </remarks>
|
||||
public CancellationToken cancelToken;
|
||||
CancellationTokenSource cancelTokenSource;
|
||||
|
||||
static readonly PackageUpdateChecker packageUpdateChecker = new PackageUpdateChecker();
|
||||
|
||||
[MenuItem("Window/Hot Reload/Open &#H")]
|
||||
internal static void Open() {
|
||||
// opening the window on CI systems was keeping Unity open indefinitely
|
||||
if (EditorWindowHelper.IsHumanControllingUs()) {
|
||||
if (Current) {
|
||||
Current.Show();
|
||||
Current.Focus();
|
||||
} else {
|
||||
Current = GetWindow<HotReloadWindow>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Window/Hot Reload/Recompile")]
|
||||
internal static void Recompile() {
|
||||
HotReloadRunTab.Recompile();
|
||||
}
|
||||
|
||||
void OnInterval(object o) {
|
||||
HotReloadRunTab.RepaintInstant();
|
||||
}
|
||||
|
||||
void OnEnable() {
|
||||
if (timer == null) {
|
||||
timer = new Timer(OnInterval, null, 20 * 1000, 20 * 1000);
|
||||
}
|
||||
Current = this;
|
||||
if (cancelTokenSource != null) {
|
||||
cancelTokenSource.Cancel();
|
||||
}
|
||||
// Set min size initially so that full UI is visible
|
||||
if (!HotReloadPrefs.OpenedWindowAtLeastOnce) {
|
||||
this.minSize = new Vector2(Constants.RecompileButtonTextHideWidth + 1, Constants.EventsListHideHeight + 70);
|
||||
HotReloadPrefs.OpenedWindowAtLeastOnce = true;
|
||||
}
|
||||
cancelTokenSource = new CancellationTokenSource();
|
||||
cancelToken = cancelTokenSource.Token;
|
||||
|
||||
this.titleContent = new GUIContent(" Hot Reload", GUIHelper.GetInvertibleIcon(InvertibleIcon.Logo));
|
||||
_showOnStartupOption = HotReloadPrefs.ShowOnStartup;
|
||||
|
||||
packageUpdateChecker.StartCheckingForNewVersion();
|
||||
}
|
||||
|
||||
void Update() {
|
||||
foreach (var tab in Tabs) {
|
||||
tab.Update();
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable() {
|
||||
if (cancelTokenSource != null) {
|
||||
cancelTokenSource.Cancel();
|
||||
cancelTokenSource = null;
|
||||
}
|
||||
|
||||
if (Current == this) {
|
||||
Current = null;
|
||||
}
|
||||
timer.Dispose();
|
||||
timer = null;
|
||||
}
|
||||
|
||||
internal void SelectTab(Type tabType) {
|
||||
selectedTab = Tabs.FindIndex(x => x.GetType() == tabType);
|
||||
}
|
||||
|
||||
public HotReloadRunTabState RunTabState { get; private set; }
|
||||
void OnGUI() {
|
||||
// TabState ensures rendering is consistent between Layout and Repaint calls
|
||||
// Without it errors like this happen:
|
||||
// ArgumentException: Getting control 2's position in a group with only 2 controls when doing repaint
|
||||
// See thread for more context: https://answers.unity.com/questions/17718/argumentexception-getting-control-2s-position-in-a.html
|
||||
if (Event.current.type == EventType.Layout) {
|
||||
RunTabState = HotReloadRunTabState.Current;
|
||||
}
|
||||
using(var scope = new EditorGUILayout.ScrollViewScope(scrollPos, false, false)) {
|
||||
scrollPos = scope.scrollPosition;
|
||||
// RenderDebug();
|
||||
RenderTabs();
|
||||
}
|
||||
GUILayout.FlexibleSpace(); // GUI below will be rendered on the bottom
|
||||
if (HotReloadWindowStyles.windowScreenHeight > 90)
|
||||
RenderBottomBar();
|
||||
}
|
||||
|
||||
void RenderDebug() {
|
||||
if (GUILayout.Button("RESET WINDOW")) {
|
||||
OnDisable();
|
||||
|
||||
RequestHelper.RequestLogin("test", "test", 1).Forget();
|
||||
|
||||
HotReloadPrefs.LicenseEmail = null;
|
||||
HotReloadPrefs.ExposeServerToLocalNetwork = true;
|
||||
HotReloadPrefs.LicensePassword = null;
|
||||
HotReloadPrefs.LoggedBurstHint = false;
|
||||
HotReloadPrefs.DontShowPromptForDownload = false;
|
||||
HotReloadPrefs.RateAppShown = false;
|
||||
HotReloadPrefs.ActiveDays = string.Empty;
|
||||
HotReloadPrefs.LaunchOnEditorStart = false;
|
||||
HotReloadPrefs.ShowUnsupportedChanges = true;
|
||||
HotReloadPrefs.RedeemLicenseEmail = null;
|
||||
HotReloadPrefs.RedeemLicenseInvoice = null;
|
||||
OnEnable();
|
||||
File.Delete(EditorCodePatcher.serverDownloader.GetExecutablePath(HotReloadCli.controller));
|
||||
InstallUtility.DebugClearInstallState();
|
||||
InstallUtility.CheckForNewInstall();
|
||||
EditorPrefs.DeleteKey(Attribution.LastLoginKey);
|
||||
File.Delete(RedeemLicenseHelper.registerOutcomePath);
|
||||
|
||||
CompileMethodDetourer.Reset();
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RenderLogo(int width = 243) {
|
||||
var isDarkMode = HotReloadWindowStyles.IsDarkMode;
|
||||
var tex = Resources.Load<Texture>(isDarkMode ? "Logo_HotReload_DarkMode" : "Logo_HotReload_LightMode");
|
||||
//Can happen during player builds where Editor Resources are unavailable
|
||||
if(tex == null) {
|
||||
return;
|
||||
}
|
||||
var targetWidth = width;
|
||||
var targetHeight = 44;
|
||||
GUILayout.Space(4f);
|
||||
// background padding top and bottom
|
||||
float padding = 5f;
|
||||
// reserve layout space for the texture
|
||||
var backgroundRect = GUILayoutUtility.GetRect(targetWidth + padding, targetHeight + padding, HotReloadWindowStyles.LogoStyle);
|
||||
// draw the texture into that reserved space. First the bg then the logo.
|
||||
if (isDarkMode) {
|
||||
GUI.DrawTexture(backgroundRect, EditorTextures.DarkGray17, ScaleMode.StretchToFill);
|
||||
} else {
|
||||
GUI.DrawTexture(backgroundRect, EditorTextures.LightGray238, ScaleMode.StretchToFill);
|
||||
}
|
||||
|
||||
var foregroundRect = backgroundRect;
|
||||
foregroundRect.yMin += padding;
|
||||
foregroundRect.yMax -= padding;
|
||||
// during player build (EditorWindow still visible), Resources.Load returns null
|
||||
if (tex) {
|
||||
GUI.DrawTexture(foregroundRect, tex, ScaleMode.ScaleToFit);
|
||||
}
|
||||
}
|
||||
|
||||
int? collapsedTab;
|
||||
void RenderTabs() {
|
||||
using(new EditorGUILayout.VerticalScope(HotReloadWindowStyles.BoxStyle)) {
|
||||
if (HotReloadWindowStyles.windowScreenHeight > 210 && HotReloadWindowStyles.windowScreenWidth > 375) {
|
||||
selectedTab = GUILayout.Toolbar(
|
||||
selectedTab,
|
||||
Tabs.Select(t =>
|
||||
new GUIContent(t.Title.StartsWith(" ", StringComparison.Ordinal) ? t.Title : " " + t.Title,
|
||||
t.Icon, t.Tooltip)).ToArray(),
|
||||
GUILayout.Height(22f) // required, otherwise largest icon height determines toolbar height
|
||||
);
|
||||
if (collapsedTab != null) {
|
||||
selectedTab = collapsedTab.Value;
|
||||
collapsedTab = null;
|
||||
}
|
||||
} else {
|
||||
if (collapsedTab == null) {
|
||||
collapsedTab = selectedTab;
|
||||
}
|
||||
// When window is super small, we pretty much can only show run tab
|
||||
SelectTab(typeof(HotReloadRunTab));
|
||||
}
|
||||
|
||||
if (HotReloadWindowStyles.windowScreenHeight > 250 && HotReloadWindowStyles.windowScreenWidth > 275) {
|
||||
RenderLogo();
|
||||
}
|
||||
|
||||
Tabs[selectedTab].OnGUI();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBottomBar() {
|
||||
SemVersion newVersion;
|
||||
var updateAvailable = packageUpdateChecker.TryGetNewVersion(out newVersion);
|
||||
|
||||
if (HotReloadWindowStyles.windowScreenWidth > Constants.RateAppHideWidth
|
||||
&& HotReloadWindowStyles.windowScreenHeight > Constants.RateAppHideHeight
|
||||
) {
|
||||
RenderRateApp();
|
||||
}
|
||||
|
||||
if (updateAvailable) {
|
||||
RenderUpdateButton(newVersion);
|
||||
}
|
||||
|
||||
using(new EditorGUILayout.HorizontalScope("ProjectBrowserBottomBarBg", GUILayout.ExpandWidth(true), GUILayout.Height(25f))) {
|
||||
RenderBottomBarCore();
|
||||
}
|
||||
}
|
||||
|
||||
static GUIStyle _renderAppBoxStyle;
|
||||
static GUIStyle renderAppBoxStyle => _renderAppBoxStyle ?? (_renderAppBoxStyle = new GUIStyle(GUI.skin.box) {
|
||||
padding = new RectOffset(10, 10, 0, 0)
|
||||
});
|
||||
|
||||
static GUILayoutOption[] _nonExpandable;
|
||||
public static GUILayoutOption[] NonExpandableLayout => _nonExpandable ?? (_nonExpandable = new [] {GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true)});
|
||||
|
||||
internal static void RenderRateApp() {
|
||||
if (!ShouldShowRateApp()) {
|
||||
return;
|
||||
}
|
||||
using (new EditorGUILayout.VerticalScope(renderAppBoxStyle)) {
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
HotReloadGUIHelper.HelpBox("Are you enjoying using Hot Reload?", MessageType.Info, 11);
|
||||
if (GUILayout.Button("Hide", NonExpandableLayout)) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.RateApp), new EditorExtraData { { "dismissed", true } }).Forget();
|
||||
HotReloadPrefs.RateAppShown = true;
|
||||
}
|
||||
}
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
if (GUILayout.Button("Yes")) {
|
||||
var openedUrl = PackageConst.IsAssetStoreBuild && EditorUtility.DisplayDialog("Rate Hot Reload", "Thank you for using Hot Reload!\n\nPlease consider leaving a review on the Asset Store to support us.", "Open in browser", "Cancel");
|
||||
if (openedUrl) {
|
||||
Application.OpenURL(Constants.UnityStoreRateAppURL);
|
||||
}
|
||||
HotReloadPrefs.RateAppShown = true;
|
||||
var data = new EditorExtraData();
|
||||
if (PackageConst.IsAssetStoreBuild) {
|
||||
data.Add("opened_url", openedUrl);
|
||||
}
|
||||
data.Add("enjoy_app", true);
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.RateApp), data).Forget();
|
||||
}
|
||||
if (GUILayout.Button("No")) {
|
||||
HotReloadPrefs.RateAppShown = true;
|
||||
var data = new EditorExtraData();
|
||||
data.Add("enjoy_app", false);
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Debug, StatFeature.RateApp), data).Forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool ShouldShowRateApp() {
|
||||
if (HotReloadPrefs.RateAppShown) {
|
||||
return false;
|
||||
}
|
||||
var activeDays = EditorCodePatcher.GetActiveDaysForRateApp();
|
||||
if (activeDays.Count < Constants.DaysToRateApp) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderUpdateButton(SemVersion newVersion) {
|
||||
if (GUILayout.Button($"Update To v{newVersion}", HotReloadWindowStyles.UpgradeButtonStyle)) {
|
||||
packageUpdateChecker.UpdatePackageAsync(newVersion).Forget(CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RenderShowOnStartup() {
|
||||
var prevLabelWidth = EditorGUIUtility.labelWidth;
|
||||
try {
|
||||
EditorGUIUtility.labelWidth = 105f;
|
||||
using (new GUILayout.VerticalScope()) {
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
GUILayout.Label("Show On Startup");
|
||||
Rect buttonRect = GUILayoutUtility.GetLastRect();
|
||||
if (EditorGUILayout.DropdownButton(new GUIContent(Regex.Replace(_showOnStartupOption.ToString(), "([a-z])([A-Z])", "$1 $2")), FocusType.Passive, GUILayout.Width(110f))) {
|
||||
GenericMenu menu = new GenericMenu();
|
||||
foreach (ShowOnStartupEnum option in Enum.GetValues(typeof(ShowOnStartupEnum))) {
|
||||
menu.AddItem(new GUIContent(Regex.Replace(option.ToString(), "([a-z])([A-Z])", "$1 $2")), false, () => {
|
||||
if (_showOnStartupOption != option) {
|
||||
_showOnStartupOption = option;
|
||||
HotReloadPrefs.ShowOnStartup = _showOnStartupOption;
|
||||
}
|
||||
});
|
||||
}
|
||||
menu.DropDown(new Rect(buttonRect.x, buttonRect.y, 100, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
EditorGUIUtility.labelWidth = prevLabelWidth;
|
||||
}
|
||||
}
|
||||
|
||||
internal static readonly OpenURLButton autoRefreshTroubleshootingBtn = new OpenURLButton("Troubleshooting", Constants.TroubleshootingURL);
|
||||
void RenderBottomBarCore() {
|
||||
bool troubleshootingShown = EditorCodePatcher.Started && HotReloadWindowStyles.windowScreenWidth >= 400;
|
||||
bool alertsShown = EditorCodePatcher.Started && HotReloadWindowStyles.windowScreenWidth > Constants.EventFiltersShownHideWidth;
|
||||
using (new EditorGUILayout.VerticalScope()) {
|
||||
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.FooterStyle)) {
|
||||
if (!troubleshootingShown) {
|
||||
GUILayout.FlexibleSpace();
|
||||
if (alertsShown) {
|
||||
GUILayout.Space(-20);
|
||||
}
|
||||
} else {
|
||||
GUILayout.Space(21);
|
||||
}
|
||||
GUILayout.Space(0);
|
||||
var lastRect = GUILayoutUtility.GetLastRect();
|
||||
// show events button when scrolls are hidden
|
||||
if (!HotReloadRunTab.CanRenderBars(RunTabState) && !RunTabState.starting) {
|
||||
using (new EditorGUILayout.VerticalScope()) {
|
||||
GUILayout.FlexibleSpace();
|
||||
var icon = HotReloadState.ShowingRedDot ? InvertibleIcon.EventsNew : InvertibleIcon.Events;
|
||||
if (GUILayout.Button(new GUIContent("", GUIHelper.GetInvertibleIcon(icon)))) {
|
||||
PopupWindow.Show(new Rect(lastRect.x, lastRect.y, 0, 0), HotReloadEventPopup.I);
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
GUILayout.Space(3f);
|
||||
}
|
||||
if (alertsShown) {
|
||||
using (new EditorGUILayout.VerticalScope()) {
|
||||
GUILayout.FlexibleSpace();
|
||||
HotReloadTimelineHelper.RenderAlertFilters();
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
if (troubleshootingShown) {
|
||||
using (new EditorGUILayout.VerticalScope()) {
|
||||
GUILayout.FlexibleSpace();
|
||||
autoRefreshTroubleshootingBtn.OnGUI();
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
GUILayout.Space(21);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f62a84c0b148b0a4582bdd9f1a69e6d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
enum ShowOnStartupEnum {
|
||||
Always,
|
||||
OnNewVersion,
|
||||
Never,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 809f47245f717ad41996974be2443feb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83e25ceea0bb7cd4ebf04b724bb0584c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,777 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SingularityGroup.HotReload.Editor {
|
||||
internal static class HotReloadWindowStyles {
|
||||
private static GUIStyle h1TitleStyle;
|
||||
private static GUIStyle h1TitleCenteredStyle;
|
||||
private static GUIStyle h2TitleStyle;
|
||||
private static GUIStyle h3TitleStyle;
|
||||
private static GUIStyle h3TitleWrapStyle;
|
||||
private static GUIStyle h4TitleStyle;
|
||||
private static GUIStyle h5TitleStyle;
|
||||
private static GUIStyle boxStyle;
|
||||
private static GUIStyle wrapStyle;
|
||||
private static GUIStyle noPaddingMiddleLeftStyle;
|
||||
private static GUIStyle middleLeftStyle;
|
||||
private static GUIStyle middleCenterStyle;
|
||||
private static GUIStyle mediumMiddleCenterStyle;
|
||||
private static GUIStyle textFieldWrapStyle;
|
||||
private static GUIStyle foldoutStyle;
|
||||
private static GUIStyle h3CenterTitleStyle;
|
||||
private static GUIStyle logoStyle;
|
||||
private static GUIStyle changelogPointersStyle;
|
||||
private static GUIStyle recompileButtonStyle;
|
||||
private static GUIStyle indicationIconStyle;
|
||||
private static GUIStyle indicationAlertIconStyle;
|
||||
private static GUIStyle startButtonStyle;
|
||||
private static GUIStyle stopButtonStyle;
|
||||
private static GUIStyle eventFilters;
|
||||
private static GUIStyle sectionOuterBoxCompactStyle;
|
||||
private static GUIStyle sectionInnerBoxStyle;
|
||||
private static GUIStyle sectionInnerBoxWideStyle;
|
||||
private static GUIStyle changelogSectionInnerBoxStyle;
|
||||
private static GUIStyle indicationBoxStyle;
|
||||
private static GUIStyle linkStyle;
|
||||
private static GUIStyle labelStyle;
|
||||
private static GUIStyle progressBarBarStyle;
|
||||
private static GUIStyle section;
|
||||
private static GUIStyle scroll;
|
||||
private static GUIStyle barStyle;
|
||||
private static GUIStyle barBgStyle;
|
||||
private static GUIStyle barChildStyle;
|
||||
private static GUIStyle barFoldoutStyle;
|
||||
private static GUIStyle timestampStyle;
|
||||
private static GUIStyle clickableLabelBoldStyle;
|
||||
private static GUIStyle _footerStyle;
|
||||
private static GUIStyle _emptyListText;
|
||||
private static GUIStyle _stacktraceTextAreaStyle;
|
||||
private static GUIStyle _customFoldoutStyle;
|
||||
private static GUIStyle _entryBoxStyle;
|
||||
private static GUIStyle _childEntryBoxStyle;
|
||||
private static GUIStyle _removeIconStyle;
|
||||
private static GUIStyle upgradeLicenseButtonStyle;
|
||||
private static GUIStyle upgradeLicenseButtonOverlayStyle;
|
||||
private static GUIStyle upgradeButtonStyle;
|
||||
private static GUIStyle hideButtonStyle;
|
||||
private static GUIStyle dynamicSection;
|
||||
private static GUIStyle dynamicSectionHelpTab;
|
||||
private static GUIStyle helpTabButton;
|
||||
private static GUIStyle indicationHelpBox;
|
||||
private static GUIStyle notificationsTitleStyle;
|
||||
|
||||
private static Color32? darkModeLinkColor;
|
||||
private static Color32? lightModeModeLinkColor;
|
||||
|
||||
public static bool IsDarkMode => EditorGUIUtility.isProSkin;
|
||||
public static int windowScreenWidth => HotReloadWindow.Current ? (int)HotReloadWindow.Current.position.width : Screen.width;
|
||||
public static int windowScreenHeight => HotReloadWindow.Current ? (int)HotReloadWindow.Current.position.height : Screen.height;
|
||||
public static GUIStyle H1TitleStyle {
|
||||
get {
|
||||
if (h1TitleStyle == null) {
|
||||
h1TitleStyle = new GUIStyle(EditorStyles.label);
|
||||
h1TitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
|
||||
h1TitleStyle.fontStyle = FontStyle.Bold;
|
||||
h1TitleStyle.fontSize = 16;
|
||||
h1TitleStyle.padding.top = 5;
|
||||
h1TitleStyle.padding.bottom = 5;
|
||||
}
|
||||
return h1TitleStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle FooterStyle {
|
||||
get {
|
||||
if (_footerStyle == null) {
|
||||
_footerStyle = new GUIStyle();
|
||||
_footerStyle.fixedHeight = 28;
|
||||
}
|
||||
return _footerStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle H1TitleCenteredStyle {
|
||||
get {
|
||||
if (h1TitleCenteredStyle == null) {
|
||||
h1TitleCenteredStyle = new GUIStyle(H1TitleStyle);
|
||||
h1TitleCenteredStyle.alignment = TextAnchor.MiddleCenter;
|
||||
}
|
||||
return h1TitleCenteredStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle H2TitleStyle {
|
||||
get {
|
||||
if (h2TitleStyle == null) {
|
||||
h2TitleStyle = new GUIStyle(EditorStyles.label);
|
||||
h2TitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
|
||||
h2TitleStyle.fontStyle = FontStyle.Bold;
|
||||
h2TitleStyle.fontSize = 14;
|
||||
h2TitleStyle.padding.top = 5;
|
||||
h2TitleStyle.padding.bottom = 5;
|
||||
}
|
||||
return h2TitleStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle H3TitleStyle {
|
||||
get {
|
||||
if (h3TitleStyle == null) {
|
||||
h3TitleStyle = new GUIStyle(EditorStyles.label);
|
||||
h3TitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
|
||||
h3TitleStyle.fontStyle = FontStyle.Bold;
|
||||
h3TitleStyle.fontSize = 12;
|
||||
h3TitleStyle.padding.top = 5;
|
||||
h3TitleStyle.padding.bottom = 5;
|
||||
}
|
||||
return h3TitleStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle NotificationsTitleStyle {
|
||||
get {
|
||||
if (notificationsTitleStyle == null) {
|
||||
notificationsTitleStyle = new GUIStyle(HotReloadWindowStyles.H3TitleStyle);
|
||||
notificationsTitleStyle.padding.bottom = 0;
|
||||
notificationsTitleStyle.padding.top = 0;
|
||||
}
|
||||
return notificationsTitleStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle H3TitleWrapStyle {
|
||||
get {
|
||||
if (h3TitleWrapStyle == null) {
|
||||
h3TitleWrapStyle = new GUIStyle(H3TitleStyle);
|
||||
h3TitleWrapStyle.wordWrap = true;
|
||||
}
|
||||
return h3TitleWrapStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle H3CenteredTitleStyle {
|
||||
get {
|
||||
if (h3CenterTitleStyle == null) {
|
||||
h3CenterTitleStyle = new GUIStyle(EditorStyles.label);
|
||||
h3CenterTitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
|
||||
h3CenterTitleStyle.fontStyle = FontStyle.Bold;
|
||||
h3CenterTitleStyle.alignment = TextAnchor.MiddleCenter;
|
||||
h3CenterTitleStyle.fontSize = 12;
|
||||
}
|
||||
return h3CenterTitleStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle H4TitleStyle {
|
||||
get {
|
||||
if (h4TitleStyle == null) {
|
||||
h4TitleStyle = new GUIStyle(EditorStyles.label);
|
||||
h4TitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
|
||||
h4TitleStyle.fontStyle = FontStyle.Bold;
|
||||
h4TitleStyle.fontSize = 11;
|
||||
}
|
||||
return h4TitleStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle H5TitleStyle {
|
||||
get {
|
||||
if (h5TitleStyle == null) {
|
||||
h5TitleStyle = new GUIStyle(EditorStyles.label);
|
||||
h5TitleStyle.normal.textColor = EditorStyles.label.normal.textColor;
|
||||
h5TitleStyle.fontStyle = FontStyle.Bold;
|
||||
h5TitleStyle.fontSize = 10;
|
||||
}
|
||||
return h5TitleStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle LabelStyle {
|
||||
get {
|
||||
if (labelStyle == null) {
|
||||
labelStyle = new GUIStyle(EditorStyles.label);
|
||||
labelStyle.fontSize = 12;
|
||||
labelStyle.clipping = TextClipping.Clip;
|
||||
labelStyle.wordWrap = true;
|
||||
}
|
||||
return labelStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle BoxStyle {
|
||||
get {
|
||||
if (boxStyle == null) {
|
||||
boxStyle = new GUIStyle(EditorStyles.helpBox);
|
||||
boxStyle.normal.textColor = GUI.skin.label.normal.textColor;
|
||||
boxStyle.fontStyle = FontStyle.Bold;
|
||||
boxStyle.alignment = TextAnchor.UpperLeft;
|
||||
}
|
||||
if (!IsDarkMode) {
|
||||
boxStyle.normal.background = Texture2D.blackTexture;
|
||||
}
|
||||
return boxStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle WrapStyle {
|
||||
get {
|
||||
if (wrapStyle == null) {
|
||||
wrapStyle = new GUIStyle(EditorStyles.label);
|
||||
wrapStyle.fontStyle = FontStyle.Normal;
|
||||
wrapStyle.wordWrap = true;
|
||||
}
|
||||
return wrapStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle NoPaddingMiddleLeftStyle {
|
||||
get {
|
||||
if (noPaddingMiddleLeftStyle == null) {
|
||||
noPaddingMiddleLeftStyle = new GUIStyle(EditorStyles.label);
|
||||
noPaddingMiddleLeftStyle.normal.textColor = GUI.skin.label.normal.textColor;
|
||||
noPaddingMiddleLeftStyle.padding = new RectOffset();
|
||||
noPaddingMiddleLeftStyle.margin = new RectOffset();
|
||||
noPaddingMiddleLeftStyle.alignment = TextAnchor.MiddleLeft;
|
||||
}
|
||||
return noPaddingMiddleLeftStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle MiddleLeftStyle {
|
||||
get {
|
||||
if (middleLeftStyle == null) {
|
||||
middleLeftStyle = new GUIStyle(EditorStyles.label);
|
||||
middleLeftStyle.fontStyle = FontStyle.Normal;
|
||||
middleLeftStyle.alignment = TextAnchor.MiddleLeft;
|
||||
}
|
||||
|
||||
return middleLeftStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle MiddleCenterStyle {
|
||||
get {
|
||||
if (middleCenterStyle == null) {
|
||||
middleCenterStyle = new GUIStyle(EditorStyles.label);
|
||||
middleCenterStyle.fontStyle = FontStyle.Normal;
|
||||
middleCenterStyle.alignment = TextAnchor.MiddleCenter;
|
||||
}
|
||||
return middleCenterStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle MediumMiddleCenterStyle {
|
||||
get {
|
||||
if (mediumMiddleCenterStyle == null) {
|
||||
mediumMiddleCenterStyle = new GUIStyle(EditorStyles.label);
|
||||
mediumMiddleCenterStyle.fontStyle = FontStyle.Normal;
|
||||
mediumMiddleCenterStyle.fontSize = 12;
|
||||
mediumMiddleCenterStyle.alignment = TextAnchor.MiddleCenter;
|
||||
}
|
||||
return mediumMiddleCenterStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle TextFieldWrapStyle {
|
||||
get {
|
||||
if (textFieldWrapStyle == null) {
|
||||
textFieldWrapStyle = new GUIStyle(EditorStyles.textField);
|
||||
textFieldWrapStyle.wordWrap = true;
|
||||
}
|
||||
return textFieldWrapStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle FoldoutStyle {
|
||||
get {
|
||||
if (foldoutStyle == null) {
|
||||
foldoutStyle = new GUIStyle(EditorStyles.foldout);
|
||||
foldoutStyle.normal.textColor = GUI.skin.label.normal.textColor;
|
||||
foldoutStyle.alignment = TextAnchor.MiddleLeft;
|
||||
foldoutStyle.fontStyle = FontStyle.Bold;
|
||||
foldoutStyle.fontSize = 12;
|
||||
}
|
||||
return foldoutStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle LogoStyle {
|
||||
get {
|
||||
if (logoStyle == null) {
|
||||
logoStyle = new GUIStyle();
|
||||
logoStyle.margin = new RectOffset(6, 6, 0, 0);
|
||||
logoStyle.padding = new RectOffset(16, 16, 0, 0);
|
||||
}
|
||||
return logoStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle ChangelogPointerStyle {
|
||||
get {
|
||||
if (changelogPointersStyle == null) {
|
||||
changelogPointersStyle = new GUIStyle(EditorStyles.label);
|
||||
changelogPointersStyle.wordWrap = true;
|
||||
changelogPointersStyle.fontSize = 12;
|
||||
changelogPointersStyle.padding.left = 20;
|
||||
}
|
||||
return changelogPointersStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle IndicationIcon {
|
||||
get {
|
||||
if (indicationIconStyle == null) {
|
||||
indicationIconStyle = new GUIStyle(H2TitleStyle);
|
||||
indicationIconStyle.fixedHeight = 20;
|
||||
}
|
||||
indicationIconStyle.padding = new RectOffset(left: windowScreenWidth > Constants.IndicationTextHideWidth ? 7 : 5, right: windowScreenWidth > Constants.IndicationTextHideWidth ? 0 : -10, top: 1, bottom: 1);
|
||||
return indicationIconStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle IndicationAlertIcon {
|
||||
get {
|
||||
if (indicationAlertIconStyle == null) {
|
||||
indicationAlertIconStyle = new GUIStyle(H2TitleStyle);
|
||||
indicationAlertIconStyle.padding = new RectOffset(left: 5, right: -7, top: 1, bottom: 1);
|
||||
indicationAlertIconStyle.fixedHeight = 20;
|
||||
}
|
||||
return indicationAlertIconStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle RecompileButton {
|
||||
get {
|
||||
if (recompileButtonStyle == null) {
|
||||
recompileButtonStyle = new GUIStyle(EditorStyles.miniButton);
|
||||
recompileButtonStyle.margin.top = 17;
|
||||
recompileButtonStyle.fixedHeight = 25;
|
||||
recompileButtonStyle.margin.right = 5;
|
||||
}
|
||||
recompileButtonStyle.fixedWidth = windowScreenWidth > Constants.RecompileButtonTextHideWidth ? 95 : 30;
|
||||
return recompileButtonStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle StartButton {
|
||||
get {
|
||||
if (startButtonStyle == null) {
|
||||
startButtonStyle = new GUIStyle(EditorStyles.miniButton);
|
||||
startButtonStyle.fixedHeight = 25;
|
||||
startButtonStyle.padding.top = 6;
|
||||
startButtonStyle.padding.bottom = 6;
|
||||
startButtonStyle.margin.top = 17;
|
||||
}
|
||||
startButtonStyle.fixedWidth = windowScreenWidth > Constants.StartButtonTextHideWidth ? 70 : 30;
|
||||
return startButtonStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle StopButton {
|
||||
get {
|
||||
if (stopButtonStyle == null) {
|
||||
stopButtonStyle = new GUIStyle(EditorStyles.miniButton);
|
||||
stopButtonStyle.fixedHeight = 25;
|
||||
stopButtonStyle.margin.top = 17;
|
||||
}
|
||||
stopButtonStyle.fixedWidth = HotReloadWindowStyles.windowScreenWidth > Constants.StartButtonTextHideWidth ? 70 : 30;
|
||||
return stopButtonStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle EventFiltersStyle {
|
||||
get {
|
||||
if (eventFilters == null) {
|
||||
eventFilters = new GUIStyle(EditorStyles.toolbarButton);
|
||||
eventFilters.fontSize = 13;
|
||||
// gets overwritten to content size
|
||||
eventFilters.fixedHeight = 26;
|
||||
eventFilters.fixedWidth = 50;
|
||||
eventFilters.margin = new RectOffset(0, 0, 0, 0);
|
||||
eventFilters.padding = new RectOffset(0, 0, 6, 6);
|
||||
}
|
||||
return eventFilters;
|
||||
}
|
||||
}
|
||||
|
||||
private static Texture2D _clearBackground;
|
||||
private static Texture2D clearBackground {
|
||||
get {
|
||||
if (_clearBackground == null) {
|
||||
_clearBackground = new Texture2D(1, 1);
|
||||
_clearBackground.SetPixel(0, 0, Color.clear);
|
||||
_clearBackground.Apply();
|
||||
}
|
||||
return _clearBackground;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle SectionOuterBoxCompact {
|
||||
get {
|
||||
if (sectionOuterBoxCompactStyle == null) {
|
||||
sectionOuterBoxCompactStyle = new GUIStyle();
|
||||
sectionOuterBoxCompactStyle.padding.top = 10;
|
||||
sectionOuterBoxCompactStyle.padding.bottom = 10;
|
||||
}
|
||||
// Looks better without a background
|
||||
sectionOuterBoxCompactStyle.normal.background = clearBackground;
|
||||
return sectionOuterBoxCompactStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle SectionInnerBox {
|
||||
get {
|
||||
if (sectionInnerBoxStyle == null) {
|
||||
sectionInnerBoxStyle = new GUIStyle();
|
||||
}
|
||||
sectionInnerBoxStyle.padding = new RectOffset(left: 0, right: 0, top: 15, bottom: 0);
|
||||
return sectionInnerBoxStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle SectionInnerBoxWide {
|
||||
get {
|
||||
if (sectionInnerBoxWideStyle == null) {
|
||||
sectionInnerBoxWideStyle = new GUIStyle(EditorStyles.helpBox);
|
||||
sectionInnerBoxWideStyle.padding.top = 15;
|
||||
sectionInnerBoxWideStyle.padding.bottom = 15;
|
||||
sectionInnerBoxWideStyle.padding.left = 10;
|
||||
sectionInnerBoxWideStyle.padding.right = 10;
|
||||
}
|
||||
return sectionInnerBoxWideStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle DynamiSection {
|
||||
get {
|
||||
if (dynamicSection == null) {
|
||||
dynamicSection = new GUIStyle();
|
||||
}
|
||||
var defaultPadding = 13;
|
||||
if (windowScreenWidth > 600) {
|
||||
var dynamicPadding = (windowScreenWidth - 600) / 2;
|
||||
dynamicSection.padding.left = defaultPadding + dynamicPadding;
|
||||
dynamicSection.padding.right = defaultPadding + dynamicPadding;
|
||||
} else if (windowScreenWidth < Constants.IndicationTextHideWidth) {
|
||||
dynamicSection.padding.left = 0;
|
||||
dynamicSection.padding.right = 0;
|
||||
} else {
|
||||
dynamicSection.padding.left = 13;
|
||||
dynamicSection.padding.right = 13;
|
||||
}
|
||||
return dynamicSection;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle DynamicSectionHelpTab {
|
||||
get {
|
||||
if (dynamicSectionHelpTab == null) {
|
||||
dynamicSectionHelpTab = new GUIStyle(DynamiSection);
|
||||
}
|
||||
dynamicSectionHelpTab.padding.left = DynamiSection.padding.left - 3;
|
||||
dynamicSectionHelpTab.padding.right = DynamiSection.padding.right - 3;
|
||||
return dynamicSectionHelpTab;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle ChangelogSectionInnerBox {
|
||||
get {
|
||||
if (changelogSectionInnerBoxStyle == null) {
|
||||
changelogSectionInnerBoxStyle = new GUIStyle(EditorStyles.helpBox);
|
||||
changelogSectionInnerBoxStyle.margin.bottom = 10;
|
||||
changelogSectionInnerBoxStyle.margin.top = 10;
|
||||
}
|
||||
return changelogSectionInnerBoxStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle IndicationBox {
|
||||
get {
|
||||
if (indicationBoxStyle == null) {
|
||||
indicationBoxStyle = new GUIStyle();
|
||||
}
|
||||
indicationBoxStyle.margin.bottom = windowScreenWidth < 141 ? 0 : 10;
|
||||
return indicationBoxStyle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static GUIStyle LinkStyle {
|
||||
get {
|
||||
if (linkStyle == null) {
|
||||
linkStyle = new GUIStyle(EditorStyles.label);
|
||||
linkStyle.fontStyle = FontStyle.Bold;
|
||||
}
|
||||
var color = IsDarkMode ? DarkModeLinkColor : LightModeModeLinkColor;
|
||||
linkStyle.normal.textColor = color;
|
||||
return linkStyle;
|
||||
}
|
||||
}
|
||||
|
||||
private static Color32 DarkModeLinkColor {
|
||||
get {
|
||||
if (darkModeLinkColor == null) {
|
||||
darkModeLinkColor = new Color32(0x3F, 0x9F, 0xFF, 0xFF);
|
||||
}
|
||||
return darkModeLinkColor.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Color32 LightModeModeLinkColor {
|
||||
get {
|
||||
if (lightModeModeLinkColor == null) {
|
||||
lightModeModeLinkColor = new Color32(0x0F, 0x52, 0xD7, 0xFF);
|
||||
}
|
||||
return lightModeModeLinkColor.Value;
|
||||
}
|
||||
}
|
||||
public static GUIStyle ProgressBarBarStyle {
|
||||
get {
|
||||
if (progressBarBarStyle != null) {
|
||||
return progressBarBarStyle;
|
||||
}
|
||||
var styles = (EditorStyles)typeof(EditorStyles)
|
||||
.GetField("s_Current", BindingFlags.Static | BindingFlags.NonPublic)
|
||||
?.GetValue(null);
|
||||
var style = styles?.GetType()
|
||||
.GetField("m_ProgressBarBar", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?.GetValue(styles);
|
||||
progressBarBarStyle = style != null ? (GUIStyle)style : GUIStyle.none;
|
||||
return progressBarBarStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle Section {
|
||||
get {
|
||||
if (section == null) {
|
||||
section = new GUIStyle(EditorStyles.helpBox);
|
||||
section.padding = new RectOffset(left: 10, right: 10, top: 10, bottom: 10);
|
||||
section.margin = new RectOffset(left: 0, right: 0, top: 0, bottom: 0);
|
||||
}
|
||||
return section;
|
||||
}
|
||||
}
|
||||
internal static GUIStyle Scroll {
|
||||
get {
|
||||
if (scroll == null) {
|
||||
scroll = new GUIStyle(EditorStyles.helpBox);
|
||||
}
|
||||
if (IsDarkMode) {
|
||||
scroll.normal.background = GUIHelper.ConvertTextureToColor(new Color(0,0,0,0.05f));
|
||||
} else {
|
||||
scroll.normal.background = GUIHelper.ConvertTextureToColor(new Color(0,0,0,0.03f));
|
||||
}
|
||||
return scroll;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle BarStyle {
|
||||
get {
|
||||
if (barStyle == null) {
|
||||
barStyle = new GUIStyle(GUI.skin.label);
|
||||
barStyle.fontSize = 12;
|
||||
barStyle.alignment = TextAnchor.MiddleLeft;
|
||||
barStyle.fixedHeight = 20;
|
||||
barStyle.padding = new RectOffset(10, 5, 2, 2);
|
||||
}
|
||||
return barStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle BarBackgroundStyle {
|
||||
get {
|
||||
if (barBgStyle == null) {
|
||||
barBgStyle = new GUIStyle();
|
||||
}
|
||||
barBgStyle.normal.background = GUIHelper.ConvertTextureToColor(Color.clear);
|
||||
barBgStyle.hover.background = GUIHelper.ConvertTextureToColor(new Color(0, 0, 0, 0.1f));
|
||||
barBgStyle.focused.background = GUIHelper.ConvertTextureToColor(Color.clear);
|
||||
barBgStyle.active.background = null;
|
||||
return barBgStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle ChildBarStyle {
|
||||
get {
|
||||
if (barChildStyle == null) {
|
||||
barChildStyle = new GUIStyle(BarStyle);
|
||||
barChildStyle.padding = new RectOffset(43, barChildStyle.padding.right, barChildStyle.padding.top, barChildStyle.padding.bottom);
|
||||
}
|
||||
return barChildStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle FoldoutBarStyle {
|
||||
get {
|
||||
if (barFoldoutStyle == null) {
|
||||
barFoldoutStyle = new GUIStyle(BarStyle);
|
||||
barFoldoutStyle.padding = new RectOffset(23, barFoldoutStyle.padding.right, barFoldoutStyle.padding.top, barFoldoutStyle.padding.bottom);
|
||||
}
|
||||
return barFoldoutStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static GUIStyle TimestampStyle {
|
||||
get {
|
||||
if (timestampStyle == null) {
|
||||
timestampStyle = new GUIStyle(GUI.skin.label);
|
||||
}
|
||||
if (IsDarkMode) {
|
||||
timestampStyle.normal.textColor = new Color(0.5f, 0.5f, 0.5f);
|
||||
} else {
|
||||
timestampStyle.normal.textColor = new Color(0.5f, 0.5f, 0.5f);
|
||||
}
|
||||
timestampStyle.hover = timestampStyle.normal;
|
||||
return timestampStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle ClickableLabelBoldStyle {
|
||||
get {
|
||||
if (clickableLabelBoldStyle == null) {
|
||||
clickableLabelBoldStyle = new GUIStyle(LabelStyle);
|
||||
clickableLabelBoldStyle.fontStyle = FontStyle.Bold;
|
||||
clickableLabelBoldStyle.fontSize = 14;
|
||||
clickableLabelBoldStyle.margin.left = 17;
|
||||
clickableLabelBoldStyle.active.textColor = clickableLabelBoldStyle.normal.textColor;
|
||||
}
|
||||
return clickableLabelBoldStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle EmptyListText {
|
||||
get {
|
||||
if (_emptyListText == null) {
|
||||
_emptyListText = new GUIStyle();
|
||||
_emptyListText.fontSize = 11;
|
||||
_emptyListText.padding.left = 15;
|
||||
_emptyListText.padding.top = 10;
|
||||
_emptyListText.alignment = TextAnchor.MiddleCenter;
|
||||
_emptyListText.normal.textColor = Color.gray;
|
||||
}
|
||||
|
||||
return _emptyListText;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle StacktraceTextAreaStyle {
|
||||
get {
|
||||
if (_stacktraceTextAreaStyle == null) {
|
||||
_stacktraceTextAreaStyle = new GUIStyle(EditorStyles.textArea);
|
||||
_stacktraceTextAreaStyle.border = new RectOffset(0, 0, 0, 0);
|
||||
}
|
||||
return _stacktraceTextAreaStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle EntryBoxStyle {
|
||||
get {
|
||||
if (_entryBoxStyle == null) {
|
||||
_entryBoxStyle = new GUIStyle();
|
||||
_entryBoxStyle.margin.left = 30;
|
||||
}
|
||||
return _entryBoxStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle ChildEntryBoxStyle {
|
||||
get {
|
||||
if (_childEntryBoxStyle == null) {
|
||||
_childEntryBoxStyle = new GUIStyle();
|
||||
_childEntryBoxStyle.margin.left = 45;
|
||||
}
|
||||
return _childEntryBoxStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle CustomFoldoutStyle {
|
||||
get {
|
||||
if (_customFoldoutStyle == null) {
|
||||
_customFoldoutStyle = new GUIStyle(EditorStyles.foldout);
|
||||
_customFoldoutStyle.margin.top = 4;
|
||||
_customFoldoutStyle.margin.left = 0;
|
||||
_customFoldoutStyle.padding.left = 0;
|
||||
_customFoldoutStyle.fixedWidth = 100;
|
||||
}
|
||||
return _customFoldoutStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle RemoveIconStyle {
|
||||
get {
|
||||
if (_removeIconStyle == null) {
|
||||
_removeIconStyle = new GUIStyle();
|
||||
_removeIconStyle.margin.top = 5;
|
||||
_removeIconStyle.fixedWidth = 17;
|
||||
_removeIconStyle.fixedHeight = 17;
|
||||
}
|
||||
return _removeIconStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle UpgradeLicenseButtonStyle {
|
||||
get {
|
||||
if (upgradeLicenseButtonStyle == null) {
|
||||
upgradeLicenseButtonStyle = new GUIStyle(GUI.skin.button);
|
||||
upgradeLicenseButtonStyle.padding = new RectOffset(5, 5, 0, 0);
|
||||
}
|
||||
return upgradeLicenseButtonStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle UpgradeLicenseButtonOverlayStyle {
|
||||
get {
|
||||
if (upgradeLicenseButtonOverlayStyle == null) {
|
||||
upgradeLicenseButtonOverlayStyle = new GUIStyle(UpgradeLicenseButtonStyle);
|
||||
}
|
||||
return upgradeLicenseButtonOverlayStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle UpgradeButtonStyle {
|
||||
get {
|
||||
if (upgradeButtonStyle == null) {
|
||||
upgradeButtonStyle = new GUIStyle(EditorStyles.miniButton);
|
||||
upgradeButtonStyle.fontStyle = FontStyle.Bold;
|
||||
upgradeButtonStyle.fontSize = 14;
|
||||
upgradeButtonStyle.fixedHeight = 24;
|
||||
}
|
||||
return upgradeButtonStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle HideButtonStyle {
|
||||
get {
|
||||
if (hideButtonStyle == null) {
|
||||
hideButtonStyle = new GUIStyle(GUI.skin.button);
|
||||
}
|
||||
return hideButtonStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle HelpTabButton {
|
||||
get {
|
||||
if (helpTabButton == null) {
|
||||
helpTabButton = new GUIStyle(GUI.skin.button);
|
||||
helpTabButton.alignment = TextAnchor.MiddleLeft;
|
||||
helpTabButton.padding.left = 10;
|
||||
}
|
||||
return helpTabButton;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GUIStyle IndicationHelpBox {
|
||||
get {
|
||||
if (indicationHelpBox == null) {
|
||||
indicationHelpBox = new GUIStyle(EditorStyles.helpBox);
|
||||
indicationHelpBox.margin.right = 0;
|
||||
indicationHelpBox.margin.left = 0;
|
||||
}
|
||||
return indicationHelpBox;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c06a986e9e8c3874f9578f0002ff3a2d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user