Files
XRLib/Assets/Scripts/Studio/Tab/StudioSideTabBarHierarchy.cs

526 lines
21 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UVC.Core;
using UVC.Studio.Manager;
using UVC.UI.List.Tree;
using UVC.UI.Tab;
using UVC.UI.Window;
namespace UVC.Studio.Tab
{
public class StudioSideTabBarHierarchy : MonoBehaviour, ITabContent
{
[SerializeField]
private HierarchyWindow? hierarchyWindow;
private bool isInitialized = false;
/// <summary>
/// StageObject ID와 TreeListItemData 간의 매핑
/// </summary>
private readonly Dictionary<string, TreeListItemData> _stageObjectToTreeItem = new();
/// <summary>
/// StageObjectManager 참조
/// </summary>
private StageObjectManager? _stageObjectManager;
/// <summary>
/// SelectionManager 참조
/// </summary>
private SelectionManager? _selectionManager;
/// <summary>
/// 선택 이벤트 처리 중 무한 루프 방지 플래그
/// </summary>
private bool _isProcessingSelection = false;
protected void Awake()
{
if (hierarchyWindow == null)
{
hierarchyWindow = GetComponentInChildren<HierarchyWindow>();
}
if (hierarchyWindow == null)
{
Debug.LogError("HierarchyWindow component is not assigned or found in Children.");
return;
}
hierarchyWindow.OnItemSelected += OnItemSelectedHandler;
hierarchyWindow.OnItemDeselected += OnItemDeselectedHandler;
hierarchyWindow.OnItemVisibilityChanged += OnItemVisibilityChangedHandler;
hierarchyWindow.OnItemDeleted += OnItemDeletedHandler;
hierarchyWindow.OnItemDoubleClicked += OnItemDoubleClickedHandler;
//다른 클래스에서 이 컴포넌트를 주입 받을 수 있도록 등록
InjectorAppContext.Instance.Injector.RegisterInstance<StudioSideTabBarHierarchy>(this, ServiceLifetime.Scene);
}
/// <summary>
/// HierarchyWindow에서 아이템이 선택되었을 때 호출
/// SelectionManager를 통해 객체를 선택하고 Outlinable을 활성화
/// 자식 항목 선택 시 해당 Transform의 속성을 PropertyWindow에 표시
/// </summary>
private void OnItemSelectedHandler(TreeListItemData item)
{
Debug.Log($"[StudioSideTabBarHierarchy] Item selected: {item.Name} (ExternalKey: {item.ExternalKey})");
if (_isProcessingSelection) return;
if (_selectionManager == null) return;
_isProcessingSelection = true;
try
{
// ExternalKey가 있으면 루트 StageObject 선택
if (!string.IsNullOrEmpty(item.ExternalKey))
{
// SelectionManager를 통해 객체 선택 (Outlinable 활성화됨)
// clearHierarchySelection: false - TreeList가 이미 자체적으로 선택을 관리하므로
// SelectionManager.DeselectAll()에서 HierarchyWindow 선택을 해제하지 않음
_selectionManager.SelectById(item.ExternalKey, addToSelection: false, clearHierarchySelection: false);
}
// ExternalKey가 없으면 자식 항목 - Tag에서 Transform 가져와서 PropertyWindow에 표시
else if (item.Tag is Transform childTransform)
{
_selectionManager.DisplayChildTransformProperties(childTransform, item.Name);
}
}
finally
{
_isProcessingSelection = false;
}
}
/// <summary>
/// HierarchyWindow에서 아이템 선택이 해제되었을 때 호출
/// SelectionManager를 통해 객체 선택을 해제하고 Outlinable을 비활성화
/// </summary>
private void OnItemDeselectedHandler(TreeListItemData item)
{
Debug.Log($"[StudioSideTabBarHierarchy] Item deselected: {item.Name} (ExternalKey: {item.ExternalKey})");
if (_isProcessingSelection) return;
if (_selectionManager == null) return;
_isProcessingSelection = true;
try
{
// ExternalKey가 있으면 루트 StageObject 선택 해제
if (!string.IsNullOrEmpty(item.ExternalKey))
{
// SelectionManager를 통해 객체 선택 해제 (Outlinable 비활성화됨)
_selectionManager.DeselectById(item.ExternalKey);
}
// ExternalKey가 없으면 자식 항목 - 자식 Transform 표시 상태 초기화
else if (item.Tag is Transform)
{
_selectionManager.ClearChildTransformDisplay();
}
}
finally
{
_isProcessingSelection = false;
}
}
/// <summary>
/// HierarchyWindow에서 아이템 가시성이 변경되었을 때 호출
/// 해당 객체의 GameObject.SetActive(true/false) 처리
/// </summary>
private void OnItemVisibilityChangedHandler(TreeListItemData item, bool isVisible)
{
Debug.Log($"[StudioSideTabBarHierarchy] Item visibility changed: {item.Name} (ExternalKey: {item.ExternalKey}), IsVisible: {isVisible}");
// ExternalKey가 있으면 루트 StageObject 가시성 설정
if (!string.IsNullOrEmpty(item.ExternalKey))
{
// SelectionManager를 통해 객체 가시성 설정
_selectionManager?.SetVisibilityById(item.ExternalKey, isVisible);
}
// ExternalKey가 없으면 자식 항목 - 직접 GameObject 가시성 설정
else if (item.Tag is Transform childTransform)
{
if (childTransform != null && childTransform.gameObject != null)
{
childTransform.gameObject.SetActive(isVisible);
Debug.Log($"[StudioSideTabBarHierarchy] Set child GameObject active: {childTransform.name} = {isVisible}");
}
}
}
/// <summary>
/// HierarchyWindow에서 아이템이 삭제되었을 때 호출 (Delete 키)
/// StageObjectManager를 통해 실제 모델을 삭제하고 PropertyWindow를 정리
/// </summary>
private void OnItemDeletedHandler(TreeListItemData item)
{
Debug.Log($"[StudioSideTabBarHierarchy] Item deleted: {item.Name} (ExternalKey: {item.ExternalKey})");
// ExternalKey가 있으면 루트 StageObject 삭제
if (!string.IsNullOrEmpty(item.ExternalKey))
{
// 매핑에서 제거
_stageObjectToTreeItem.Remove(item.ExternalKey);
// SelectionManager를 통해 선택 해제 (PropertyWindow 정리됨)
_selectionManager?.DeselectById(item.ExternalKey);
// StageObjectManager를 통해 실제 모델 삭제
_stageObjectManager?.Unregister(item.ExternalKey);
}
// ExternalKey가 없으면 자식 항목 - 실제 GameObject 삭제 및 PropertyWindow 정리
else if (item.Tag is Transform childTransform)
{
// PropertyWindow 표시 초기화
_selectionManager?.ClearChildTransformDisplay();
// 실제 GameObject 삭제
if (childTransform != null && childTransform.gameObject != null)
{
Debug.Log($"[StudioSideTabBarHierarchy] Destroying child GameObject: {childTransform.name}");
Destroy(childTransform.gameObject);
}
}
}
/// <summary>
/// HierarchyWindow에서 아이템이 더블클릭되었을 때 호출
/// StageObjectManager.Focus를 통해 해당 객체로 카메라 포커스 이동
/// </summary>
private void OnItemDoubleClickedHandler(TreeListItemData item)
{
Debug.Log($"[StudioSideTabBarHierarchy] Item double clicked: {item.Name} (ExternalKey: {item.ExternalKey})");
if (_stageObjectManager == null) return;
// ExternalKey가 있으면 ID로 포커스
if (!string.IsNullOrEmpty(item.ExternalKey))
{
_stageObjectManager.Focus(item.ExternalKey);
}
// ExternalKey가 없으면 자식 항목 - Tag에서 Transform의 GameObject로 포커스
else if (item.Tag is Transform childTransform && childTransform.gameObject != null)
{
_stageObjectManager.Focus(childTransform.gameObject);
}
}
/// <summary>
/// HierarchyWindow의 선택을 해제합니다.
/// SelectionManager에서 빈 공간 클릭 시 호출됩니다.
/// </summary>
public void ClearSelection()
{
if (hierarchyWindow != null)
{
hierarchyWindow.ClearSelection();
}
}
protected async void Start()
{
// InjectorSceneContext 초기화 대기
await InjectorSceneContext.Instance.WaitForInitializationAsync();
// StageObjectManager 가져오기
_stageObjectManager = InjectorAppContext.Instance.Get<StageObjectManager>();
if (_stageObjectManager != null)
{
// 이벤트 구독
_stageObjectManager.OnObjectAdded += OnStageObjectAdded;
_stageObjectManager.OnObjectRemoved += OnStageObjectRemoved;
// 이미 등록된 객체들 반영
foreach (var kvp in _stageObjectManager.Objects)
{
AddTreeItem(kvp.Value);
}
}
else
{
Debug.LogWarning("[StudioSideTabBarHierarchy] StageObjectManager not found.");
}
// SelectionManager 가져오기
if (_selectionManager == null) _selectionManager = InjectorAppContext.Instance.Get<SelectionManager>();
if (_selectionManager != null)
{
// 선택 변경 이벤트 구독 (화면 클릭으로 선택 시 HierarchyWindow 동기화)
_selectionManager.OnSelectionChanged += OnSelectionManagerSelectionChanged;
}
else
{
Debug.LogWarning("[StudioSideTabBarHierarchy] SelectionManager not found.");
}
isInitialized = true;
}
/// <summary>
/// SelectionManager에서 선택이 변경되었을 때 호출 (화면에서 객체 클릭 시)
/// HierarchyWindow의 선택 상태를 동기화
/// </summary>
private void OnSelectionManagerSelectionChanged(StageObjectManager.StageObject stageObject, bool isSelected)
{
if (_isProcessingSelection) return;
if (hierarchyWindow == null || string.IsNullOrEmpty(stageObject.Id)) return;
// 매핑에서 TreeListItemData 찾기
if (!_stageObjectToTreeItem.TryGetValue(stageObject.Id, out var treeItem)) return;
_isProcessingSelection = true;
try
{
if (isSelected)
{
// HierarchyWindow에서 해당 아이템 선택
hierarchyWindow.SelectItem(treeItem.Name);
Debug.Log($"[StudioSideTabBarHierarchy] Synced selection to HierarchyWindow: {treeItem.Name}");
}
else
{
// HierarchyWindow에서 해당 아이템 선택 해제
hierarchyWindow.DeselectItem(treeItem.Name);
Debug.Log($"[StudioSideTabBarHierarchy] Synced deselection to HierarchyWindow: {treeItem.Name}");
}
}
finally
{
_isProcessingSelection = false;
}
}
/// <summary>
/// StageObject가 추가될 때 호출
/// </summary>
private void OnStageObjectAdded(StageObjectManager.StageObject stageObject)
{
AddTreeItem(stageObject);
}
/// <summary>
/// StageObject가 제거될 때 호출
/// </summary>
private void OnStageObjectRemoved(StageObjectManager.StageObject stageObject)
{
RemoveTreeItem(stageObject);
}
/// <summary>
/// TreeListItemData를 생성하여 HierarchyWindow에 추가
/// </summary>
private void AddTreeItem(StageObjectManager.StageObject stageObject)
{
if (hierarchyWindow == null) return;
// 이미 존재하는지 확인
if (_stageObjectToTreeItem.ContainsKey(stageObject.Id))
{
Debug.LogWarning($"[StudioSideTabBarHierarchy] TreeItem already exists for StageObject: {stageObject.Id}");
return;
}
// TreeListItemData 생성 (하위 자식 포함)
var treeItem = CreateTreeItemRecursive(
stageObject.GameObject != null ? stageObject.GameObject.transform : null,
stageObject.GameObject != null ? stageObject.GameObject.name : stageObject.Equipment.id,
stageObject.Id
);
// 매핑 저장
_stageObjectToTreeItem[stageObject.Id] = treeItem;
// HierarchyWindow에 추가
hierarchyWindow.AddItem(treeItem);
Debug.Log($"[StudioSideTabBarHierarchy] Added TreeItem: {treeItem.Name} (including all children)");
}
/// <summary>
/// Transform의 자식들을 재귀적으로 탐색하여 TreeListItemData를 생성
/// </summary>
/// <param name="transform">탐색할 Transform (null이면 이름만 사용)</param>
/// <param name="name">표시 이름</param>
/// <param name="externalKey">외부 키 (루트 노드에만 설정)</param>
/// <returns>생성된 TreeListItemData</returns>
private TreeListItemData CreateTreeItemRecursive(Transform? transform, string name, string? externalKey = null)
{
var treeItem = new TreeListItemData(name);
if (!string.IsNullOrEmpty(externalKey))
{
treeItem.ExternalKey = externalKey;
}
// Transform을 Tag에 저장 (자식 Transform 접근용)
treeItem.Tag = transform;
// Transform이 있으면 자식들을 재귀적으로 추가
if (transform != null)
{
foreach (Transform child in transform)
{
var childItem = CreateTreeItemRecursive(child, child.name);
treeItem.AddChild(childItem);
}
}
return treeItem;
}
/// <summary>
/// TreeListItemData를 HierarchyWindow에서 제거
/// </summary>
private void RemoveTreeItem(StageObjectManager.StageObject stageObject)
{
if (hierarchyWindow == null) return;
// 매핑에서 찾기
if (_stageObjectToTreeItem.TryGetValue(stageObject.Id, out var treeItem))
{
// HierarchyWindow에서 제거
hierarchyWindow.DeleteItem(treeItem);
// 매핑에서 제거
_stageObjectToTreeItem.Remove(stageObject.Id);
Debug.Log($"[StudioSideTabBarHierarchy] Removed TreeItem: {treeItem.Name}");
}
}
/// <summary>
/// TreeListItemData로 StageObject ID 찾기
/// </summary>
public string? GetStageObjectId(TreeListItemData treeItem)
{
return treeItem.ExternalKey;
}
/// <summary>
/// StageObject ID로 TreeListItemData 찾기
/// </summary>
public TreeListItemData? GetTreeItem(string stageObjectId)
{
return _stageObjectToTreeItem.TryGetValue(stageObjectId, out var treeItem) ? treeItem : null;
}
/// <summary>
/// StageObject의 표시 이름을 변경합니다.
/// HierarchyWindow의 TreeList에도 반영됩니다.
/// </summary>
/// <param name="stageObjectId">변경할 StageObject의 ID</param>
/// <param name="newName">새 이름</param>
public void UpdateItemName(string stageObjectId, string newName)
{
if (hierarchyWindow == null) return;
if (!_stageObjectToTreeItem.TryGetValue(stageObjectId, out var treeItem)) return;
// HierarchyWindow.SetItemName을 통해 TreeList UI도 함께 갱신
hierarchyWindow.SetItemName(treeItem, newName);
Debug.Log($"[StudioSideTabBarHierarchy] Updated item name: {stageObjectId} -> {newName}");
}
/// <summary>
/// 탭 콘텐츠에 데이터를 전달합니다.
/// 탭이 활성화될 때 호출되며, SelectionManager의 선택 상태를 HierarchyWindow에 동기화합니다.
/// </summary>
/// <param name="data">전달할 데이터 객체</param>
public void SetContentData(object? data)
{
Debug.Log("StudioSideTabBarHierarchy: SetContentData called");
// SelectionManager에 선택된 항목이 있으면 HierarchyWindow에 반영
SyncSelectionFromSelectionManager();
}
/// <summary>
/// SelectionManager의 현재 선택 상태를 HierarchyWindow에 동기화합니다.
/// </summary>
private async void SyncSelectionFromSelectionManager()
{
if (!isInitialized)
{
await UniTask.WaitUntil(() => isInitialized).TimeoutWithoutException(new TimeSpan(0, 0, 1));
}
Debug.Log($"StudioSideTabBarHierarchy: SyncSelectionFromSelectionManager called. _selectionManager == null:{_selectionManager == null}, hierarchyWindow == null:{hierarchyWindow == null}");
if (_selectionManager == null || hierarchyWindow == null) return;
var selectedObjects = _selectionManager.SelectedObjects;
if (selectedObjects.Count == 0) return;
_isProcessingSelection = true;
try
{
foreach (var stageObject in selectedObjects)
{
if (string.IsNullOrEmpty(stageObject.Id)) continue;
// 매핑에서 TreeListItemData 찾기
if (_stageObjectToTreeItem.TryGetValue(stageObject.Id, out var treeItem))
{
hierarchyWindow.SelectItem(treeItem.Name);
Debug.Log($"[StudioSideTabBarHierarchy] Synced existing selection: {treeItem.Name}");
}
}
}
finally
{
_isProcessingSelection = false;
}
}
/// <summary>
/// 탭 전환 시 데이터가 있는 경우 전달 되는 데이터. SetContentData 이후 호출 됨
/// </summary>
/// <param name="data">전달할 데이터 객체</param>
public void UpdateContentData(object? data)
{
}
/// <summary>
/// 닫힐 때 실행되는 로직을 처리합니다.
/// </summary>
/// <returns>비동기 닫기 작업을 나타내는 <see cref="UniTask"/>입니다.</returns>
public UniTask OnCloseAsync()
{
Debug.Log("StudioSideTabBarHierarchy: OnClose called");
return UniTask.CompletedTask;
}
protected void OnDestroy()
{
if (hierarchyWindow != null)
{
hierarchyWindow.OnItemSelected -= OnItemSelectedHandler;
hierarchyWindow.OnItemDeselected -= OnItemDeselectedHandler;
hierarchyWindow.OnItemVisibilityChanged -= OnItemVisibilityChangedHandler;
hierarchyWindow.OnItemDeleted -= OnItemDeletedHandler;
hierarchyWindow.OnItemDoubleClicked -= OnItemDoubleClickedHandler;
}
// StageObjectManager 이벤트 구독 해제
if (_stageObjectManager != null)
{
_stageObjectManager.OnObjectAdded -= OnStageObjectAdded;
_stageObjectManager.OnObjectRemoved -= OnStageObjectRemoved;
_stageObjectManager = null;
}
// SelectionManager 이벤트 구독 해제
if (_selectionManager != null)
{
_selectionManager.OnSelectionChanged -= OnSelectionManagerSelectionChanged;
_selectionManager = null;
}
// 매핑 정리
_stageObjectToTreeItem.Clear();
}
}
}