first commit
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user