Files
ichni_Creator_Studio/Assets/Scripts/DynamicUI/Hierarchy/HierarchyTab.cs

456 lines
17 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.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<HierarchyTab> 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<GameObject> indentationLines = new List<GameObject>();
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<HierarchyTab>();
// 清除旧的缩进线
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<RectTransform>().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>();
quickMover.Initialize(haveTransformSubmodule);
}
else if (connectedGameElement is NoteBase noteBase && noteBase.noteVisual != null)
{
QuickMover quickMover = Instantiate(EditorManager.instance.basePrefabs.QuickMoveObj).GetComponent<QuickMover>();
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<Transform> thisSubTree = GetTabSubTreeTransforms(this);
List<Transform> 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<Transform> thisElemSubTree = GetElementSubTreeTransforms(connectedGameElement);
List<Transform> 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<PathNode>().ToList();
track.trackPathSubmodule.SetPathPoints();
}
}
// 获取tab及其所有子tab的transform前序遍历
private List<Transform> GetTabSubTreeTransforms(HierarchyTab tab)
{
List<Transform> list = new List<Transform>();
list.Add(tab.transform);
foreach (var child in tab.childTabList)
list.AddRange(GetTabSubTreeTransforms(child));
return list;
}
// 获取GameElement及其所有子元素的transform前序遍历
private List<Transform> GetElementSubTreeTransforms(GameElement element)
{
List<Transform> list = new List<Transform>();
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<GameElement> 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<GameElement> 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<GameElement> 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;
}
}
}