526 lines
21 KiB
C#
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();
|
|
}
|
|
}
|
|
} |