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