/* ┌──────────────────────────────────────────────────────────────────┐ │ Author: Ivan Murzak (https://github.com/IvanMurzak) │ │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │ │ Copyright (c) 2025 Ivan Murzak │ │ Licensed under the Apache License, Version 2.0. │ │ See the LICENSE file in the project root for more information. │ └──────────────────────────────────────────────────────────────────┘ */ #nullable enable using System; using com.IvanMurzak.Unity.MCP.Editor.DependencyResolver; using com.IvanMurzak.Unity.MCP.Editor.Utils; using R3; using UnityEditor; using UnityEditor.PackageManager; using UnityEditor.PackageManager.Requests; using UnityEngine; using UnityEngine.UIElements; namespace com.IvanMurzak.Unity.MCP.Editor.UI { /// /// A popup window that notifies the user when a new version of AI Game Developer is available. /// public class UpdatePopupWindow : NotificationPopupWindow { private static readonly string[] _windowUxmlPaths = EditorAssetLoader.GetEditorAssetPaths("Editor/UI/uxml/UpdatePopupWindow.uxml"); private static readonly string[] _windowUssPaths = EditorAssetLoader.GetEditorAssetPaths("Editor/UI/uss/UpdatePopupWindow.uss"); private static readonly string[] LogoIconPaths = EditorAssetLoader.GetEditorAssetPaths("Editor/Gizmos/logo_512.png"); private const string PackageId = "com.ivanmurzak.unity.mcp"; protected override string[] WindowUxmlPaths => _windowUxmlPaths; protected override string[] WindowUssPaths => _windowUssPaths; protected override string WindowTitle => "Update Available"; // [SerializeField] so the version strings survive Unity's domain reload (script // recompile). Without this, after a recompile the EditorWindow instance is recreated, // CreateGUI() is invoked again, and BindUI() re-binds button click handlers — but // these private fields revert to empty, so the labels render empty and "Install // Update" attempts to install "{PackageId}@" (empty version), which silently fails. // See https://github.com/IvanMurzak/Unity-MCP/issues/702. [SerializeField] private string currentVersion = string.Empty; [SerializeField] private string latestVersion = string.Empty; // The AddRequest holds a native Package Manager handle and the R3 subscription holds // a reactive pipeline; neither survives a domain reload. Unity does, however, carry // these private references over the reload boundary even without [SerializeField], so // OnInstallUpdateClicked's "already in progress" guard would permanently lock the // button. ResetInstallState() (called from OnEnable + OnDestroy) keeps the contract. private AddRequest? addRequest; private IDisposable? _stopSubscription; /// /// Shows the update popup window with version information. /// public static UpdatePopupWindow ShowWindow(string currentVersion, string latestVersion) { var window = GetWindow(utility: false, "Update Available", focus: true); window.currentVersion = currentVersion ?? "Unknown"; window.latestVersion = latestVersion ?? "Unknown"; CenterAndSizeWindow( window: window, width: 350f, height: 450f, minWidth: 350f, minHeight: 450f, maxWidth: 350f, maxHeight: 450f); window.CreateGUI(); window.Show(); window.Focus(); return window; } protected override void OnEnable() { base.OnEnable(); ResetInstallState(); } protected override void BindUI(VisualElement root) { // Set icon var iconContainer = root.Q("icon-container"); if (iconContainer == null) throw new InvalidOperationException("icon-container VisualElement not found in UXML"); var icon = EditorAssetLoader.LoadAssetAtPath(LogoIconPaths); if (icon == null) throw new InvalidOperationException("Logo icon not found in specified paths"); iconContainer.style.backgroundImage = new StyleBackground(icon); // Set version labels var currentVersionLabel = root.Q