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