using System; using System.Collections.Generic; using System.Threading.Tasks; using UnityEditor; using UnityEngine; using UnityEditor.UIElements; using UnityEngine.UIElements; using SingularityGroup.HotReload.Editor.Localization; using Application = UnityEngine.Application; #if UNITY_2021_3_OR_NEWER using System.IO; using System.IO.Compression; using SingularityGroup.HotReload.Editor.Cli; using SingularityGroup.HotReload.EditorDependencies; using CompressionLevel = System.IO.Compression.CompressionLevel; #endif namespace SingularityGroup.HotReload.Editor { internal enum ReportMode { BugReport, Feedback } internal class HotReloadBugReportWindow : EditorWindow { static List SeverityChoices => new List { Translations.BugReport.SeverityNormal, Translations.BugReport.SeverityLow, Translations.BugReport.SeverityHigh, }; static List FrequencyChoices => new List { Translations.BugReport.FrequencyFirstTime, Translations.BugReport.FrequencySometimes, Translations.BugReport.FrequencyAlways, }; [SerializeField] ReportMode _mode = ReportMode.BugReport; [SerializeField] VisualTreeAsset visualTree; [SerializeField] StyleSheet styleSheet; string _discordUrl; string _contactUrl; TextField _titleField; PopupField _severityPopup; PopupField _frequencyPopup; TextField _emailField; IMGUIContainer _detailsContainer; [SerializeField] string _detailsText = ""; [SerializeField] string _emailText = ""; [SerializeField] string _titleText = ""; Label _detailsLabel; Label _validationLabel; Button _submitButton; VisualElement _severitySection; VisualElement _frequencySection; ScrollView _scrollView; VisualElement _detailsSlot; VisualElement _successScreen; VisualElement _failureScreen; Label _failureErrorLabel; Button _tabBugReport; Button _tabFeedback; bool _isOnResultScreen; static Vector2 GetMinSize(ReportMode mode) { return mode == ReportMode.Feedback ? new Vector2(420, 500) : new Vector2(420, 620); } #region Public API /// /// Opens the window with the given mode and configuration. /// Prefer using for a cleaner call site. /// public static HotReloadBugReportWindow Open( ReportMode mode, string discordUrl = null, string contactUrl = null, string title = null, string email = null, string details = null ) { var wnd = GetWindow(utility: true); wnd._mode = mode; wnd._discordUrl = discordUrl; wnd._contactUrl = contactUrl; wnd.titleContent = new GUIContent( mode == ReportMode.Feedback ? Translations.BugReport.WindowTitleFeedback : Translations.BugReport.WindowTitleBugReport); wnd.minSize = wnd.maxSize = GetMinSize(mode); wnd._titleText = title; wnd._emailText = email; wnd._detailsText = details; wnd.RebuildUI(); return wnd; } /// /// Prefills form fields on an already-open window. Null values are ignored. /// public void Prefill() { if (_titleText != null && _titleField != null) { _titleField.SetValueWithoutNotify(_titleText); } if (_emailText != null && _emailField != null) { _emailField.SetValueWithoutNotify(_emailText); } } #endregion void OnEnable() { RebuildUI(); } bool HasUnsavedFormContent() { if (_isOnResultScreen) { return false; } if (!string.IsNullOrWhiteSpace(_titleField.value)) { return true; } if (!string.IsNullOrWhiteSpace(_detailsText) && _detailsText != Translations.BugReport.PlaceholderDetails) { return true; } return false; } void OnDestroy() { if (_detailsContainer != null && _detailsSlot != null) { _detailsSlot.Remove(_detailsContainer); _detailsContainer = null; } if (!HasUnsavedFormContent()) { return; } bool discard = EditorUtility.DisplayDialog( Translations.BugReport.DiscardDialogTitle, Translations.BugReport.DiscardDialogMessage, Translations.BugReport.DiscardDialogConfirm, Translations.BugReport.DiscardDialogCancel); if (!discard) { _emailText = _emailField.value; _titleText = _titleField.value; EditorApplication.delayCall += () => { Open(_mode, _discordUrl, _contactUrl); }; } else { _detailsText = null; _emailText = null; _titleText = null; } } void RebuildUI() { DetachCallbacks(); if (visualTree == null) { Log.Warning($"Could not open bug report. Please reach out to our support: {_contactUrl}"); return; } rootVisualElement.Clear(); visualTree.CloneTree(rootVisualElement); if (styleSheet != null) { rootVisualElement.styleSheets.Add(styleSheet); } QueryElements(); AttachIMGUIContainers(); ApplyTranslations(); CreatePopupFields(); ApplyMode(); SetupSubmit(); SetupResultScreenButtons(); FixSingleLineFieldAlignment(_titleField); FixSingleLineFieldAlignment(_emailField); Prefill(); AttachCallbacks(); ShowFormView(); } void AttachIMGUIContainers() { _detailsSlot = rootVisualElement.Q("details-field-slot"); // Build the IMGUI textarea and inject it _detailsContainer = new IMGUIContainer(DrawDetailsField); _detailsSlot.Add(_detailsContainer); } void QueryElements() { _titleField = rootVisualElement.Q("title-field"); _emailField = rootVisualElement.Q("email-field"); _detailsLabel = rootVisualElement.Q