using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; using BaseGames.Editor.Modules; namespace BaseGames.Editor { /// /// 数据管理总枢纽窗口(DataHub)。 /// 布局:导航侧边栏(120px) | TwoPaneSplitView → 列表区(220px) + 详情区(flex)。 /// 菜单:BaseGames / Data Hub (priority=50) /// public class DataHubWindow : EditorWindow { private const string UssPath = "Assets/_Game/Scripts/Editor/UIToolkit/Editor.uss"; private const string PrefKey = "DataHub.ActiveModuleId"; private const float NavWidth = 120f; private const float ListWidth = 220f; private const float MinWinWidth = 680f; private const float MinWinHeight = 420f; [MenuItem("BaseGames/Data Hub", priority = 50)] public static void Open() { var wnd = GetWindow(); wnd.titleContent = new GUIContent("Data Hub", EditorGUIUtility.IconContent("d_ScriptableObject Icon").image); wnd.minSize = new Vector2(MinWinWidth, MinWinHeight); } // ── 状态 ───────────────────────────────────────────────────────────── private readonly List _modules = new(); private readonly HashSet _initializedIds = new(); private IDataModule _activeModule; private VisualElement _navSidebar; // NavBar 搜索:按 DisplayName 过滤可见模块 private string _navFilter = ""; private readonly Dictionary _navButtons = new(); // 缓存:列表区和详情区引用(由 TwoPaneSplitView 子节点提供) private VisualElement _listWrapper; private VisualElement _detailWrapper; // 当前选中资产 private UnityEngine.Object _selected; // ── 生命周期 ────────────────────────────────────────────────────────── public void CreateGUI() { // USS var uss = AssetDatabase.LoadAssetAtPath(UssPath); if (uss != null) rootVisualElement.styleSheets.Add(uss); // 注册模块 RegisterModules(); // 构建 UI BuildLayout(); // 恢复上次激活的模块 string savedId = EditorPrefs.GetString(PrefKey, string.Empty); var toActivate = _modules.Find(m => m.ModuleId == savedId) ?? _modules.FirstOrDefault(); if (toActivate != null) ActivateModule(toActivate); } // ── 模块注册 ────────────────────────────────────────────────────────── private void RegisterModules() { _modules.Clear(); // 自动发现所有实现 IDataModule 的非抽象类型(排除接口/抽象类本身) // TypeCache 仅在 UnityEditor 中可用,零运行时开销 var moduleTypes = UnityEditor.TypeCache.GetTypesDerivedFrom(); var instances = new List(moduleTypes.Count); foreach (var t in moduleTypes) { if (t.IsAbstract || t.IsInterface) continue; try { instances.Add((IDataModule)Activator.CreateInstance(t)); } catch (Exception ex) { Debug.LogWarning($"[DataHubWindow] 无法实例化模块 {t.Name}: {ex.Message}"); } } // 按 DisplayOrder 升序排列(若模块实现了 IDataModuleOrdered 则使用其值,否则默认 0) instances.Sort((a, b) => { int oa = a is IDataModuleOrdered ao ? ao.DisplayOrder : 0; int ob = b is IDataModuleOrdered bo ? bo.DisplayOrder : 0; int c = oa.CompareTo(ob); return c != 0 ? c : string.Compare(a.DisplayName, b.DisplayName, StringComparison.OrdinalIgnoreCase); }); _modules.AddRange(instances); } // ── 布局 ───────────────────────────────────────────────────────────── private void BuildLayout() { var root = rootVisualElement; root.style.flexDirection = FlexDirection.Row; root.style.flexGrow = 1; // 导航侧边栏 _navSidebar = BuildNavSidebar(); root.Add(_navSidebar); // 垂直分隔线 var divider = new VisualElement(); divider.style.width = 1; divider.style.backgroundColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.25f)); root.Add(divider); // TwoPaneSplitView(列表 + 详情) var split = new TwoPaneSplitView(0, ListWidth, TwoPaneSplitViewOrientation.Horizontal); split.style.flexGrow = 1; root.Add(split); // 列表区容器 _listWrapper = new VisualElement(); _listWrapper.style.flexGrow = 1; _listWrapper.style.overflow = Overflow.Hidden; split.Add(_listWrapper); // 详情区:外层 ScrollView 提供滚动视口,_detailWrapper 是内容容器(自然高度) var detailScroll = new ScrollView(ScrollViewMode.Vertical); detailScroll.style.flexGrow = 1; detailScroll.style.overflow = Overflow.Hidden; split.Add(detailScroll); _detailWrapper = new VisualElement(); _detailWrapper.style.flexDirection = FlexDirection.Column; _detailWrapper.style.paddingBottom = 16; detailScroll.Add(_detailWrapper); } private VisualElement BuildNavSidebar() { var sidebar = new VisualElement(); sidebar.style.width = NavWidth; sidebar.style.flexShrink = 0; sidebar.style.flexDirection = FlexDirection.Column; sidebar.style.paddingTop = 8; // 标题 var title = new Label("DATA HUB"); title.style.fontSize = 10; title.style.opacity = 0.5f; title.style.paddingLeft = 10; title.style.marginBottom = 4; title.style.unityFontStyleAndWeight = FontStyle.Bold; sidebar.Add(title); // 搜索框 var searchField = new TextField(); searchField.style.marginLeft = 6; searchField.style.marginRight = 6; searchField.style.marginBottom = 6; searchField.tooltip = "按模块名称过滤"; // 自定义占位符效果 if (string.IsNullOrEmpty(searchField.value)) searchField.value = ""; searchField.RegisterValueChangedCallback(evt => { _navFilter = evt.newValue ?? ""; ApplyNavFilter(); }); sidebar.Add(searchField); _navButtons.Clear(); foreach (var module in _modules) { var btn = BuildNavItem(module); _navButtons[module.ModuleId] = btn; sidebar.Add(btn); } // 弹性填充 var spacer = new VisualElement(); spacer.style.flexGrow = 1; sidebar.Add(spacer); return sidebar; } private void ApplyNavFilter() { foreach (var module in _modules) { if (!_navButtons.TryGetValue(module.ModuleId, out var btn)) continue; bool visible = string.IsNullOrEmpty(_navFilter) || module.DisplayName.IndexOf(_navFilter, System.StringComparison.OrdinalIgnoreCase) >= 0 || module.ModuleId.IndexOf(_navFilter, System.StringComparison.OrdinalIgnoreCase) >= 0; btn.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None; } } private Button BuildNavItem(IDataModule module) { var btn = new Button(() => ActivateModule(module)); btn.name = "nav-" + module.ModuleId; btn.style.flexDirection = FlexDirection.Row; btn.style.alignItems = Align.Center; btn.style.paddingLeft = 10; btn.style.paddingRight = 8; btn.style.paddingTop = 8; btn.style.paddingBottom = 8; btn.style.borderTopLeftRadius = 0; btn.style.borderTopRightRadius = 0; btn.style.borderBottomLeftRadius = 0; btn.style.borderBottomRightRadius = 0; btn.style.borderLeftWidth = 0; btn.style.borderRightWidth = 0; btn.style.borderTopWidth = 0; btn.style.borderBottomWidth = 0; btn.style.backgroundColor = new StyleColor(Color.clear); btn.style.marginBottom = 2; // 图标(容错:图标名无效时跳过,不报错) if (!string.IsNullOrEmpty(module.IconName)) { var content = EditorGUIUtility.IconContent(module.IconName); if (content?.image != null) { var icon = new Image { image = content.image }; icon.style.width = 16; icon.style.height = 16; icon.style.marginRight = 6; btn.Add(icon); } } var label = new Label(module.DisplayName); label.style.flexGrow = 1; btn.Add(label); return btn; } // ── 模块切换 ────────────────────────────────────────────────────────── private void ActivateModule(IDataModule module) { if (_activeModule == module) return; _activeModule = module; _selected = null; // 更新导航项视觉状态 foreach (var m in _modules) { var navBtn = _navSidebar.Q