Files
DuckovMods/SceneView/TreeViewNode.cs

437 lines
18 KiB
C#
Raw Normal View History

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 = "";
2025-11-18 18:45:14 +08:00
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;
2025-11-18 18:45:14 +08:00
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);
2025-11-18 18:45:14 +08:00
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;
2025-11-18 18:45:14 +08:00
text.color = Color.white; // 确保文本颜色可见
}
if (objectEnable == null)
{
var button = ControlUtilities.CreateButton(rectTransform, buttonConfig, null);
objectEnable = button.button;
objectText = button.text;
2025-11-18 18:45:14 +08:00
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;
2025-11-18 18:45:14 +08:00
rect.pivot = new Vector2(1, 1); // 右上角锚点
rect.sizeDelta = new Vector2(30, 30);
2025-11-18 18:45:14 +08:00
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;
}
}
2025-11-18 18:45:14 +08:00
// 确保子内容一开始是隐藏的 (懒加载的初始状态)
child.SetActive(false);
childRectTransform.anchorMin = new Vector2(0, 1);
childRectTransform.anchorMax = new Vector2(1, 1);
childRectTransform.pivot = new Vector2(0.5f, 1);
2025-11-18 18:45:14 +08:00
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;
}
}
2025-11-18 18:45:14 +08:00
// 调整布局组参数,确保正确计算
verticalLayout.padding.left = (int)(buttonHeight * 0.5f); // 增加左边距以缩进
verticalLayout.padding.top = 2;
verticalLayout.padding.bottom = 4;
verticalLayout.spacing = 2;
2025-11-18 18:45:14 +08:00
verticalLayout.childControlHeight = false; // 由ContentSizeFitter控制子节点高度
verticalLayout.childControlWidth = true; // 子节点宽度按父级宽度自适应
verticalLayout.childForceExpandHeight = false; // 不要强制展开高度
verticalLayout.childAlignment = TextAnchor.UpperLeft; // 确保子元素从上开始排列
sizeFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
}
2025-11-18 18:45:14 +08:00
// 修改为只初始化当前节点,不立即生成子内容
public IEnumerator InitializeNode(GameObject? targetObject, int depth = 0)
{
2025-11-18 18:45:14 +08:00
_targetGameObject = targetObject;
_nodeDepth = depth;
2025-11-18 18:45:14 +08:00
// 清除之前的监听器,避免重复
label?.onClick.RemoveAllListeners();
objectEnable?.onClick.RemoveAllListeners();
2025-11-18 18:45:14 +08:00
if (targetObject == null) // 代表这是场景根节点
{
2025-11-18 18:45:14 +08:00
originalText = $"场景 {SceneManager.GetActiveScene().name}";
if (text) text.text = originalText + " →"; // 默认收拢,显示右箭头
label?.onClick.AddListener(OnExpand); // 场景节点点击展开自身
objectEnable?.gameObject.SetActive(false); // 场景节点没有Enable/Disable功能
yield break; // 场景节点不立即生成根对象,等待点击展开
}
2025-11-18 18:45:14 +08:00
// 过滤掉特定Canvas
if (targetObject == null || targetObject.name == CanvasControl.ViewCanvasName)
{
2025-11-18 18:45:14 +08:00
yield break;
}
2025-11-18 18:45:14 +08:00
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()); // 初始化时异步更新高度
}
2025-11-18 18:45:14 +08:00
// 修改 OnExpand 方法以实现懒加载
private void OnExpand()
{
2025-11-18 18:45:14 +08:00
if (child == null)
{
2025-11-18 18:45:14 +08:00
Debug.LogError("Child content not found.");
return;
}
2025-11-18 18:45:14 +08:00
CanvasControl.FocusGameObject=_targetGameObject;
2025-11-18 18:45:14 +08:00
// 如果已经生成,直接切换显示状态
if (_childrenGenerated)
{
2025-11-18 18:45:14 +08:00
ToggleExpansionVisuals();
StartCoroutine(DelayedUpdateHeight()); // 每次展开/收拢后异步更新高度
}
else // 如果子内容未生成,则异步生成
{
ToggleExpansionVisuals(); // 生成完成后再切换显示状态 (展开或根据后续逻辑)
// 在生成子节点之前,确保子内容容器是激活的
child.SetActive(true); // <--- 新增这行,在生成子节点之前激活容器
StartCoroutine(GenerateChildrenContent(_targetGameObject, _nodeDepth + 1, () =>
{
_childrenGenerated = true;
// 注意ToggleExpansionVisuals 会再次调用 child.SetActive()
// 如果是展开状态,它会保持 true。如果是收拢则设置为 false。
StartCoroutine(DelayedUpdateHeight()); // 生成并展开后异步更新高度
}));
}
}
2025-11-18 18:45:14 +08:00
// 新增方法生成子内容组件和子GameObject
private IEnumerator GenerateChildrenContent(GameObject? targetObject, int depth, Action? onComplete = null)
{
2025-11-18 18:45:14 +08:00
ClearChildNodes(); // 在生成之前清空所有旧的子节点
if (targetObject == null) // 场景根节点需要遍历根GameObject
{
var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects();
foreach (var rootObject in rootObjects)
{
2025-11-18 18:45:14 +08:00
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;
2025-11-18 18:45:14 +08:00
childNode.CreateUI(); // 确保UI元素已创建
// yield return StartCoroutine(childNode.InitializeNode(rootObject, depth)); // 浅初始化子节点
yield return null; // 每处理一个子节点就yield一次分散帧压力
}
2025-11-18 18:45:14 +08:00
onComplete?.Invoke();
yield break;
}
2025-11-18 18:45:14 +08:00
// 生成当前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);
2025-11-18 18:45:14 +08:00
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;
2025-11-18 18:45:14 +08:00
button.onClick.AddListener(() => OnButtonClick(button, comp));
yield return null; // 每创建一个组件按钮就yield一次
}
2025-11-18 18:45:14 +08:00
// 遍历目标对象的所有子对象并创建新的TreeViewNode浅初始化
foreach (Transform childTransform in targetObject.transform)
{
if (childTransform == null)
{
Debug.LogError("Child transform is null.");
continue;
}
2025-11-18 18:45:14 +08:00
var childNodeGO = new GameObject(childTransform.gameObject.name);
var childNode = childNodeGO.AddComponent<TreeViewNode>();
childNode.transform.SetParent(childRectTransform, false);
childNode.parent = this;
2025-11-18 18:45:14 +08:00
childNode.CreateUI(); // 确保UI元素已创建
// yield return StartCoroutine(childNode.InitializeNode(childTransform.gameObject, depth)); // 浅初始化子节点
yield return null; // 每处理一个子节点就yield一次
}
onComplete?.Invoke();
2025-11-18 18:45:14 +08:00
}
private void ToggleExpansionVisuals()
{
if (child == null) return;
2025-11-18 18:45:14 +08:00
// 切换 child 的激活状态
child.SetActive(!child.activeSelf);
if (text != null)
{
if (child.activeSelf)
{
text.text = originalText + " ↓"; // 下三角符号表示展开
}
else
{
text.text = originalText + " →"; // 右三角符号表示收拢
}
}
}
2025-11-18 18:45:14 +08:00
// 异步更新高度,给布局系统一帧时间来计算
private IEnumerator DelayedUpdateHeight()
{
2025-11-18 18:45:14 +08:00
// 等待一帧,让 LayoutGroup 和 ContentSizeFitter 完成计算
yield return null;
InternalUpdateHeight();
}
2025-11-18 18:45:14 +08:00
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 = "";
2025-11-18 18:45:14 +08:00
int baseIndent = depth; // GameObject的深度
if (isComponent) baseIndent++; // 组件比GameObject多一级缩进
2025-11-18 18:45:14 +08:00
// 增加额外的左边距,以产生层级感
for (var i = 0; i < baseIndent; i++)
{
indent += " ";
}
return $"{indent}{name}";
}
public void ClearChildNodes()
{
if (childRectTransform != null)
2025-11-18 18:45:14 +08:00
{
// 遍历并销毁所有子对象
for (int i = childRectTransform.childCount - 1; i >= 0; i--)
{
Destroy(childRectTransform.GetChild(i).gameObject);
}
}
}
private void OnButtonClick(Button button, Behaviour component)
{
2025-11-18 18:45:14 +08:00
if (button == null || component == null) return;
// 切换组件的启用状态
var isEnabled = !component.enabled;
component.enabled = isEnabled;
// 根据组件的启用状态修改按钮背景颜色
2025-11-18 18:45:14 +08:00
button.image.color = isEnabled ? new Color(0.2f, 0.4f, 0.2f, 1f) : new Color(0.4f, 0.2f, 0.2f, 1f);
}
2025-11-18 18:45:14 +08:00
private void OnObjectEnable(GameObject? gameObject, Button? button, TMP_Text? text)
{
2025-11-18 18:45:14 +08:00
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;
2025-11-18 18:45:14 +08:00
text.text = "√";
}
else
{
button.image.color = Color.red;
2025-11-18 18:45:14 +08:00
text.text = "×";
}
}
}
2025-11-18 18:45:14 +08:00
}