#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; /// /// StageObject ID와 TreeListItemData 간의 매핑 /// private readonly Dictionary _stageObjectToTreeItem = new(); /// /// StageObjectManager 참조 /// private StageObjectManager? _stageObjectManager; /// /// SelectionManager 참조 /// private SelectionManager? _selectionManager; /// /// 선택 이벤트 처리 중 무한 루프 방지 플래그 /// private bool _isProcessingSelection = false; protected void Awake() { if (hierarchyWindow == null) { hierarchyWindow = GetComponentInChildren(); } if (hierarchyWindow == null) { Debug.LogError("HierarchyWindow component is not assigned or found in Children."); return; } hierarchyWindow.OnItemSelected += OnItemSelectedHandler; hierarchyWindow.OnItemDeselected += OnItemDeselectedHandler; hierarchyWindow.OnItemVisibilityChanged += OnItemVisibilityChangedHandler; //다른 클래스에서 이 컴포넌트를 주입 받을 수 있도록 등록 InjectorAppContext.Instance.Injector.RegisterInstance(this, ServiceLifetime.Scene); } /// /// HierarchyWindow에서 아이템이 선택되었을 때 호출 /// SelectionManager를 통해 객체를 선택하고 Outlinable을 활성화 /// private void OnItemSelectedHandler(TreeListItemData item) { Debug.Log($"[StudioSideTabBarHierarchy] Item selected: {item.Name} (ExternalKey: {item.ExternalKey})"); if (_isProcessingSelection) return; if (_selectionManager == null || string.IsNullOrEmpty(item.ExternalKey)) return; _isProcessingSelection = true; try { // SelectionManager를 통해 객체 선택 (Outlinable 활성화됨) _selectionManager.SelectById(item.ExternalKey); } finally { _isProcessingSelection = false; } } /// /// HierarchyWindow에서 아이템 선택이 해제되었을 때 호출 /// SelectionManager를 통해 객체 선택을 해제하고 Outlinable을 비활성화 /// private void OnItemDeselectedHandler(TreeListItemData item) { Debug.Log($"[StudioSideTabBarHierarchy] Item deselected: {item.Name} (ExternalKey: {item.ExternalKey})"); if (_isProcessingSelection) return; if (_selectionManager == null || string.IsNullOrEmpty(item.ExternalKey)) return; _isProcessingSelection = true; try { // SelectionManager를 통해 객체 선택 해제 (Outlinable 비활성화됨) _selectionManager.DeselectById(item.ExternalKey); } finally { _isProcessingSelection = false; } } /// /// HierarchyWindow에서 아이템 가시성이 변경되었을 때 호출 /// 해당 객체의 GameObject.SetActive(true/false) 처리 /// private void OnItemVisibilityChangedHandler(TreeListItemData item, bool isVisible) { Debug.Log($"[StudioSideTabBarHierarchy] Item visibility changed: {item.Name} (ExternalKey: {item.ExternalKey}), IsVisible: {isVisible}"); if (_selectionManager == null || string.IsNullOrEmpty(item.ExternalKey)) return; // SelectionManager를 통해 객체 가시성 설정 _selectionManager.SetVisibilityById(item.ExternalKey, isVisible); } protected async void Start() { // InjectorSceneContext 초기화 대기 await InjectorSceneContext.Instance.WaitForInitializationAsync(); // StageObjectManager 가져오기 _stageObjectManager = InjectorAppContext.Instance.Get(); 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(); if (_selectionManager != null) { // 선택 변경 이벤트 구독 (화면 클릭으로 선택 시 HierarchyWindow 동기화) _selectionManager.OnSelectionChanged += OnSelectionManagerSelectionChanged; } else { Debug.LogWarning("[StudioSideTabBarHierarchy] SelectionManager not found."); } isInitialized = true; } /// /// SelectionManager에서 선택이 변경되었을 때 호출 (화면에서 객체 클릭 시) /// HierarchyWindow의 선택 상태를 동기화 /// 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; } } /// /// StageObject가 추가될 때 호출 /// private void OnStageObjectAdded(StageObjectManager.StageObject stageObject) { AddTreeItem(stageObject); } /// /// StageObject가 제거될 때 호출 /// private void OnStageObjectRemoved(StageObjectManager.StageObject stageObject) { RemoveTreeItem(stageObject); } /// /// TreeListItemData를 생성하여 HierarchyWindow에 추가 /// 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)"); } /// /// Transform의 자식들을 재귀적으로 탐색하여 TreeListItemData를 생성 /// /// 탐색할 Transform (null이면 이름만 사용) /// 표시 이름 /// 외부 키 (루트 노드에만 설정) /// 생성된 TreeListItemData private TreeListItemData CreateTreeItemRecursive(Transform? transform, string name, string? externalKey = null) { var treeItem = new TreeListItemData(name); if (!string.IsNullOrEmpty(externalKey)) { treeItem.ExternalKey = externalKey; } // Transform이 있으면 자식들을 재귀적으로 추가 if (transform != null) { foreach (Transform child in transform) { var childItem = CreateTreeItemRecursive(child, child.name); treeItem.AddChild(childItem); } } return treeItem; } /// /// TreeListItemData를 HierarchyWindow에서 제거 /// 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}"); } } /// /// TreeListItemData로 StageObject ID 찾기 /// public string? GetStageObjectId(TreeListItemData treeItem) { return treeItem.ExternalKey; } /// /// StageObject ID로 TreeListItemData 찾기 /// public TreeListItemData? GetTreeItem(string stageObjectId) { return _stageObjectToTreeItem.TryGetValue(stageObjectId, out var treeItem) ? treeItem : null; } /// /// StageObject의 표시 이름을 변경합니다. /// HierarchyWindow의 TreeList에도 반영됩니다. /// /// 변경할 StageObject의 ID /// 새 이름 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}"); } /// /// 탭 콘텐츠에 데이터를 전달합니다. /// 탭이 활성화될 때 호출되며, SelectionManager의 선택 상태를 HierarchyWindow에 동기화합니다. /// /// 전달할 데이터 객체 public void SetContentData(object? data) { Debug.Log("StudioSideTabBarHierarchy: SetContentData called"); // SelectionManager에 선택된 항목이 있으면 HierarchyWindow에 반영 SyncSelectionFromSelectionManager(); } /// /// SelectionManager의 현재 선택 상태를 HierarchyWindow에 동기화합니다. /// 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; } } /// /// 탭 전환 시 데이터가 있는 경우 전달 되는 데이터. SetContentData 이후 호출 됨 /// /// 전달할 데이터 객체 public void UpdateContentData(object? data) { } /// /// 닫힐 때 실행되는 로직을 처리합니다. /// /// 비동기 닫기 작업을 나타내는 입니다. 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; } // StageObjectManager 이벤트 구독 해제 if (_stageObjectManager != null) { _stageObjectManager.OnObjectAdded -= OnStageObjectAdded; _stageObjectManager.OnObjectRemoved -= OnStageObjectRemoved; _stageObjectManager = null; } // SelectionManager 이벤트 구독 해제 if (_selectionManager != null) { _selectionManager.OnSelectionChanged -= OnSelectionManagerSelectionChanged; _selectionManager = null; } // 매핑 정리 _stageObjectToTreeItem.Clear(); } } }