using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace BaseGames.Editor
{
///
/// SO 资产总管理窗口 —— 浏览、搜索并在 Project 窗口定位项目中所有 ScriptableObject 资产。
///
/// 布局:顶部搜索栏 | 左侧分类列表 | 右侧资产列表(名称 / 类型 / 路径)
/// 功能:单击资产行 → Project 窗口 Ping 并选中;双击 → 同上并聚焦 Project 窗口。
/// 菜单:BaseGames / Tools / SO Manager (Priority 2)
///
public class SOManagerWindow : EditorWindow
{
private const string DataRoot = "Assets/_Game/Data";
private const string UssPath = "Assets/_Game/Scripts/Editor/UIToolkit/Editor.uss";
// ── 数据模型 ──────────────────────────────────────────────────────────
private sealed class CategoryEntry
{
public string Label;
public string Folder; // null = 全部
public int Count;
}
private sealed class AssetEntry
{
public string Name;
public string TypeName;
public string AssetPath;
public ScriptableObject Asset;
}
private readonly List _categories = new();
private readonly List _allAssets = new();
private readonly List _filtered = new();
private int _selectedCatIdx = 0;
private string _search = "";
// ── UI 引用 ───────────────────────────────────────────────────────────
private ListView _catList;
private ListView _assetList;
private TextField _searchField;
private Label _statusLabel;
// ── 菜单入口 ──────────────────────────────────────────────────────────
[MenuItem("BaseGames/Tools/SO Manager", priority = 2)]
public static void Open()
{
var wnd = GetWindow();
wnd.titleContent = new GUIContent("SO Manager",
EditorGUIUtility.IconContent("d_ScriptableObject Icon").image);
wnd.minSize = new Vector2(680, 420);
}
// ── 生命周期 ──────────────────────────────────────────────────────────
public void CreateGUI()
{
var uss = AssetDatabase.LoadAssetAtPath(UssPath);
if (uss != null) rootVisualElement.styleSheets.Add(uss);
BuildUI();
Refresh();
}
private void OnFocus() => Refresh();
// ── UI 构建 ───────────────────────────────────────────────────────────
private void BuildUI()
{
rootVisualElement.style.flexDirection = FlexDirection.Column;
// ─ 顶部工具栏 ──────────────────────────────────────────────────────
var toolbar = new VisualElement();
toolbar.style.flexDirection = FlexDirection.Row;
toolbar.style.paddingLeft = 8;
toolbar.style.paddingRight = 8;
toolbar.style.paddingTop = 5;
toolbar.style.paddingBottom = 5;
toolbar.style.borderBottomWidth = 1;
toolbar.style.borderBottomColor = new Color(0.15f, 0.15f, 0.15f);
toolbar.style.backgroundColor = new Color(0.22f, 0.22f, 0.22f, 0.6f);
var searchLbl = new Label("搜索:");
searchLbl.style.unityTextAlign = TextAnchor.MiddleLeft;
searchLbl.style.marginRight = 4;
_searchField = new TextField();
_searchField.style.flexGrow = 1;
_searchField.RegisterValueChangedCallback(e =>
{
_search = e.newValue;
ApplyFilter();
});
var refreshBtn = new Button(Refresh) { text = "↻ 刷新" };
refreshBtn.style.marginLeft = 8;
refreshBtn.style.width = 58;
toolbar.Add(searchLbl);
toolbar.Add(_searchField);
toolbar.Add(refreshBtn);
rootVisualElement.Add(toolbar);
// ─ 主体:两栏分割 ──────────────────────────────────────────────────
var split = new TwoPaneSplitView(0, 164, TwoPaneSplitViewOrientation.Horizontal);
split.style.flexGrow = 1;
// 左栏:分类列表 ────────────────────────────────────────────────────
var leftPane = new VisualElement();
leftPane.style.flexDirection = FlexDirection.Column;
leftPane.style.minWidth = 100;
var catHeader = new Label("分类");
catHeader.style.paddingLeft = 8;
catHeader.style.paddingTop = 5;
catHeader.style.paddingBottom = 5;
catHeader.style.unityFontStyleAndWeight = FontStyle.Bold;
catHeader.style.borderBottomWidth = 1;
catHeader.style.borderBottomColor = new Color(0.22f, 0.22f, 0.22f);
catHeader.style.backgroundColor = new Color(0.22f, 0.22f, 0.22f, 0.4f);
leftPane.Add(catHeader);
_catList = new ListView
{
makeItem = MakeCatItem,
bindItem = BindCatItem,
selectionType = SelectionType.Single,
fixedItemHeight = 26,
};
_catList.style.flexGrow = 1;
_catList.selectionChanged += _ =>
{
if (_catList.selectedIndex >= 0)
{
_selectedCatIdx = _catList.selectedIndex;
ApplyFilter();
}
};
leftPane.Add(_catList);
// 右栏:资产列表 ────────────────────────────────────────────────────
var rightPane = new VisualElement();
rightPane.style.flexDirection = FlexDirection.Column;
// 列标题行
var colHeader = new VisualElement();
colHeader.style.flexDirection = FlexDirection.Row;
colHeader.style.paddingLeft = 8;
colHeader.style.paddingRight = 8;
colHeader.style.paddingTop = 4;
colHeader.style.paddingBottom = 4;
colHeader.style.borderBottomWidth = 1;
colHeader.style.borderBottomColor = new Color(0.22f, 0.22f, 0.22f);
colHeader.style.backgroundColor = new Color(0.22f, 0.22f, 0.22f, 0.4f);
colHeader.Add(MakeHeaderLabel("资产名", true, 0));
colHeader.Add(MakeHeaderLabel("类型", false, 170));
colHeader.Add(MakeHeaderLabel("路径", true, 0));
rightPane.Add(colHeader);
_assetList = new ListView
{
makeItem = MakeAssetRow,
bindItem = BindAssetRow,
selectionType = SelectionType.Single,
fixedItemHeight = 22,
};
_assetList.style.flexGrow = 1;
_assetList.selectionChanged += _ => OnAssetPicked();
_assetList.itemsChosen += _ => FocusProjectWindow();
rightPane.Add(_assetList);
// 状态栏
_statusLabel = new Label("—");
_statusLabel.style.paddingLeft = 8;
_statusLabel.style.paddingTop = 3;
_statusLabel.style.paddingBottom = 3;
_statusLabel.style.borderTopWidth = 1;
_statusLabel.style.borderTopColor = new Color(0.15f, 0.15f, 0.15f);
_statusLabel.style.color = new Color(0.58f, 0.58f, 0.58f);
_statusLabel.style.fontSize = 11;
rightPane.Add(_statusLabel);
split.Add(leftPane);
split.Add(rightPane);
rootVisualElement.Add(split);
}
private static Label MakeHeaderLabel(string text, bool grow, int fixedWidth)
{
var lbl = new Label(text);
lbl.style.unityFontStyleAndWeight = FontStyle.Bold;
lbl.style.overflow = Overflow.Hidden;
if (grow) lbl.style.flexGrow = 1;
if (fixedWidth > 0) lbl.style.width = fixedWidth;
return lbl;
}
// ── 分类列表项 ────────────────────────────────────────────────────────
private static VisualElement MakeCatItem()
{
var lbl = new Label();
lbl.style.paddingLeft = 10;
lbl.style.paddingRight = 6;
lbl.style.overflow = Overflow.Hidden;
lbl.style.unityTextAlign = TextAnchor.MiddleLeft;
return lbl;
}
private void BindCatItem(VisualElement el, int i)
{
if (i >= _categories.Count) return;
var cat = _categories[i];
((Label)el).text = $"{cat.Label} ({cat.Count})";
}
// ── 资产列表项 ────────────────────────────────────────────────────────
private static VisualElement MakeAssetRow()
{
var row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center;
row.style.paddingLeft = 8;
row.style.paddingRight = 8;
var nameEl = new Label { name = "n" };
nameEl.style.flexGrow = 1;
nameEl.style.overflow = Overflow.Hidden;
nameEl.style.textOverflow = TextOverflow.Ellipsis;
var typeEl = new Label { name = "t" };
typeEl.style.width = 170;
typeEl.style.overflow = Overflow.Hidden;
typeEl.style.textOverflow = TextOverflow.Ellipsis;
typeEl.style.color = new Color(0.52f, 0.80f, 1.00f);
typeEl.style.fontSize = 11;
var pathEl = new Label { name = "p" };
pathEl.style.flexGrow = 1;
pathEl.style.overflow = Overflow.Hidden;
pathEl.style.textOverflow = TextOverflow.Ellipsis;
pathEl.style.color = new Color(0.48f, 0.48f, 0.48f);
pathEl.style.fontSize = 10;
row.Add(nameEl);
row.Add(typeEl);
row.Add(pathEl);
return row;
}
private void BindAssetRow(VisualElement el, int i)
{
if (i >= _filtered.Count) return;
var e = _filtered[i];
el.Q