using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DG.Tweening; using Lean.Pool; using Ichni.RhythmGame; using Michsky.MUIP; using Sirenix.Utilities; using TMPro; using Unity.VisualScripting; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.Serialization; using UnityEngine.UI; namespace Ichni.Editor { public partial class HierarchyTab : MonoBehaviour { public GameObject indentationLinePrefab; public GameElement connectedGameElement; public HierarchyTab parentTab; public List childTabList; public int tabLayer; public bool isSelected; public bool isExpanded; public Image BgImage; public RectTransform tabRect; public LayoutElement layoutElement; public RectTransform tabMainRect; public Button tabButton; public Button expandButton; public DoubleCheckButton deleteButton; public TMP_Text tabButtonText; private List indentationLines = new List(); public void SetTab(GameElement targetElement, GameElement parentElement, bool animate = true) { if (animate) { transform.localScale = Vector3.one; transform.DOScale(Vector3.one, 0.2f).SetEase(Ease.OutCirc).From(new Vector3(1, 0, 1)); } else { transform.DOKill(); transform.localScale = Vector3.one; } connectedGameElement = targetElement; tabButtonText.text = targetElement.elementName; targetElement.connectedTab = this; this.isExpanded = false; this.isSelected = false; this.childTabList = new List(); // 清除旧的缩进线 for (int i = indentationLines.Count - 1; i >= 0; i--) { if (indentationLines[i] != null) Destroy(indentationLines[i]); } indentationLines.Clear(); if (parentElement == null) { this.tabLayer = 0; this.parentTab = null; this.transform.SetAsLastSibling(); } else { this.parentTab = parentElement.connectedTab; parentElement.connectedTab.childTabList.Add(this); this.tabLayer = this.parentTab.tabLayer + 1; this.transform.SetSiblingIndex(this.parentTab.transform.GetSiblingIndex() + GetAllChildrenCount(this.parentTab)); if (!this.parentTab.isExpanded) { this.isExpanded = false; var pt = this.parentTab; SetExpansion(false); pt.SetStatus(); return; } for (int i = 1; i <= this.tabLayer; i++) { float lineX = 30 * i - 15; var d = Instantiate(indentationLinePrefab, tabRect); d.GetComponent().anchoredPosition = new Vector2(lineX, 0); indentationLines.Add(d); } parentTab?.SetStatus(); } float posX = (30 * tabLayer); tabMainRect.anchoredPosition = new Vector2(posX, tabMainRect.anchoredPosition.y); tabButton.onClick.RemoveAllListeners(); tabButton.onClick.AddListener(SelectGameElement); expandButton.onClick.RemoveAllListeners(); expandButton.onClick.AddListener(ExpandOrFold); deleteButton.onConfirm = () => EditorManager.instance.operationManager.CopyPasteDeleteModule.DeleteElement(connectedGameElement); SetStatus(); } private void OnDestroy() { transform.DOKill(); // 清除缩进线 for (int i = indentationLines.Count - 1; i >= 0; i--) { if (indentationLines[i] != null) Destroy(indentationLines[i]); } indentationLines.Clear(); } public void SetStatus() { expandButton.gameObject.SetActive(!connectedGameElement.childElementList.IsNullOrEmpty()); } } public partial class HierarchyTab { private int GetAllChildrenCount(HierarchyTab tab) { int c = tab.childTabList.Count; for (int i = 0; i < tab.childTabList.Count; i++) { c += GetAllChildrenCount(tab.childTabList[i]); } return c; } public void ExecuteOrSelect() { if (EditorManager.instance.uiManager.hierarchy.NeedExecute) { EditorManager.instance.uiManager.hierarchy.upLoadElement = connectedGameElement; } else { SelectGameElement(); } } public void SelectGameElement() { if (Keyboard.current.leftCtrlKey.isPressed) { if (!isSelected) { EditorManager.instance.operationManager.AddSelectElement(connectedGameElement); } else { EditorManager.instance.operationManager.RemoveSelectElement(connectedGameElement); } } else { EditorManager.instance.operationManager.ClearSelectedElements(); EditorManager.instance.operationManager.AddSelectElement(connectedGameElement); } if (EditorManager.instance.useQuickMove) { if (connectedGameElement is IHaveTransformSubmodule haveTransformSubmodule) { QuickMover quickMover = Instantiate(EditorManager.instance.basePrefabs.QuickMoveObj).GetComponent(); quickMover.Initialize(haveTransformSubmodule); } else if (connectedGameElement is NoteBase noteBase && noteBase.noteVisual != null) { QuickMover quickMover = Instantiate(EditorManager.instance.basePrefabs.QuickMoveObj).GetComponent(); quickMover.Initialize(noteBase.noteVisual); } else if (QuickMover.instance != null) { Destroy(QuickMover.instance.gameObject); QuickMover.instance = null; } } EditorManager.instance.uiManager.inspector.SetInspector(connectedGameElement); } public void MoveTab(bool isUp) { // 1. 基础合法性检查:根节点或孤立节点无法移动 if (parentTab == null || connectedGameElement.parentElement == null) return; // 获取当前层级的列表 var tabList = parentTab.childTabList; int tabIndex = tabList.IndexOf(this); var elementList = connectedGameElement.parentElement.childElementList; int elemIndex = elementList.IndexOf(connectedGameElement); // 计算目标索引 int targetTabIndex = isUp ? tabIndex - 1 : tabIndex + 1; int targetElemIndex = isUp ? elemIndex - 1 : elemIndex + 1; // 2. 边界检查:如果是第一个往上或最后一个往下,则返回 if (targetTabIndex < 0 || targetTabIndex >= tabList.Count) return; if (targetElemIndex < 0 || targetElemIndex >= elementList.Count) return; // 获取目标对象 var targetTab = tabList[targetTabIndex]; var targetElem = elementList[targetElemIndex]; // 3. 交换逻辑数据顺序(List 内部排序) tabList[tabIndex] = targetTab; tabList[targetTabIndex] = this; elementList[elemIndex] = targetElem; elementList[targetElemIndex] = connectedGameElement; // 4. 处理 UI Tab 的 Sibling Index(解决偏移问题的核心) List thisSubTree = GetTabSubTreeTransforms(this); List targetSubTree = GetTabSubTreeTransforms(targetTab); if (thisSubTree.Count > 0 && targetSubTree.Count > 0) { if (isUp) { // 向上移:正序,插到目标分支的最上方 int insertIndex = targetSubTree[0].GetSiblingIndex(); for (int i = 0; i < thisSubTree.Count; i++) { thisSubTree[i].SetSiblingIndex(insertIndex + i); } } else { // 向下移:逆序,依次插到目标分支最末尾的索引位置 // 这样先移过去的会被后移过去的顶到后面,保持原有父子相对顺序 int targetLastIndex = targetSubTree[targetSubTree.Count - 1].GetSiblingIndex(); for (int i = thisSubTree.Count - 1; i >= 0; i--) { thisSubTree[i].SetSiblingIndex(targetLastIndex); } } } // 5. 处理场景 GameElement 的 Sibling Index(同理) List thisElemSubTree = GetElementSubTreeTransforms(connectedGameElement); List targetElemSubTree = GetElementSubTreeTransforms(targetElem); if (thisElemSubTree.Count > 0 && targetElemSubTree.Count > 0) { if (isUp) { int insertIndex = targetElemSubTree[0].GetSiblingIndex(); for (int i = 0; i < thisElemSubTree.Count; i++) { thisElemSubTree[i].SetSiblingIndex(insertIndex + i); } } else { int targetLastIndex = targetElemSubTree[targetElemSubTree.Count - 1].GetSiblingIndex(); for (int i = thisElemSubTree.Count - 1; i >= 0; i--) { thisElemSubTree[i].SetSiblingIndex(targetLastIndex); } } } //奇妙的特判阶段 foreach (var i in parentTab.childTabList) { i.connectedGameElement.Refresh(); } parentTab.connectedGameElement.Refresh(); if (parentTab.connectedGameElement is Track track) { track.trackPathSubmodule.pathNodeList = track.childElementList.OfType().ToList(); track.trackPathSubmodule.SetPathPoints(); } } // 获取tab及其所有子tab的transform(前序遍历) private List GetTabSubTreeTransforms(HierarchyTab tab) { List list = new List(); list.Add(tab.transform); foreach (var child in tab.childTabList) list.AddRange(GetTabSubTreeTransforms(child)); return list; } // 获取GameElement及其所有子元素的transform(前序遍历) private List GetElementSubTreeTransforms(GameElement element) { List list = new List(); if (element.transform != null) list.Add(element.transform); foreach (var child in element.childElementList) list.AddRange(GetElementSubTreeTransforms(child)); return list; } public void ExpandOrFold() { ExpandOrFold(false); } public void ExpandOrFold(bool forceAllExPand = false) { this.childTabList.RemoveAll(s => s == null); isExpanded = !isExpanded; ExpandAnim(); if (isExpanded) { connectedGameElement.ScanAndAddEnableTypes(); List FixedList = !forceAllExPand ? connectedGameElement.GetChildrenByTypes() : connectedGameElement.childElementList; ExpandAsync(FixedList); } else { //expandButton.transform.Rotate(new Vector3(0, 0, 180)); for (int i = childTabList.Count - 1; i >= 0; i--) { childTabList[i].SetExpansion(isExpanded); } } } private void SetExpansion(bool expand) { if (!expand && isExpanded) { for (int i = childTabList.Count - 1; i >= 0; i--) { if (childTabList[i] != null) childTabList[i].SetExpansion(expand); } } if (!expand) { CleanupAndDespawn(); } } private void CleanupAndDespawn() { if (connectedGameElement != null) { // 如果该元素还在选中列表中,先移除(须在 connectedTab=null 之前执行) if (isSelected) { var sel = EditorManager.instance.operationManager.currentSelectedElements; sel.Remove(connectedGameElement); isSelected = false; } connectedGameElement.connectedTab = null; } parentTab?.childTabList.Remove(this); EditorManager.instance.uiManager.hierarchy.tabList.Remove(this); transform.DOKill(); tabButton.onClick.RemoveAllListeners(); expandButton.onClick.RemoveAllListeners(); for (int i = indentationLines.Count - 1; i >= 0; i--) { if (indentationLines[i] != null) Destroy(indentationLines[i]); } indentationLines.Clear(); connectedGameElement = null; parentTab = null; childTabList?.Clear(); isExpanded = false; isExpandDone = true; tabLayer = 0; LeanPool.Despawn(gameObject); } private void ExpandAnim() { expandButton.transform.DORotate(new Vector3(0, 0, !isExpanded ? 0f : 180f), 0.2f); } public bool isExpandDone = true; private void ExpandImmediately(List FixedList) { float StrandTimeWhileStartUp = EditorManager.instance.CurrentFrameRate; float frameTime = 1f / StrandTimeWhileStartUp * 3f; for (var index = 0; index < FixedList.Count; index++) { var childElement = FixedList[index]; EditorManager.instance.uiManager.hierarchy.GenerateTab(childElement, connectedGameElement); } } public IEnumerator ExpandSyncBatched() { connectedGameElement.ScanAndAddEnableTypes(); isExpanded = true; isExpandDone = false; ExpandAnim(); var children = connectedGameElement.childElementList; int batchCount = 0; for (int i = 0; i < children.Count; i++) { if (!isExpanded) break; if (children[i].connectedTab == null) { EditorManager.instance.uiManager.hierarchy.GenerateTab(children[i], connectedGameElement, false); batchCount++; if (batchCount >= 15) { batchCount = 0; yield return null; if (!isExpanded) break; } } } isExpandDone = true; } async void ExpandAsync(List FixedList) { isExpandDone = false; for (var index = 0; index < FixedList.Count; index++) { var childElement = FixedList[index]; await EditorManager.instance.uiManager.hierarchy.GenerateTabAsync(childElement, connectedGameElement); if (!isExpanded) break; } isExpandDone = true; } } }