Files
DuckovMods/SceneView/TreeViewNode.cs
2025-11-18 18:45:14 +08:00

437 lines
18 KiB
C#
Raw Permalink 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;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
namespace SceneView
{
public class TreeViewNode : MonoBehaviour
{
public TreeViewNode? parent;
public RectTransform? rectTransform;
public TextMeshProUGUI? text;
public Button? label;
public GameObject? child;
public RectTransform? childRectTransform;
public VerticalLayoutGroup? verticalLayout;
public Button? objectEnable;
public TextMeshProUGUI? objectText;
public float buttonHeight = 50;
private string originalText = "";
private GameObject? _targetGameObject; // 保存当前节点代表的GameObject
private bool _childrenGenerated = false; // 标记子节点和组件是否已经生成
private int _nodeDepth = 0; // 当前节点的深度
private void Awake()
{
CreateUI();
}
[ContextMenu("CreateUI")]
public void CreateUI()
{
// 获取 RectTransform 组件,如果不存在则添加
if (rectTransform == null)
{
rectTransform = GetComponent<RectTransform>();
if (rectTransform == null) rectTransform = gameObject.AddComponent<RectTransform>();
if (rectTransform == null)
{
Debug.LogError("Failed to add RectTransform to the GameObject.");
return;
}
}
rectTransform.sizeDelta = new Vector2(0, buttonHeight);
rectTransform.anchorMin = Vector2.up;
rectTransform.anchorMax = Vector2.one;
rectTransform.pivot = new Vector2(0.5f, 1); // 确保轴心点在顶部中央,便于布局
var buttonConfig = ButtonConfig.Default;
buttonConfig.RectConfig.Pivot = new Vector2(0.5f, 1);
buttonConfig.RectConfig.AnchorMin = new Vector2(0, 1);
buttonConfig.RectConfig.AnchorMax = new Vector2(1, 1);
buttonConfig.RectConfig.SizeDelta = new Vector2(0, buttonHeight);
buttonConfig.BackgroundColor = new Color(0.2f, 0.2f, 0.2f, 1f); // 默认背景色
// 创建按钮并检查是否已存在
if (label == null)
{
var button = ControlUtilities.CreateButton(rectTransform, buttonConfig, null);
label = button.button;
text = button.text;
if (text == null)
{
Debug.LogError("Failed to get button text.");
return;
}
text.alignment = TextAlignmentOptions.MidlineLeft;
text.color = Color.white; // 确保文本颜色可见
}
if (objectEnable == null)
{
var button = ControlUtilities.CreateButton(rectTransform, buttonConfig, null);
objectEnable = button.button;
objectText = button.text;
objectText.text = ""; // 默认文本为空,使用颜色作指示
objectText.color = Color.black; // 确保文本颜色可见
button.button.image.color = Color.yellow; // 默认颜色
var rect = objectEnable.GetComponent<RectTransform>();
rect.anchorMin = Vector2.one;
rect.anchorMax = Vector2.one;
rect.pivot = new Vector2(1, 1); // 右上角锚点
rect.sizeDelta = new Vector2(30, 30);
rect.anchoredPosition = new Vector2(-10, -10); // 距离右上角10像素
}
// 检查子对象是否存在
child = transform.Find("content")?.gameObject;
if (child == null)
{
child = new GameObject("content");
child.transform.SetParent(rectTransform, false);
if (child == null)
{
Debug.LogError("Failed to create or set parent for child content.");
return;
}
childRectTransform = child.AddComponent<RectTransform>();
if (childRectTransform == null)
{
Debug.LogError("Failed to add RectTransform to the child content.");
return;
}
}
else
{
childRectTransform = child.GetComponent<RectTransform>();
if (childRectTransform == null)
{
Debug.LogError("Failed to get RectTransform for the child content.");
return;
}
}
// 确保子内容一开始是隐藏的 (懒加载的初始状态)
child.SetActive(false);
childRectTransform.anchorMin = new Vector2(0, 1);
childRectTransform.anchorMax = new Vector2(1, 1);
childRectTransform.pivot = new Vector2(0.5f, 1);
childRectTransform.offsetMax = new Vector2(0, -buttonHeight); // 占据主按钮以下的空间
childRectTransform.offsetMin = new Vector2(0, 0); // 确保底部没有额外偏移
verticalLayout = child.GetComponent<VerticalLayoutGroup>();
var sizeFitter = child.GetComponent<ContentSizeFitter>();
if (verticalLayout == null)
{
verticalLayout = child.AddComponent<VerticalLayoutGroup>();
if (verticalLayout == null)
{
Debug.LogError("Failed to add VerticalLayoutGroup to the child content.");
return;
}
}
if (sizeFitter == null)
{
sizeFitter = child.AddComponent<ContentSizeFitter>();
if (sizeFitter == null)
{
Debug.LogError("Failed to add ContentSizeFitter to the child content.");
return;
}
}
// 调整布局组参数,确保正确计算
verticalLayout.padding.left = (int)(buttonHeight * 0.5f); // 增加左边距以缩进
verticalLayout.padding.top = 2;
verticalLayout.padding.bottom = 4;
verticalLayout.spacing = 2;
verticalLayout.childControlHeight = false; // 由ContentSizeFitter控制子节点高度
verticalLayout.childControlWidth = true; // 子节点宽度按父级宽度自适应
verticalLayout.childForceExpandHeight = false; // 不要强制展开高度
verticalLayout.childAlignment = TextAnchor.UpperLeft; // 确保子元素从上开始排列
sizeFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
}
// 修改为只初始化当前节点,不立即生成子内容
public IEnumerator InitializeNode(GameObject? targetObject, int depth = 0)
{
_targetGameObject = targetObject;
_nodeDepth = depth;
// 清除之前的监听器,避免重复
label?.onClick.RemoveAllListeners();
objectEnable?.onClick.RemoveAllListeners();
if (targetObject == null) // 代表这是场景根节点
{
originalText = $"场景 {SceneManager.GetActiveScene().name}";
if (text) text.text = originalText + " →"; // 默认收拢,显示右箭头
label?.onClick.AddListener(OnExpand); // 场景节点点击展开自身
objectEnable?.gameObject.SetActive(false); // 场景节点没有Enable/Disable功能
yield break; // 场景节点不立即生成根对象,等待点击展开
}
// 过滤掉特定Canvas
if (targetObject == null || targetObject.name == CanvasControl.ViewCanvasName)
{
yield break;
}
originalText = GetIndentedName(targetObject.name, depth);
if (text != null) text.text = originalText + " →"; // 默认收拢,显示右箭头
// 注册展开事件
label?.onClick.AddListener(OnExpand);
// 注册对象启用/禁用事件
objectEnable?.gameObject.SetActive(true);
objectEnable?.onClick.AddListener(() => OnObjectEnable(_targetGameObject, objectEnable, objectText));
UpdateObjButton(_targetGameObject, objectEnable, objectText);
// 确保子内容一开始是隐藏的
if (child) child.SetActive(false);
yield return StartCoroutine(DelayedUpdateHeight()); // 初始化时异步更新高度
}
// 修改 OnExpand 方法以实现懒加载
private void OnExpand()
{
if (child == null)
{
Debug.LogError("Child content not found.");
return;
}
CanvasControl.FocusGameObject=_targetGameObject;
// 如果已经生成,直接切换显示状态
if (_childrenGenerated)
{
ToggleExpansionVisuals();
StartCoroutine(DelayedUpdateHeight()); // 每次展开/收拢后异步更新高度
}
else // 如果子内容未生成,则异步生成
{
ToggleExpansionVisuals(); // 生成完成后再切换显示状态 (展开或根据后续逻辑)
// 在生成子节点之前,确保子内容容器是激活的
child.SetActive(true); // <--- 新增这行,在生成子节点之前激活容器
StartCoroutine(GenerateChildrenContent(_targetGameObject, _nodeDepth + 1, () =>
{
_childrenGenerated = true;
// 注意ToggleExpansionVisuals 会再次调用 child.SetActive()
// 如果是展开状态,它会保持 true。如果是收拢则设置为 false。
StartCoroutine(DelayedUpdateHeight()); // 生成并展开后异步更新高度
}));
}
}
// 新增方法生成子内容组件和子GameObject
private IEnumerator GenerateChildrenContent(GameObject? targetObject, int depth, Action? onComplete = null)
{
ClearChildNodes(); // 在生成之前清空所有旧的子节点
if (targetObject == null) // 场景根节点需要遍历根GameObject
{
var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects();
foreach (var rootObject in rootObjects)
{
if (!rootObject || rootObject.name == CanvasControl.ViewCanvasName) continue;
var childNodeGO = new GameObject(rootObject.name);
var childNode = childNodeGO.AddComponent<TreeViewNode>();
childNode.transform.SetParent(childRectTransform, false);
childNode.parent = this;
childNode.CreateUI(); // 确保UI元素已创建
// yield return StartCoroutine(childNode.InitializeNode(rootObject, depth)); // 浅初始化子节点
yield return null; // 每处理一个子节点就yield一次分散帧压力
}
onComplete?.Invoke();
yield break;
}
// 生成当前GameObject的组件按钮
var components = targetObject.GetComponents<Behaviour>();
foreach (var component in components)
{
if (childRectTransform == null)
{
Debug.LogError("childRectTransform is null");
continue;
}
var config = ButtonConfig.Default;
config.RectConfig.SizeDelta = new Vector2(0, 40);
config.BackgroundColor = component.enabled ? new Color(0.2f, 0.4f, 0.2f, 1f) : new Color(0.4f, 0.2f, 0.2f, 1f); // 根据组件启用状态设置颜色
config.Text = GetIndentedName(component.GetType().Name, depth, true); // 组件也需要缩进
config.TextColor = Color.white;
var buttonBack = ControlUtilities.CreateButton(childRectTransform, config, null);
if (buttonBack.button == null)
{
Debug.LogError("buttonBack.button is null");
continue;
}
var button = buttonBack.button;
var comp = component;
button.onClick.AddListener(() => OnButtonClick(button, comp));
yield return null; // 每创建一个组件按钮就yield一次
}
// 遍历目标对象的所有子对象并创建新的TreeViewNode浅初始化
foreach (Transform childTransform in targetObject.transform)
{
if (childTransform == null)
{
Debug.LogError("Child transform is null.");
continue;
}
var childNodeGO = new GameObject(childTransform.gameObject.name);
var childNode = childNodeGO.AddComponent<TreeViewNode>();
childNode.transform.SetParent(childRectTransform, false);
childNode.parent = this;
childNode.CreateUI(); // 确保UI元素已创建
// yield return StartCoroutine(childNode.InitializeNode(childTransform.gameObject, depth)); // 浅初始化子节点
yield return null; // 每处理一个子节点就yield一次
}
onComplete?.Invoke();
}
private void ToggleExpansionVisuals()
{
if (child == null) return;
// 切换 child 的激活状态
child.SetActive(!child.activeSelf);
if (text != null)
{
if (child.activeSelf)
{
text.text = originalText + " ↓"; // 下三角符号表示展开
}
else
{
text.text = originalText + " →"; // 右三角符号表示收拢
}
}
}
// 异步更新高度,给布局系统一帧时间来计算
private IEnumerator DelayedUpdateHeight()
{
// 等待一帧,让 LayoutGroup 和 ContentSizeFitter 完成计算
yield return null;
InternalUpdateHeight();
}
private void InternalUpdateHeight()
{
if (rectTransform == null || child == null || childRectTransform == null)
{
return; // Editor退出时可能为null
}
float currentChildHeight = 0f;
if (child.activeSelf && _childrenGenerated)
{
// 强制重建 childRectTransform 的布局,以获取准确的 PreferredHeight
LayoutRebuilder.ForceRebuildLayoutImmediate(childRectTransform);
currentChildHeight = LayoutUtility.GetPreferredHeight(childRectTransform);
}
// 设置主RectTransform的高度 = 按钮高度 + 子内容高度
rectTransform.sizeDelta = new Vector2(rectTransform.sizeDelta.x, buttonHeight + currentChildHeight);
// Debug.Log($"Node: {gameObject.name}, childActive: {child.activeSelf}, childrenGenerated: {_childrenGenerated}, currentChildHeight: {currentChildHeight}, finalNodeHeight: {rectTransform.sizeDelta.y}");
// 如果有父节点,通知父节点重新计算布局
if (parent != null)
{
// 强制重建父节点的 childRectTransform 的布局
LayoutRebuilder.ForceRebuildLayoutImmediate(parent.childRectTransform);
// 递归父节点异步更新高度
StartCoroutine(parent.DelayedUpdateHeight());
}
}
// 调整 GetIndentedName 以支持组件类型的缩进
private string GetIndentedName(string name, int depth, bool isComponent = false)
{
var indent = "";
int baseIndent = depth; // GameObject的深度
if (isComponent) baseIndent++; // 组件比GameObject多一级缩进
// 增加额外的左边距,以产生层级感
for (var i = 0; i < baseIndent; i++)
{
indent += " ";
}
return $"{indent}{name}";
}
public void ClearChildNodes()
{
if (childRectTransform != null)
{
// 遍历并销毁所有子对象
for (int i = childRectTransform.childCount - 1; i >= 0; i--)
{
Destroy(childRectTransform.GetChild(i).gameObject);
}
}
}
private void OnButtonClick(Button button, Behaviour component)
{
if (button == null || component == null) return;
// 切换组件的启用状态
var isEnabled = !component.enabled;
component.enabled = isEnabled;
// 根据组件的启用状态修改按钮背景颜色
button.image.color = isEnabled ? new Color(0.2f, 0.4f, 0.2f, 1f) : new Color(0.4f, 0.2f, 0.2f, 1f);
}
private void OnObjectEnable(GameObject? gameObject, Button? button, TMP_Text? text)
{
if (gameObject == null || button == null || text == null) return;
Debug.Log($"设置{gameObject.name} active = {!gameObject.activeSelf}");
gameObject.SetActive(!gameObject.activeSelf);
UpdateObjButton(gameObject, button, text);
}
private void UpdateObjButton(GameObject gameObject, Button button, TMP_Text text)
{
if (gameObject.activeSelf)
{
button.image.color = Color.green;
text.text = "√";
}
else
{
button.image.color = Color.red;
text.text = "×";
}
}
}
}