Files
XericUIActionVessel/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorManager.cs
T
2026-06-21 22:33:13 +08:00

441 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.Pool;
using Object = UnityEngine.Object;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 节点下生成反射属性管理器
/// </summary>
/// <remarks>
/// 管理器需要挂载在 Unity 节点中,管理器中指定的属性承载节点将用于生成属性内容。
///
/// 新的生命周期流程:
/// 1. BuildEntries(object payload) — 传入数据对象,构建 Entry 列表
/// 2. OnPostBuildEntries(entries) — 虚方法,可重写添加额外 Entry
/// 3. 自动按 FieldOrder 排序
/// 4. GenerateUI() — 根据 Entry 列表生成 UI 实例并绑定
///
/// 对象池说明:
/// - Entry 通过 XuiFieldTempEntry.Pool 静态池管理
/// - UI 预制体通过内部 PropertyGroupPools 字典管理,以 PropertyGroupScriptableObject 为键
/// </remarks>
public class XuiavrgInspectorManager : MonoBehaviour
{
#region 对象池
/// <summary>
/// UI 预制体对象池,以 PropertyGroupScriptableObject 为键
/// </summary>
private Dictionary<XuiavrgPropertyGroupScriptableObject, ObjectPool<GameObject>> _propertyGroupPools
= new Dictionary<XuiavrgPropertyGroupScriptableObject, ObjectPool<GameObject>>();
/// <summary>
/// 从指定 PropertyGroup 的预制体池获取 UI 实例
/// </summary>
public GameObject PropertyGroupPoolGet(XuiavrgPropertyGroupScriptableObject group)
{
if (group == null) return null;
if (!_propertyGroupPools.TryGetValue(group, out var pool))
{
var prefab = group.prefab;
pool = new ObjectPool<GameObject>(
createFunc: () =>
prefab != null
? Object.Instantiate(prefab, transform)
: new GameObject("PropertyItem", typeof(RectTransform)),
actionOnGet: go => go.SetActive(true),
actionOnRelease: go => go.SetActive(false),
actionOnDestroy: go =>
{
if (go) Destroy(go);
},
collectionCheck: false,
defaultCapacity: 4,
maxSize: 64
);
_propertyGroupPools[group] = pool;
}
return pool.Get();
}
/// <summary>
/// 将 UI 实例归还到指定 PropertyGroup 的预制体池
/// </summary>
public void PropertyGroupPoolRelease(XuiavrgPropertyGroupScriptableObject group, GameObject instance)
{
if (group == null || instance == null) return;
if (_propertyGroupPools.TryGetValue(group, out var pool))
{
pool.Release(instance);
}
else
{
if (instance) Destroy(instance);
}
}
/// <summary>
/// 清空所有预制体对象池
/// </summary>
public void ClearAllPropertyGroupPools()
{
foreach (var pool in _propertyGroupPools.Values)
{
pool.Clear();
}
_propertyGroupPools.Clear();
}
#endregion
#region 字段
[SerializeField]
[Tooltip("当前使用的 UI 主题名称。切换主题会立即销毁所有已生成 UI 和对象池,需重新 BuildEntries + GenerateUI")]
private string _currentTheme = XuiavrgPropertyGroupScriptableObject.DefaultThemeName;
/// <summary>
/// 当前 UI 主题名称
/// </summary>
public string CurrentTheme => _currentTheme;
/// <summary>
/// 当前活跃的 Entry 列表
/// </summary>
public List<IFieldEntry> CurrentEntries { get; private set; } = new List<IFieldEntry>();
/// <summary>
/// 当前传入的数据负载
/// </summary>
public object CurrentPayload { get; private set; }
/// <summary>
/// 切换 UI 主题。会立即销毁所有已生成 UI 和对象池。
/// 调用后需重新 BuildEntries + GenerateUI。
/// </summary>
/// <param name="themeName">新的主题名称</param>
public void SetTheme(string themeName)
{
if (_currentTheme == themeName) return;
_currentTheme = string.IsNullOrEmpty(themeName)
? XuiavrgPropertyGroupScriptableObject.DefaultThemeName
: themeName;
// 销毁所有已生成 UI 和对象池
ClearAll();
}
#endregion
#region 核心生命周期
/// <summary>
/// 传入对象实例,构建 Entry 列表
/// </summary>
/// <param name="payload">数据端对象</param>
public void BuildEntries(object payload)
{
// 清空之前的 Entry
ClearCurrentEntries();
CurrentPayload = payload;
if (payload == null)
{
Debug.LogError("[Xuiavrg] 未指定有效负载,无法构建 Entry");
return;
}
var type = payload.GetType();
var memberInfos = type.GetRuntimeMemberInfo().ToList();
// 用于合并编组 Entry 的临时字典
var groupEntries = new Dictionary<string, XuiFieldTempEntry>();
foreach (var memberInfo in memberInfos)
{
// 检查隐藏标记
var hideAttr = memberInfo.GetCustomAttribute<XuiavrgHideAttribute>();
if (hideAttr != null && hideAttr.Hide)
continue;
// 检查编组标记
var groupAttr = memberInfo.GetCustomAttribute<XuiavrgFieldEntryGroupAttribute>();
if (groupAttr != null)
{
// 编组字段:合并到同名 Entry
if (!groupEntries.TryGetValue(groupAttr.GroupPath, out var groupEntry))
{
groupEntry = XuiFieldTempEntry.Get();
groupEntry.Initialize(groupAttr.GroupPath);
groupEntries[groupAttr.GroupPath] = groupEntry;
CurrentEntries.Add(groupEntry);
}
groupEntry.RegisterBinding(
memberInfo.Name, memberInfo, payload,
groupAttr.Tag, groupAttr.TagIndex);
}
else
{
// 普通字段:创建独立 Entry
var entry = XuiFieldTempEntry.Get();
entry.Initialize(memberInfo.Name);
entry.RegisterBinding(memberInfo.Name, memberInfo, payload);
// 检查预制体覆盖
var overrideAttr = memberInfo.GetCustomAttribute<XuiavrgFieldPrefabOverrideAttribute>();
if (overrideAttr != null)
{
entry.PrefabOverrideName = overrideAttr.PrefabGroupName;
}
CurrentEntries.Add(entry);
}
// 应用排序标记
var orderAttr = memberInfo.GetCustomAttribute<XuiavrgFieldOrderAttribute>();
if (orderAttr != null)
{
// 找到该成员对应的 Entry(可能是编组 Entry)
if (groupAttr != null && groupEntries.TryGetValue(groupAttr.GroupPath, out var gEntry))
{
if (gEntry.FieldOrder == 0 || orderAttr.Order < gEntry.FieldOrder)
gEntry.FieldOrder = orderAttr.Order;
}
else
{
// 普通字段的排序应用到其 Entry
var normalEntry = CurrentEntries.LastOrDefault();
if (normalEntry != null)
normalEntry.FieldOrder = orderAttr.Order;
}
}
// 应用旧的 IXuiavrgAttribute(兼容)
ApplyLegacyAttributes(memberInfo, payload, groupAttr, groupEntries);
}
// 调用后期回调
OnPostBuildEntries(CurrentEntries);
// 排序
SortEntries();
}
/// <summary>
/// 应用旧的 IXuiavrgAttribute 体系中的属性(兼容保留)
/// </summary>
private void ApplyLegacyAttributes(
MemberInfo memberInfo, object payload,
XuiavrgFieldEntryGroupAttribute groupAttr,
Dictionary<string, XuiFieldTempEntry> groupEntries)
{
// 处理旧的 XuiavrgAttribute(设置 labelName, inspectorOrder 等)
var oldAttr = memberInfo.GetCustomAttribute<XuiavrgAttribute>();
if (oldAttr != null)
{
// 创建临时的 InspectorProperty 来接收绑定
var tempProp = new XuiavrgInspectorProperty();
tempProp.RegsiterDataValue(memberInfo, payload);
oldAttr.BindProperty(tempProp);
// 将相关属性同步到 Entry
IFieldEntry targetEntry;
if (groupAttr != null && groupEntries.TryGetValue(groupAttr.GroupPath, out var gEntry))
targetEntry = gEntry;
else
targetEntry = CurrentEntries.LastOrDefault(e => !e.IsGroupEntry || e == null) ?? CurrentEntries.LastOrDefault();
if (targetEntry != null)
{
if (tempProp.inspectorOrder != 0 && targetEntry.FieldOrder == 0)
targetEntry.FieldOrder = tempProp.inspectorOrder;
targetEntry.PrefabOverrideName ??= tempProp.uiPropertyName;
}
}
// 处理其他旧的验证 Attribute
foreach (var attr in memberInfo.GetCustomAttributes().OfType<IXuiavrgAttribute>())
{
if (attr is XuiavrgAttribute) continue; // 已处理
if (attr is XuiavrgHideAttribute) continue; // 已处理
if (attr is XuiavrgFieldEntryGroupAttribute) continue; // 已处理
if (attr is XuiavrgFieldOrderAttribute) continue; // 已处理
if (attr is XuiavrgFieldPrefabOverrideAttribute) continue; // 已处理
// 其他旧 Attribute(Required, StringCheck, NumberCheck 等)
var tempProp = new XuiavrgInspectorProperty();
tempProp.RegsiterDataValue(memberInfo, payload);
attr.BindProperty(tempProp);
// 这些验证信息目前由 InspectorProperty 保留,后续可迁移到 Entry
}
}
/// <summary>
/// 生成后期回调(可重写)
/// 在此方法中可以手动添加额外的 Entry(如 Preview 结构、不在数据对象中的内容)
/// </summary>
/// <param name="entries">当前 Entry 列表</param>
protected virtual void OnPostBuildEntries(List<IFieldEntry> entries)
{
// 默认无操作,子类可重写
}
/// <summary>
/// 对 Entry 列表按 FieldOrder 排序
/// </summary>
private void SortEntries()
{
CurrentEntries.Sort((a, b) =>
{
int orderCompare = a.FieldOrder.CompareTo(b.FieldOrder);
if (orderCompare != 0) return orderCompare;
return string.Compare(a.EntryName, b.EntryName, StringComparison.Ordinal);
});
}
/// <summary>
/// 根据 Entry 列表生成 UI
/// </summary>
public void GenerateUI()
{
if (CurrentEntries == null || CurrentEntries.Count == 0)
{
Debug.LogWarning("[Xuiavrg] 没有可生成的 Entry,请先调用 BuildEntries");
return;
}
foreach (var entry in CurrentEntries)
{
if (entry is not XuiFieldTempEntry tempEntry) continue;
// 查找匹配的 PropertyGroup
var group = ResolvePropertyGroup(entry);
if (group == null)
{
Debug.LogWarning($"[Xuiavrg] 未找到 Entry '{entry.EntryName}' 的匹配预制体组,跳过");
continue;
}
entry.BoundPrefabGroup = group;
// 从对象池获取 UI 实例
var uiInstance = PropertyGroupPoolGet(group);
if (uiInstance == null) continue;
// 设置父节点和命名
uiInstance.name = entry.EntryName;
uiInstance.transform.SetParent(transform, false);
// 绑定 UI 组件(首次绑定不传缓存,由 Entry 内部收集)
entry.BindUIComponent(uiInstance, null);
// 绑定事件
entry.OnEntryBind();
}
}
/// <summary>
/// 解析 Entry 对应的 PropertyGroup
/// 优先使用 PrefabOverrideName,否则按 Tag 匹配,所有查找在 currentTheme 下进行
/// </summary>
private XuiavrgPropertyGroupScriptableObject ResolvePropertyGroup(IFieldEntry entry)
{
// 1. 优先检查 PrefabOverrideName(按主题 + UI 名称查找)
if (!string.IsNullOrEmpty(entry.PrefabOverrideName))
{
var overrideGroup = XuiavrgPropertyGroupScriptableObject.FindGroupByUiName(_currentTheme, entry.PrefabOverrideName);
if (overrideGroup != null) return overrideGroup;
}
// 2. 按 Tag 查找匹配的组(在当前主题下)
if (entry.Tag != XuiavrgFieldTag.None)
{
var tags = new List<XuiavrgFieldTag> { entry.Tag };
// 收集子 Entry 的 Tag(如果是编组 Entry)
if (entry.IsGroupEntry)
{
foreach (var child in entry.Children)
{
if (child.Tag != XuiavrgFieldTag.None && !tags.Contains(child.Tag))
tags.Add(child.Tag);
}
}
var matchedGroup = XuiavrgPropertyGroupScriptableObject.FindGroup(_currentTheme, tags.ToArray());
if (matchedGroup != null) return matchedGroup;
}
// 3. 回退到当前主题下的 "Default" UI 名称
return XuiavrgPropertyGroupScriptableObject.FindGroupByUiName(_currentTheme, "Default");
}
/// <summary>
/// 清空当前界面和 Entry 列表
/// </summary>
public void ClearAll()
{
ClearCurrentEntries();
ClearAllPropertyGroupPools();
CurrentPayload = null;
}
/// <summary>
/// 清空当前 Entry 列表(归还到池中)
/// </summary>
private void ClearCurrentEntries()
{
foreach (var entry in CurrentEntries)
{
// 释放 UI 实例
if (entry.UIInstance != null && entry.BoundPrefabGroup != null)
{
PropertyGroupPoolRelease(entry.BoundPrefabGroup, entry.UIInstance);
}
// 归还 Entry 到池
if (entry is XuiFieldTempEntry tempEntry)
{
XuiFieldTempEntry.Release(tempEntry);
}
}
CurrentEntries.Clear();
}
#endregion
#region 生命周期
private void OnDestroy()
{
ClearAll();
}
#endregion
#region 兼容旧 API
/// <summary>
/// [已弃用] 请使用 BuildEntries + GenerateUI 替代
/// </summary>
[Obsolete("Use BuildEntries + GenerateUI instead", false)]
public void SchemaHydration(object payload)
{
BuildEntries(payload);
GenerateUI();
}
#endregion
}
}