/*
┌──────────────────────────────────────────────────────────────────┐
│ 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