/* ┌──────────────────────────────────────────────────────────────────┐ │ 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 System.Collections.Generic; using System.Linq; using com.IvanMurzak.McpPlugin; using com.IvanMurzak.Unity.MCP.Editor.Utils; using Microsoft.Extensions.Logging; using R3; using UnityEditor; using UnityEngine.UIElements; namespace com.IvanMurzak.Unity.MCP.Editor.UI { /// /// Filter type for MCP list windows (Tools, Prompts, Resources). /// public enum McpFilterType { All, Enabled, Disabled } /// /// Base class for MCP list windows providing common ListView functionality. /// /// The view model type for list items. public abstract class McpListWindowBase : McpWindowBase where TViewModel : class, IMcpItemViewModel { private const string FilterStatsFormat = "Filtered: {0}, Total: {1}"; protected abstract string[] ItemUxmlPaths { get; } protected abstract string MissingTemplateMessage { get; } protected readonly CompositeDisposable _disposables = new(); private IDisposable? _innerSubscription; protected VisualTreeAsset? itemTemplate; protected List allItems = new(); protected ListView? listView; protected Label? emptyListLabel; protected TextField? filterField; protected DropdownField? typeDropdown; protected Label? filterStatsLabel; public override void CreateGUI() { rootVisualElement.Clear(); _disposables.Clear(); InitializePlugin(); var visualTree = EditorAssetLoader.LoadAssetAtPath(WindowUxmlPaths, Logger); if (visualTree == null) return; visualTree.CloneTree(rootVisualElement); ApplyStyleSheets(rootVisualElement); itemTemplate = EditorAssetLoader.LoadAssetAtPath(ItemUxmlPaths, Logger); InitializeFilters(rootVisualElement); RefreshItems(); PopulateList(); SubscribeToUpdates(); } protected virtual void OnDisable() { _innerSubscription?.Dispose(); _innerSubscription = null; _disposables.Clear(); } protected virtual Observable? GetOnUpdatedObservable(IMcpPlugin plugin) => null; private void SubscribeToUpdates() { UnityMcpPluginEditor.PluginProperty .Subscribe(plugin => { _innerSubscription?.Dispose(); _innerSubscription = null; if (plugin == null) return; _innerSubscription = GetOnUpdatedObservable(plugin)? .ObserveOnCurrentSynchronizationContext() .Subscribe(_ => { RefreshItems(); PopulateList(); }); }).AddTo(_disposables); } protected void InitializePlugin() { UnityMcpPluginEditor.InitSingletonIfNeeded(); UnityMcpPluginEditor.Instance.BuildMcpPluginIfNeeded(); UnityMcpPluginEditor.Instance.AddUnityLogCollectorIfNeeded(() => new BufferedFileLogStorage()); } protected void InitializeFilters(VisualElement root) { filterField = root.Q("filter-textfield"); if (filterField != null) filterField.RegisterValueChangedCallback(evt => PopulateList()); typeDropdown = root.Q("type-dropdown"); if (typeDropdown != null) { typeDropdown.choices = Enum.GetNames(typeof(McpFilterType)).ToList(); typeDropdown.index = (int)McpFilterType.All; typeDropdown.RegisterValueChangedCallback(evt => PopulateList()); } filterStatsLabel = root.Q