#nullable enable using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// 트리 뷰 컴포넌트. /// Unity TreeView를 래핑하여 커스텀 스타일을 적용합니다. /// 계층적(부모-자식) 구조의 데이터를 접기/펼치기로 표시합니다. /// /// /// TreeView(트리 뷰)란? /// /// TreeView는 계층 구조(부모-자식 관계)를 가진 데이터를 표시하는 UI 컴포넌트입니다. /// 폴더 구조, 조직도, 메뉴 계층, 게임 오브젝트 계층 등을 표현할 때 사용합니다. /// 각 노드는 펼치거나 접을 수 있어 복잡한 구조를 효율적으로 탐색할 수 있습니다. /// /// /// ListView vs TreeView: /// /// ListView - 단순 목록 (1차원 데이터) /// TreeView - 계층 구조 (부모-자식, 중첩 가능) /// /// /// TreeViewItemData 구조: /// /// TreeViewItemData(int id, T data, List> children) /// - id: 고유 식별자 (필수, 중복 불가) /// - data: 노드에 저장할 데이터 /// - children: 자식 노드 목록 (없으면 리프 노드) /// /// /// 필수 설정: /// /// SetRootItems() - 루트 노드 설정 /// makeItem - 항목 VisualElement 생성 함수 /// bindItem - 데이터를 요소에 바인딩하는 함수 /// /// /// 주요 메서드: /// /// SetRootItems() - 전체 트리 데이터 설정 /// GetItemDataForIndex(index) - 인덱스로 데이터 조회 /// ExpandItem(id) - 특정 노드 펼치기 /// CollapseItem(id) - 특정 노드 접기 /// ExpandAll() - 모든 노드 펼치기 /// CollapseAll() - 모든 노드 접기 /// /// /// 실제 활용 예시: /// /// 파일 탐색기 - 폴더/파일 계층 구조 /// Unity Hierarchy - 게임 오브젝트 계층 /// 조직도 - 부서/팀/직원 구조 /// 메뉴 시스템 - 다단계 메뉴 /// 카테고리 - 대분류/중분류/소분류 /// /// /// /// C# 코드에서 사용: /// /// // 트리 뷰 생성 /// var treeView = new UTKTreeView(); /// treeView.fixedItemHeight = 24; /// /// // 계층 데이터 구성 /// var rootItems = new List> /// { /// new TreeViewItemData(0, "프로젝트", new List> /// { /// new TreeViewItemData(1, "Assets", new List> /// { /// new TreeViewItemData(2, "Scripts"), /// new TreeViewItemData(3, "Prefabs"), /// new TreeViewItemData(4, "Materials") /// }), /// new TreeViewItemData(5, "Packages") /// }), /// new TreeViewItemData(6, "Settings") /// }; /// /// treeView.SetRootItems(rootItems); /// /// // 항목 생성 함수 /// treeView.makeItem = () => new Label(); /// /// // 데이터 바인딩 함수 /// treeView.bindItem = (element, index) => { /// var data = treeView.GetItemDataForIndex(index); /// (element as Label).text = data; /// }; /// /// // 선택 이벤트 /// treeView.OnItemSelected += (index) => { /// var selectedData = treeView.GetItemDataForIndex(index); /// Debug.Log($"선택됨: {selectedData}"); /// }; /// /// // 더블클릭 이벤트 (폴더 열기 등) /// treeView.OnItemDoubleClicked += (index) => { /// var data = treeView.GetItemDataForIndex(index); /// OpenFolder(data); /// }; /// /// // 특정 노드 펼치기 /// treeView.ExpandItem(0); // ID 0인 노드 펼치기 /// /// // 모든 노드 펼치기 /// treeView.ExpandAll(); /// /// UXML에서 사용: /// /// /// /// /// /// /// /// [UxmlElement] public partial class UTKTreeView : TreeView, IDisposable { #region Constants private const string USS_PATH = "UIToolkit/List/UTKTreeView"; #endregion #region Fields private bool _disposed; #endregion #region Events /// 아이템 선택 이벤트 public event Action? OnItemSelected; /// 아이템 더블클릭 이벤트 public event Action? OnItemDoubleClicked; #endregion #region Constructor public UTKTreeView() : base() { UTKThemeManager.Instance.ApplyThemeToElement(this); var uss = Resources.Load(USS_PATH); if (uss != null) { styleSheets.Add(uss); } SetupStyles(); SetupEvents(); SubscribeToThemeChanges(); } #endregion #region Setup private void SetupStyles() { AddToClassList("utk-treeview"); } private void SetupEvents() { selectionChanged += OnSelectionChanged; itemsChosen += OnItemsChosen; } private void SubscribeToThemeChanges() { UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged; RegisterCallback(OnAttachToPanelForTheme); RegisterCallback(OnDetachFromPanelForTheme); } private void OnAttachToPanelForTheme(AttachToPanelEvent evt) { UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged; UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged; UTKThemeManager.Instance.ApplyThemeToElement(this); } private void OnDetachFromPanelForTheme(DetachFromPanelEvent evt) { UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged; } private void OnThemeChanged(UTKTheme theme) { UTKThemeManager.Instance.ApplyThemeToElement(this); } #endregion #region Event Handlers private void OnSelectionChanged(IEnumerable items) { OnItemSelected?.Invoke(selectedIndex); } private void OnItemsChosen(IEnumerable items) { OnItemDoubleClicked?.Invoke(selectedIndex); } #endregion #region IDisposable public void Dispose() { if (_disposed) return; _disposed = true; selectionChanged -= OnSelectionChanged; itemsChosen -= OnItemsChosen; UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged; UnregisterCallback(OnAttachToPanelForTheme); UnregisterCallback(OnDetachFromPanelForTheme); OnItemSelected = null; OnItemDoubleClicked = null; } #endregion } }