UTKComponentList, UTKTreeList 완료
This commit is contained in:
1481
Assets/Scripts/UVC/UIToolkit/List/UTKComponentList.cs
Normal file
1481
Assets/Scripts/UVC/UIToolkit/List/UTKComponentList.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eae6cfc99d965024bb62ed28a883b8a8
|
||||
135
Assets/Scripts/UVC/UIToolkit/List/UTKComponentListItemData.cs
Normal file
135
Assets/Scripts/UVC/UIToolkit/List/UTKComponentListItemData.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UVC.UIToolkit.List
|
||||
{
|
||||
/// <summary>
|
||||
/// 트리 구조 리스트의 개별 항목 데이터를 나타내는 클래스입니다.
|
||||
///
|
||||
/// <para><b>개요:</b></para>
|
||||
/// <para>
|
||||
/// UTKComponentListItemData는 계층적 트리 구조에서 각 노드(항목)를 표현합니다.
|
||||
/// 부모-자식 관계를 통해 중첩된 트리 구조를 구성할 수 있으며,
|
||||
/// UTKComponentListWindow(UI)에서 사용됩니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>주요 용도:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item>3D glTF 모델의 계층 구조를 UI 트리로 표현</item>
|
||||
/// <item>트리 리스트에서 항목 선택/확장/가시성 상태 관리</item>
|
||||
/// <item>모델 뷰어와 트리 리스트 간의 데이터 동기화</item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>사용 예시:</b></para>
|
||||
/// <code>
|
||||
/// // 루트 항목 생성
|
||||
/// var root = new UTKComponentListItemData { id = 1, name = "루트" };
|
||||
///
|
||||
/// // 자식 항목 추가
|
||||
/// var child1 = new UTKComponentListItemData { id = 2, name = "자식1" };
|
||||
/// var child2 = new UTKComponentListItemData { id = 3, name = "자식2" };
|
||||
/// root.Add(child1);
|
||||
/// root.Add(child2);
|
||||
///
|
||||
/// // 손자 항목 추가
|
||||
/// var grandChild = new UTKComponentListItemData { id = 4, name = "손자" };
|
||||
/// child1.Add(grandChild);
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public class UTKComponentListItemData: IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 항목의 고유 식별자입니다.
|
||||
/// TreeView에서 항목을 구분하고 선택하는 데 사용됩니다.
|
||||
/// 일반적으로 UTKComponentList.SetData() 호출 시 자동으로 할당됩니다.
|
||||
/// </summary>
|
||||
public int id = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 항목의 표시 이름입니다.
|
||||
/// UI의 TreeView에서 사용자에게 보여지는 텍스트입니다.
|
||||
/// glTF 모델 로드 시 GameObject 이름이 자동으로 설정됩니다.
|
||||
/// </summary>
|
||||
public string name = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 추가 옵션 또는 설명 텍스트입니다.
|
||||
/// 검색 필터링 시 name과 함께 검색 대상이 됩니다.
|
||||
/// </summary>
|
||||
public string option = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 트리뷰에서 해당 항목이 펼쳐져 있는지 여부입니다.
|
||||
/// true: 자식 항목들이 표시됨 / false: 자식 항목들이 숨겨짐
|
||||
/// </summary>
|
||||
public bool isExpanded = true;
|
||||
|
||||
/// <summary>
|
||||
/// 해당 항목이 현재 선택되어 있는지 여부입니다.
|
||||
/// 선택 상태가 변경되면 UTKComponentList.OnSelectionChanged 이벤트가 발생합니다.
|
||||
/// </summary>
|
||||
public bool isSelected = false;
|
||||
|
||||
/// <summary>
|
||||
/// 부모 항목에 대한 참조입니다.
|
||||
/// 루트 항목의 경우 null입니다.
|
||||
/// Add() 메서드로 자식을 추가하면 자동으로 설정됩니다.
|
||||
/// </summary>
|
||||
public UTKComponentListItemData? parent;
|
||||
|
||||
/// <summary>
|
||||
/// 자식 항목들의 리스트입니다.
|
||||
/// 재귀적 트리 구조를 형성합니다.
|
||||
/// Add() 메서드를 통해 자식을 추가할 수 있습니다.
|
||||
/// </summary>
|
||||
public List<UTKComponentListItemData> children = new List<UTKComponentListItemData>();
|
||||
|
||||
/// <summary>
|
||||
/// 해당 항목(및 연결된 3D 오브젝트)의 가시성 상태입니다.
|
||||
/// UI의 눈 아이콘 버튼으로 토글되며,
|
||||
/// 변경 시 UTKComponentListWindow.OnVisibilityChanged 이벤트를 통해
|
||||
/// 3D 모델의 GameObject.SetActive()가 호출됩니다.
|
||||
/// </summary>
|
||||
public bool IsVisible = true;
|
||||
|
||||
/// <summary>
|
||||
/// 외부 시스템(예: 간트 차트)과의 연동을 위한 외부 키입니다.
|
||||
/// 검색 필터링 시 검색 대상에 포함됩니다.
|
||||
/// 모델과 차트 간의 동기화에 사용될 수 있습니다.
|
||||
/// </summary>
|
||||
public string ExternalKey = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 검색 결과를 감싸는 임시 그룹 항목인지 여부입니다.
|
||||
/// true인 경우 setting-btn이 숨겨지고 클릭되지 않습니다.
|
||||
/// 검색 시 자동 생성되며, 검색 해제 시 제거됩니다.
|
||||
/// </summary>
|
||||
public bool isSearchResultGroup = false;
|
||||
|
||||
/// <summary>
|
||||
/// 자식 항목을 추가합니다.
|
||||
/// 추가되는 자식의 parent 속성이 자동으로 현재 항목으로 설정됩니다.
|
||||
/// </summary>
|
||||
/// <param name="child">추가할 자식 항목</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var parent = new UTKComponentListItemData { name = "부모" };
|
||||
/// var child = new UTKComponentListItemData { name = "자식" };
|
||||
/// parent.Add(child);
|
||||
/// // child.parent == parent (자동 설정됨)
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void Add(UTKComponentListItemData child)
|
||||
{
|
||||
child.parent = this;
|
||||
children.Add(child);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
children.Clear();
|
||||
parent = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f72bc175f9d7aba45aa08ca58319bf06
|
||||
289
Assets/Scripts/UVC/UIToolkit/Window/UTKComponentListWindow.cs
Normal file
289
Assets/Scripts/UVC/UIToolkit/Window/UTKComponentListWindow.cs
Normal file
@@ -0,0 +1,289 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit.List;
|
||||
|
||||
namespace UVC.UIToolkit.Window
|
||||
{
|
||||
/// <summary>
|
||||
/// UTKComponentList를 래핑하여 윈도우 형태로 제공하는 컴포넌트입니다.
|
||||
///
|
||||
/// <para><b>개요:</b></para>
|
||||
/// <para>
|
||||
/// UTKComponentListWindow는 UTKComponentList를 내부에 포함하고 헤더(타이틀, 닫기 버튼)를 추가한
|
||||
/// 윈도우 형태의 컴포넌트입니다. 모든 트리 관련 기능은 내부 UTKComponentList에 위임됩니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>UXML에서 사용:</b></para>
|
||||
/// <code>
|
||||
/// <UVC.UIToolkit.Window.UTKComponentListWindow name="tree-list-window" />
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>코드에서 사용:</b></para>
|
||||
/// <code>
|
||||
/// var window = root.Q<UTKComponentListWindow>();
|
||||
/// window.OnItemSelected += (items) => Debug.Log($"선택: {items[0].name}");
|
||||
/// window.SetData(treeItems);
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKComponentListWindow : VisualElement, IDisposable
|
||||
{
|
||||
#region IDisposable
|
||||
private bool _disposed = false;
|
||||
#endregion
|
||||
|
||||
#region 상수 (Constants)
|
||||
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
|
||||
private const string UXML_PATH = "UIToolkit/Window/UTKComponentListWindow";
|
||||
#endregion
|
||||
|
||||
#region UI 컴포넌트 참조 (UI Component References)
|
||||
/// <summary>내부 UTKComponentList 컴포넌트</summary>
|
||||
private UTKComponentList? _componentList;
|
||||
|
||||
/// <summary>트리 리스트 닫기 버튼</summary>
|
||||
private Button? _closeButton;
|
||||
#endregion
|
||||
|
||||
#region 공개 속성 (Public Properties)
|
||||
/// <summary>
|
||||
/// 항목 삭제 기능 활성화 여부입니다.
|
||||
/// true일 때만 Delete/Backspace 키로 항목 삭제 이벤트가 발생합니다.
|
||||
/// 기본값은 false입니다.
|
||||
/// </summary>
|
||||
public bool EnabledDeleteItem
|
||||
{
|
||||
get => _componentList?.EnabledDeleteItem ?? false;
|
||||
set { if (_componentList != null) _componentList.EnabledDeleteItem = value; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 외부 이벤트 (Public Events)
|
||||
/// <summary>
|
||||
/// 메인/검색 리스트에서 항목이 선택될 때 발생합니다.
|
||||
/// </summary>
|
||||
public Action<List<UTKComponentListItemData>>? OnItemSelected
|
||||
{
|
||||
get => _componentList?.OnItemSelected;
|
||||
set { if (_componentList != null) _componentList.OnItemSelected = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메인/검색 리스트에서 항목이 선택 해제될 때 발생합니다.
|
||||
/// </summary>
|
||||
public Action<List<UTKComponentListItemData>>? OnItemDeselected
|
||||
{
|
||||
get => _componentList?.OnItemDeselected;
|
||||
set { if (_componentList != null) _componentList.OnItemDeselected = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 항목의 가시성(눈 아이콘)이 변경될 때 발생합니다.
|
||||
/// </summary>
|
||||
public event Action<UTKComponentListItemData, bool>? OnItemVisibilityChanged
|
||||
{
|
||||
add { if (_componentList != null) _componentList.OnItemVisibilityChanged += value; }
|
||||
remove { if (_componentList != null) _componentList.OnItemVisibilityChanged -= value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메인/검색 리스트에서 항목이 삭제될 때 발생합니다 (Delete 키).
|
||||
/// </summary>
|
||||
public Action<UTKComponentListItemData>? OnItemDeleted
|
||||
{
|
||||
get => _componentList?.OnItemDeleted;
|
||||
set { if (_componentList != null) _componentList.OnItemDeleted = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메인/검색 리스트에서 항목이 더블클릭될 때 발생합니다.
|
||||
/// </summary>
|
||||
public Action<UTKComponentListItemData>? OnItemDoubleClicked
|
||||
{
|
||||
get => _componentList?.OnItemDoubleClicked;
|
||||
set { if (_componentList != null) _componentList.OnItemDoubleClicked = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이콘을 클릭할 때 발생합니다.
|
||||
/// </summary>
|
||||
public Action<string, UTKComponentListItemData>? OnItemIconClicked
|
||||
{
|
||||
get => _componentList?.OnItemIconClicked;
|
||||
set { if (_componentList != null) _componentList.OnItemIconClicked = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 트리 리스트가 닫힐 때(숨겨질 때) 발생합니다.
|
||||
/// 닫기 버튼 클릭 시 트리거됩니다.
|
||||
/// </summary>
|
||||
public event Action? OnClosed;
|
||||
#endregion
|
||||
|
||||
#region 생성자 (Constructor)
|
||||
/// <summary>
|
||||
/// UTKComponentListWindow 컴포넌트를 초기화합니다.
|
||||
/// UXML 템플릿을 로드하고 내부 UTKComponentList를 설정합니다.
|
||||
/// </summary>
|
||||
public UTKComponentListWindow()
|
||||
{
|
||||
// 1. 메인 UXML 로드 및 복제
|
||||
var visualTree = Resources.Load<VisualTreeAsset>(UXML_PATH);
|
||||
if (visualTree == null)
|
||||
{
|
||||
Debug.LogError($"[UTKComponentListWindow] UXML not found at: {UXML_PATH}");
|
||||
return;
|
||||
}
|
||||
visualTree.CloneTree(this);
|
||||
|
||||
// 2. 내부 UTKComponentList 찾기 (UXML의 ui:Instance로 생성된 컴포넌트)
|
||||
_componentList = this.Q<UTKComponentList>();
|
||||
if (_componentList == null)
|
||||
{
|
||||
Debug.LogError("[UTKComponentListWindow] UTKComponentList not found in UXML");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 닫기 버튼 찾기 및 이벤트 연결
|
||||
_closeButton = this.Q<Button>("close-btn");
|
||||
if (_closeButton != null)
|
||||
{
|
||||
_closeButton.clicked += () =>
|
||||
{
|
||||
this.style.display = DisplayStyle.None;
|
||||
OnClosed?.Invoke();
|
||||
};
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 공개 메서드 (Public Methods)
|
||||
/// <summary>
|
||||
/// 트리 리스트를 화면에 표시합니다.
|
||||
/// </summary>
|
||||
public void Show()
|
||||
{
|
||||
this.style.display = DisplayStyle.Flex;
|
||||
_componentList?.Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 트리 데이터를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="roots">루트 항목들의 리스트</param>
|
||||
public void SetData(List<UTKComponentListItemData> roots)
|
||||
{
|
||||
_componentList?.SetData(roots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 ID의 항목을 프로그래밍 방식으로 선택합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">선택할 항목의 ID</param>
|
||||
public void SelectByItemId(int itemId)
|
||||
{
|
||||
_componentList?.SelectByItemId(itemId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 이름 목록에 해당하는 항목만 표시하고 나머지는 숨깁니다.
|
||||
/// </summary>
|
||||
/// <param name="items">표시할 항목들의 이름 목록</param>
|
||||
/// <param name="depth">검색 깊이</param>
|
||||
public void ShowItems(List<string> items, int depth = 1)
|
||||
{
|
||||
_componentList?.ShowItems(items, depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 루트 레벨에 새 항목을 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">추가할 항목 데이터</param>
|
||||
public void AddItem(UTKComponentListItemData data)
|
||||
{
|
||||
_componentList?.AddItem(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 부모 항목의 자식으로 새 항목을 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="parent">부모 항목</param>
|
||||
/// <param name="data">추가할 항목 데이터</param>
|
||||
public void AddItem(UTKComponentListItemData? parent, UTKComponentListItemData data)
|
||||
{
|
||||
_componentList?.AddItem(parent, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 트리에서 항목을 완전히 삭제합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">삭제할 항목 데이터</param>
|
||||
public void DeleteItem(UTKComponentListItemData data)
|
||||
{
|
||||
_componentList?.DeleteItem(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 항목의 이름을 변경하고 UI를 갱신합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">변경할 항목 데이터</param>
|
||||
/// <param name="newName">새 이름</param>
|
||||
public void SetItemName(UTKComponentListItemData data, string newName)
|
||||
{
|
||||
_componentList?.SetItemName(data, newName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이름으로 항목을 찾아 선택합니다.
|
||||
/// </summary>
|
||||
/// <param name="name">선택할 항목의 이름</param>
|
||||
/// <param name="notify">선택 이벤트 발송 여부</param>
|
||||
public void SelectItem(string name, bool notify = true)
|
||||
{
|
||||
_componentList?.SelectItem(name, notify);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이름으로 항목을 찾아 선택을 해제합니다.
|
||||
/// </summary>
|
||||
/// <param name="name">선택 해제할 항목의 이름</param>
|
||||
/// <param name="notify">선택 해제 이벤트 발송 여부</param>
|
||||
public void DeselectItem(string name, bool notify = true)
|
||||
{
|
||||
_componentList?.DeselectItem(name, notify);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 항목의 선택을 해제합니다.
|
||||
/// </summary>
|
||||
public void ClearSelection()
|
||||
{
|
||||
_componentList?.ClearSelection();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
/// <summary>
|
||||
/// 리소스를 해제하고 이벤트 핸들러를 정리합니다.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
// 내부 UTKComponentList 정리
|
||||
_componentList?.Dispose();
|
||||
_componentList = null;
|
||||
|
||||
// 외부 이벤트 구독자 정리
|
||||
OnClosed = null;
|
||||
|
||||
// UI 참조 정리
|
||||
_closeButton = null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79be44292c9ef794fa3fceb7ef6e7322
|
||||
538
Assets/Scripts/UVC/UIToolkit/Window/UTKComponentTabListWindow.cs
Normal file
538
Assets/Scripts/UVC/UIToolkit/Window/UTKComponentTabListWindow.cs
Normal file
@@ -0,0 +1,538 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit.List;
|
||||
|
||||
namespace UVC.UIToolkit.Window
|
||||
{
|
||||
/// <summary>
|
||||
/// UTKComponentList와 탭 기능을 결합하여 윈도우 형태로 제공하는 컴포넌트입니다.
|
||||
///
|
||||
/// <para><b>개요:</b></para>
|
||||
/// <para>
|
||||
/// UTKComponentTabListWindow는 UTKComponentList를 내부에 포함하고 헤더(타이틀, 닫기 버튼)와
|
||||
/// 탭 버튼들을 추가한 윈도우 형태의 컴포넌트입니다. 탭을 통해 카테고리별 필터링이 가능합니다.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><b>UXML에서 사용:</b></para>
|
||||
/// <code>
|
||||
/// <UVC.UIToolkit.Window.UTKComponentTabListWindow name="tab-list-window" />
|
||||
/// </code>
|
||||
///
|
||||
/// <para><b>코드에서 사용:</b></para>
|
||||
/// <code>
|
||||
/// var window = root.Q<UTKComponentTabListWindow>();
|
||||
/// window.OnItemSelected += (items) => Debug.Log($"선택: {items[0].name}");
|
||||
/// window.SetData(treeItems);
|
||||
/// window.SelectTab(0); // 첫 번째 카테고리 탭 선택
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKComponentTabListWindow : VisualElement, IDisposable
|
||||
{
|
||||
#region IDisposable
|
||||
private bool _disposed = false;
|
||||
#endregion
|
||||
|
||||
#region 상수 (Constants)
|
||||
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
|
||||
private const string UXML_PATH = "UIToolkit/Window/UTKComponentTabListWindow";
|
||||
|
||||
/// <summary>"전체" 탭을 나타내는 인덱스</summary>
|
||||
private const int ALL_TAB_INDEX = -1;
|
||||
#endregion
|
||||
|
||||
#region UI 컴포넌트 참조 (UI Component References)
|
||||
/// <summary>내부 UTKComponentList 컴포넌트</summary>
|
||||
private UTKComponentList? _componentList;
|
||||
|
||||
/// <summary>탭 버튼 컨테이너</summary>
|
||||
private VisualElement? _tabContainer;
|
||||
|
||||
/// <summary>트리 리스트 닫기 버튼</summary>
|
||||
private Button? _closeButton;
|
||||
#endregion
|
||||
|
||||
#region 탭 관련 데이터 (Tab Data)
|
||||
/// <summary>원본 전체 데이터</summary>
|
||||
private List<UTKComponentListItemData> _originalRoots = new();
|
||||
|
||||
/// <summary>현재 선택된 탭 인덱스 (-1: 전체)</summary>
|
||||
private int _selectedTabIndex = ALL_TAB_INDEX;
|
||||
|
||||
/// <summary>탭 버튼들</summary>
|
||||
private List<Button> _tabButtons = new();
|
||||
|
||||
/// <summary>탭별 검색어 저장 (key: 탭 인덱스, value: 검색어)</summary>
|
||||
private Dictionary<int, string> _tabSearchQueries = new();
|
||||
#endregion
|
||||
|
||||
#region 공개 속성 (Public Properties)
|
||||
/// <summary>
|
||||
/// 항목 삭제 기능 활성화 여부입니다.
|
||||
/// true일 때만 Delete/Backspace 키로 항목 삭제 이벤트가 발생합니다.
|
||||
/// 기본값은 false입니다.
|
||||
/// </summary>
|
||||
public bool EnabledDeleteItem
|
||||
{
|
||||
get => _componentList?.EnabledDeleteItem ?? false;
|
||||
set { if (_componentList != null) _componentList.EnabledDeleteItem = value; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 외부 이벤트 (Public Events)
|
||||
/// <summary>
|
||||
/// 메인/검색 리스트에서 항목이 선택될 때 발생합니다.
|
||||
/// </summary>
|
||||
public Action<List<UTKComponentListItemData>>? OnItemSelected
|
||||
{
|
||||
get => _componentList?.OnItemSelected;
|
||||
set { if (_componentList != null) _componentList.OnItemSelected = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메인/검색 리스트에서 항목이 선택 해제될 때 발생합니다.
|
||||
/// </summary>
|
||||
public Action<List<UTKComponentListItemData>>? OnItemDeselected
|
||||
{
|
||||
get => _componentList?.OnItemDeselected;
|
||||
set { if (_componentList != null) _componentList.OnItemDeselected = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 항목의 가시성(눈 아이콘)이 변경될 때 발생합니다.
|
||||
/// </summary>
|
||||
public event Action<UTKComponentListItemData, bool>? OnItemVisibilityChanged
|
||||
{
|
||||
add { if (_componentList != null) _componentList.OnItemVisibilityChanged += value; }
|
||||
remove { if (_componentList != null) _componentList.OnItemVisibilityChanged -= value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메인/검색 리스트에서 항목이 삭제될 때 발생합니다 (Delete 키).
|
||||
/// </summary>
|
||||
public Action<UTKComponentListItemData>? OnItemDeleted
|
||||
{
|
||||
get => _componentList?.OnItemDeleted;
|
||||
set { if (_componentList != null) _componentList.OnItemDeleted = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메인/검색 리스트에서 항목이 더블클릭될 때 발생합니다.
|
||||
/// </summary>
|
||||
public Action<UTKComponentListItemData>? OnItemDoubleClicked
|
||||
{
|
||||
get => _componentList?.OnItemDoubleClicked;
|
||||
set { if (_componentList != null) _componentList.OnItemDoubleClicked = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이콘을 클릭할 때 발생합니다.
|
||||
/// </summary>
|
||||
public Action<string, UTKComponentListItemData>? OnItemIconClicked
|
||||
{
|
||||
get => _componentList?.OnItemIconClicked;
|
||||
set { if (_componentList != null) _componentList.OnItemIconClicked = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 트리 리스트가 닫힐 때(숨겨질 때) 발생합니다.
|
||||
/// 닫기 버튼 클릭 시 트리거됩니다.
|
||||
/// </summary>
|
||||
public event Action? OnClosed;
|
||||
#endregion
|
||||
|
||||
#region 생성자 (Constructor)
|
||||
/// <summary>
|
||||
/// UTKComponentTabListWindow 컴포넌트를 초기화합니다.
|
||||
/// UXML 템플릿을 로드하고 내부 UTKComponentList와 탭을 설정합니다.
|
||||
/// </summary>
|
||||
public UTKComponentTabListWindow()
|
||||
{
|
||||
// 1. 메인 UXML 로드 및 복제
|
||||
var visualTree = Resources.Load<VisualTreeAsset>(UXML_PATH);
|
||||
if (visualTree == null)
|
||||
{
|
||||
Debug.LogError($"[UTKComponentTabListWindow] UXML not found at: {UXML_PATH}");
|
||||
return;
|
||||
}
|
||||
visualTree.CloneTree(this);
|
||||
|
||||
// 2. 내부 UTKComponentList 찾기 (UXML의 커스텀 요소로 생성된 컴포넌트)
|
||||
_componentList = this.Q<UTKComponentList>();
|
||||
if (_componentList == null)
|
||||
{
|
||||
Debug.LogError("[UTKComponentTabListWindow] UTKComponentList not found in UXML");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 탭 관련 요소 찾기
|
||||
_tabContainer = this.Q<VisualElement>("tab-container");
|
||||
|
||||
// 4. 닫기 버튼 찾기 및 이벤트 연결
|
||||
_closeButton = this.Q<Button>("close-btn");
|
||||
if (_closeButton != null)
|
||||
{
|
||||
_closeButton.clicked += () =>
|
||||
{
|
||||
this.style.display = DisplayStyle.None;
|
||||
OnClosed?.Invoke();
|
||||
};
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 공개 메서드 (Public Methods)
|
||||
/// <summary>
|
||||
/// 트리 리스트를 화면에 표시합니다.
|
||||
/// </summary>
|
||||
public void Show()
|
||||
{
|
||||
this.style.display = DisplayStyle.Flex;
|
||||
_componentList?.Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 트리 데이터를 설정합니다.
|
||||
/// 데이터의 카테고리를 분석하여 탭을 자동 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="roots">루트 항목들의 리스트</param>
|
||||
public void SetData(List<UTKComponentListItemData> roots)
|
||||
{
|
||||
_originalRoots = roots ?? new List<UTKComponentListItemData>();
|
||||
|
||||
// 탭 생성 (카테고리 기반)
|
||||
CreateTabs();
|
||||
|
||||
// 전체 탭 선택 (기본값)
|
||||
SelectTab(ALL_TAB_INDEX);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 탭을 선택하고 해당 데이터를 TreeView에 표시합니다.
|
||||
/// 이전 탭의 검색어를 저장하고, 새 탭의 검색어를 복원합니다.
|
||||
/// </summary>
|
||||
/// <param name="tabIndex">선택할 탭 인덱스 (-1: 전체, 0~: 카테고리)</param>
|
||||
public void SelectTab(int tabIndex)
|
||||
{
|
||||
// 1. 이전 탭의 검색어 저장
|
||||
if (_componentList != null)
|
||||
{
|
||||
var currentQuery = _componentList.SearchQuery;
|
||||
if (!string.IsNullOrEmpty(currentQuery))
|
||||
{
|
||||
_tabSearchQueries[_selectedTabIndex] = currentQuery;
|
||||
}
|
||||
else
|
||||
{
|
||||
_tabSearchQueries.Remove(_selectedTabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 탭 인덱스 변경 및 스타일 업데이트
|
||||
_selectedTabIndex = tabIndex;
|
||||
UpdateTabStyles();
|
||||
|
||||
// 3. 데이터 필터링 및 표시
|
||||
var filteredData = FilterDataByTab(tabIndex);
|
||||
_componentList?.SetData(filteredData);
|
||||
|
||||
// 4. 새 탭의 검색어 복원 및 적용
|
||||
if (_componentList != null)
|
||||
{
|
||||
if (_tabSearchQueries.TryGetValue(tabIndex, out var savedQuery) && !string.IsNullOrEmpty(savedQuery))
|
||||
{
|
||||
_componentList.ApplySearch(savedQuery);
|
||||
}
|
||||
else
|
||||
{
|
||||
_componentList.ApplySearch(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 ID의 항목을 프로그래밍 방식으로 선택합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">선택할 항목의 ID</param>
|
||||
public void SelectByItemId(int itemId)
|
||||
{
|
||||
_componentList?.SelectByItemId(itemId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 이름 목록에 해당하는 항목만 표시하고 나머지는 숨깁니다.
|
||||
/// </summary>
|
||||
/// <param name="items">표시할 항목들의 이름 목록</param>
|
||||
/// <param name="depth">검색 깊이</param>
|
||||
public void ShowItems(List<string> items, int depth = 1)
|
||||
{
|
||||
_componentList?.ShowItems(items, depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 루트 레벨에 새 항목을 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">추가할 항목 데이터</param>
|
||||
public void AddItem(UTKComponentListItemData data)
|
||||
{
|
||||
_originalRoots.Add(data);
|
||||
CreateTabs();
|
||||
SelectTab(_selectedTabIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 부모 항목의 자식으로 새 항목을 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="parent">부모 항목</param>
|
||||
/// <param name="data">추가할 항목 데이터</param>
|
||||
public void AddItem(UTKComponentListItemData? parent, UTKComponentListItemData data)
|
||||
{
|
||||
if (parent == null)
|
||||
{
|
||||
AddItem(data);
|
||||
return;
|
||||
}
|
||||
|
||||
parent.children ??= new List<UTKComponentListItemData>();
|
||||
parent.children.Add(data);
|
||||
|
||||
// 탭 및 표시 갱신
|
||||
CreateTabs();
|
||||
SelectTab(_selectedTabIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 트리에서 항목을 완전히 삭제합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">삭제할 항목 데이터</param>
|
||||
public void DeleteItem(UTKComponentListItemData data)
|
||||
{
|
||||
if (RemoveItemFromRoots(data))
|
||||
{
|
||||
CreateTabs();
|
||||
SelectTab(_selectedTabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 항목의 이름을 변경하고 UI를 갱신합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">변경할 항목 데이터</param>
|
||||
/// <param name="newName">새 이름</param>
|
||||
public void SetItemName(UTKComponentListItemData data, string newName)
|
||||
{
|
||||
_componentList?.SetItemName(data, newName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이름으로 항목을 찾아 선택합니다.
|
||||
/// </summary>
|
||||
/// <param name="name">선택할 항목의 이름</param>
|
||||
/// <param name="notify">선택 이벤트 발송 여부</param>
|
||||
public void SelectItem(string name, bool notify = true)
|
||||
{
|
||||
_componentList?.SelectItem(name, notify);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이름으로 항목을 찾아 선택을 해제합니다.
|
||||
/// </summary>
|
||||
/// <param name="name">선택 해제할 항목의 이름</param>
|
||||
/// <param name="notify">선택 해제 이벤트 발송 여부</param>
|
||||
public void DeselectItem(string name, bool notify = true)
|
||||
{
|
||||
_componentList?.DeselectItem(name, notify);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 항목의 선택을 해제합니다.
|
||||
/// </summary>
|
||||
public void ClearSelection()
|
||||
{
|
||||
_componentList?.ClearSelection();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 탭 관리 (Tab Management)
|
||||
/// <summary>
|
||||
/// 데이터의 카테고리를 분석하여 탭 버튼을 생성합니다.
|
||||
/// </summary>
|
||||
private void CreateTabs()
|
||||
{
|
||||
if (_tabContainer == null) return;
|
||||
|
||||
// 기존 탭 제거
|
||||
_tabContainer.Clear();
|
||||
_tabButtons.Clear();
|
||||
|
||||
// "전체" 탭 생성
|
||||
var allTab = CreateTabButton("All", ALL_TAB_INDEX);
|
||||
_tabContainer.Add(allTab);
|
||||
_tabButtons.Add(allTab);
|
||||
|
||||
// 카테고리별 탭 생성 (루트 항목들이 그룹이면 해당 이름으로 탭 생성)
|
||||
var categories = _originalRoots
|
||||
.Where(r => r.children != null && r.children.Count > 0)
|
||||
.Select(r => r.name)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < categories.Count; i++)
|
||||
{
|
||||
var categoryTab = CreateTabButton(categories[i], i);
|
||||
_tabContainer.Add(categoryTab);
|
||||
_tabButtons.Add(categoryTab);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 탭 버튼을 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="label">탭 레이블</param>
|
||||
/// <param name="index">탭 인덱스</param>
|
||||
/// <returns>생성된 버튼</returns>
|
||||
private Button CreateTabButton(string label, int index)
|
||||
{
|
||||
var button = new Button(() => SelectTab(index))
|
||||
{
|
||||
text = label
|
||||
};
|
||||
button.AddToClassList("tab-button");
|
||||
return button;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 탭의 스타일을 업데이트합니다.
|
||||
/// </summary>
|
||||
private void UpdateTabStyles()
|
||||
{
|
||||
for (int i = 0; i < _tabButtons.Count; i++)
|
||||
{
|
||||
var btn = _tabButtons[i];
|
||||
// 인덱스 0 = "전체" 탭 (ALL_TAB_INDEX = -1)
|
||||
// 인덱스 1~ = 카테고리 탭 (0~)
|
||||
int tabIndex = i == 0 ? ALL_TAB_INDEX : i - 1;
|
||||
|
||||
if (tabIndex == _selectedTabIndex)
|
||||
{
|
||||
btn.AddToClassList("tab-button-selected");
|
||||
}
|
||||
else
|
||||
{
|
||||
btn.RemoveFromClassList("tab-button-selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 탭에 따라 데이터를 필터링합니다.
|
||||
/// </summary>
|
||||
/// <param name="tabIndex">탭 인덱스 (-1: 전체)</param>
|
||||
/// <returns>필터링된 데이터</returns>
|
||||
private List<UTKComponentListItemData> FilterDataByTab(int tabIndex)
|
||||
{
|
||||
// 전체 탭이면 모든 데이터 반환
|
||||
if (tabIndex == ALL_TAB_INDEX)
|
||||
{
|
||||
return _originalRoots;
|
||||
}
|
||||
|
||||
// 카테고리 탭이면 해당 카테고리의 자식들만 반환 (부모 없이 자식만 표시)
|
||||
var categories = _originalRoots
|
||||
.Where(r => r.children != null && r.children.Count > 0)
|
||||
.ToList();
|
||||
|
||||
if (tabIndex >= 0 && tabIndex < categories.Count)
|
||||
{
|
||||
return categories[tabIndex].children ?? new List<UTKComponentListItemData>();
|
||||
}
|
||||
|
||||
return _originalRoots;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 내부 헬퍼 메서드 (Internal Helper Methods)
|
||||
/// <summary>
|
||||
/// 원본 데이터에서 항목을 재귀적으로 삭제합니다.
|
||||
/// </summary>
|
||||
/// <param name="target">삭제할 항목</param>
|
||||
/// <returns>삭제 성공 여부</returns>
|
||||
private bool RemoveItemFromRoots(UTKComponentListItemData target)
|
||||
{
|
||||
if (_originalRoots.Remove(target))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var root in _originalRoots)
|
||||
{
|
||||
if (RemoveFromChildren(root, target))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 부모 노드의 자식들에서 대상 항목을 재귀적으로 삭제합니다.
|
||||
/// </summary>
|
||||
/// <param name="parent">부모 노드</param>
|
||||
/// <param name="target">삭제할 대상 항목</param>
|
||||
/// <returns>삭제 성공 여부</returns>
|
||||
private bool RemoveFromChildren(UTKComponentListItemData parent, UTKComponentListItemData target)
|
||||
{
|
||||
if (parent.children == null || parent.children.Count == 0) return false;
|
||||
|
||||
if (parent.children.Remove(target))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var child in parent.children)
|
||||
{
|
||||
if (RemoveFromChildren(child, target))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
/// <summary>
|
||||
/// 리소스를 해제하고 이벤트 핸들러를 정리합니다.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
// 내부 UTKComponentList 정리
|
||||
_componentList?.Dispose();
|
||||
_componentList = null;
|
||||
|
||||
// 탭 버튼 정리
|
||||
_tabButtons.Clear();
|
||||
_tabContainer = null;
|
||||
|
||||
// 외부 이벤트 구독자 정리
|
||||
OnClosed = null;
|
||||
|
||||
// 데이터 정리
|
||||
_originalRoots.Clear();
|
||||
_tabSearchQueries.Clear();
|
||||
|
||||
// UI 참조 정리
|
||||
_closeButton = null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a75f5e337fff0364db73c575f1716d4d
|
||||
@@ -44,7 +44,14 @@ namespace UVC.UIToolkit.Window
|
||||
/// <item>Resources/UIToolkit/Window/UTKTreeListWindow.uxml - 메인 레이아웃</item>
|
||||
/// <item>Resources/UIToolkit/List/UTKTreeListItem.uxml - 개별 항목 템플릿</item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>선택 해제 방지:</b></para>
|
||||
/// <para>
|
||||
/// 빈 영역 클릭 시 선택이 해제되지 않도록 하려면 EventSystem의 InputSystemUIInputModule 컴포넌트에서
|
||||
/// <c>Deselect On Background Click</c> 옵션을 해제해야 합니다.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKTreeListWindow : VisualElement, IDisposable
|
||||
{
|
||||
#region IDisposable
|
||||
@@ -102,6 +109,21 @@ namespace UVC.UIToolkit.Window
|
||||
/// 프로그래밍 방식으로 선택 시 이벤트를 발송하지 않으려면 true로 설정합니다.
|
||||
/// </summary>
|
||||
private bool _suppressSelectionEvent = false;
|
||||
|
||||
/// <summary>
|
||||
/// 펼침/접힘 이벤트 처리를 일시적으로 억제하는 플래그입니다.
|
||||
/// ExpandByData() 실행 중 이벤트로 인한 데이터 덮어쓰기를 방지합니다.
|
||||
/// </summary>
|
||||
private bool _suppressExpandEvent = false;
|
||||
#endregion
|
||||
|
||||
#region 공개 속성 (Public Properties)
|
||||
/// <summary>
|
||||
/// 항목 삭제 기능 활성화 여부입니다.
|
||||
/// true일 때만 Delete/Backspace 키로 항목 삭제 이벤트가 발생합니다.
|
||||
/// 기본값은 true입니다.
|
||||
/// </summary>
|
||||
public bool EnabledDeleteItem { get; set; } = true;
|
||||
#endregion
|
||||
|
||||
#region 외부 이벤트 (Public Events)
|
||||
@@ -159,7 +181,7 @@ namespace UVC.UIToolkit.Window
|
||||
// 2. 자식 요소 참조 획득 (UXML의 name 속성으로 찾음)
|
||||
_searchField = this.Q<TextField>("search-field");
|
||||
_treeView = this.Q<TreeView>("main-tree-view");
|
||||
_closeButton = this.Q<Button>("hide-btn");
|
||||
_closeButton = this.Q<Button>("close-btn");
|
||||
_clearButton = this.Q<Button>("clear-btn");
|
||||
|
||||
// 3. 이벤트 연결 및 로직 초기화
|
||||
@@ -173,10 +195,11 @@ namespace UVC.UIToolkit.Window
|
||||
/// </summary>
|
||||
private void InitializeLogic()
|
||||
{
|
||||
// 검색창 이벤트: 입력 값이 변경될 때마다 필터링 실행
|
||||
// 검색창 이벤트: Enter 키를 눌렀을 때 또는 포커스를 잃었을 때 필터링 실행
|
||||
if (_searchField != null)
|
||||
{
|
||||
_searchField.RegisterValueChangedCallback(OnSearchValueChanged);
|
||||
_searchField.RegisterCallback<KeyDownEvent>(OnSearchFieldKeyDown);
|
||||
_searchField.RegisterCallback<FocusOutEvent>(OnSearchFieldFocusOut);
|
||||
}
|
||||
|
||||
// TreeView 설정
|
||||
@@ -188,6 +211,7 @@ namespace UVC.UIToolkit.Window
|
||||
_treeView.bindItem = BindTreeItem;
|
||||
_treeView.selectionChanged += OnTreeViewSelectionChanged;
|
||||
_treeView.itemsChosen += OnTreeViewItemsChosen;
|
||||
_treeView.itemExpandedChanged += OnTreeViewItemExpandedChanged;
|
||||
_treeView.RegisterCallback<KeyDownEvent>(OnTreeViewKeyDown);
|
||||
}
|
||||
|
||||
@@ -356,9 +380,9 @@ namespace UVC.UIToolkit.Window
|
||||
// TreeView에 데이터 설정
|
||||
_treeView!.SetRootItems<UTKTreeListItemData>(_rootData);
|
||||
|
||||
// UI 갱신 및 모든 항목 펼치기
|
||||
// UI 갱신 및 데이터의 isExpanded에 따라 항목 펼치기
|
||||
_treeView!.Rebuild();
|
||||
_treeView!.ExpandAll();
|
||||
ExpandByData(_originalRoots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -418,7 +442,7 @@ namespace UVC.UIToolkit.Window
|
||||
// TreeView 갱신
|
||||
_treeView?.SetRootItems(_rootData);
|
||||
_treeView?.Rebuild();
|
||||
if (_treeView != null && _treeView.autoExpand) _treeView.ExpandAll();
|
||||
if (_treeView != null && _treeView.autoExpand) ExpandByData(_originalRoots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -623,7 +647,7 @@ namespace UVC.UIToolkit.Window
|
||||
_rootData = ConvertToTreeViewData(_originalRoots, visited, 0);
|
||||
_treeView?.SetRootItems(_rootData);
|
||||
_treeView?.Rebuild();
|
||||
_treeView?.ExpandAll();
|
||||
if (_treeView != null && _treeView.autoExpand) ExpandByData(_originalRoots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -779,6 +803,66 @@ namespace UVC.UIToolkit.Window
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 확장/축소 처리 (Expand/Collapse Handling)
|
||||
/// <summary>
|
||||
/// TreeView의 항목 확장/축소 이벤트를 처리합니다.
|
||||
/// 데이터의 isExpanded 값을 동기화합니다.
|
||||
/// </summary>
|
||||
/// <param name="evt">확장 변경 이벤트 인자</param>
|
||||
private void OnTreeViewItemExpandedChanged(TreeViewExpansionChangedArgs evt)
|
||||
{
|
||||
// ExpandByData() 실행 중에는 이벤트 무시 (데이터 덮어쓰기 방지)
|
||||
if (_suppressExpandEvent) return;
|
||||
|
||||
var item = _treeView?.GetItemDataForId<UTKTreeListItemData>(evt.id);
|
||||
if (item != null)
|
||||
{
|
||||
item.isExpanded = evt.isExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 데이터의 isExpanded 속성에 따라 항목을 펼치거나 접습니다.
|
||||
/// </summary>
|
||||
/// <param name="items">처리할 항목 리스트</param>
|
||||
private void ExpandByData(List<UTKTreeListItemData> items)
|
||||
{
|
||||
if (_treeView == null || items == null) return;
|
||||
|
||||
// 이벤트 억제 시작 (재귀 호출 고려하여 최상위에서만 설정)
|
||||
bool wasSupressed = _suppressExpandEvent;
|
||||
_suppressExpandEvent = true;
|
||||
|
||||
try
|
||||
{
|
||||
ExpandByDataInternal(items);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressExpandEvent = wasSupressed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ExpandByData의 내부 재귀 구현입니다.
|
||||
/// </summary>
|
||||
private void ExpandByDataInternal(List<UTKTreeListItemData> items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.children != null && item.children.Count > 0)
|
||||
{
|
||||
if (item.isExpanded)
|
||||
_treeView!.ExpandItem(item.id);
|
||||
else
|
||||
_treeView!.CollapseItem(item.id);
|
||||
|
||||
ExpandByDataInternal(item.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 선택 처리 (Selection Handling)
|
||||
/// <summary>
|
||||
/// TreeView의 선택 변경 이벤트를 처리합니다.
|
||||
@@ -856,6 +940,9 @@ namespace UVC.UIToolkit.Window
|
||||
/// <param name="evt">키 입력 이벤트</param>
|
||||
private void OnTreeViewKeyDown(KeyDownEvent evt)
|
||||
{
|
||||
// EnabledDeleteItem이 false이면 삭제 키 무시
|
||||
if (!EnabledDeleteItem) return;
|
||||
|
||||
if (evt.keyCode == KeyCode.Delete || evt.keyCode == KeyCode.Backspace)
|
||||
{
|
||||
if (_previouslySelectedItems.Count > 0)
|
||||
@@ -925,6 +1012,16 @@ namespace UVC.UIToolkit.Window
|
||||
var item = _treeView.GetItemDataForIndex<UTKTreeListItemData>(index);
|
||||
if (item == null) return;
|
||||
|
||||
// 0. 자식이 없는 항목의 토글(화살표) 영역 너비 제거
|
||||
// element는 TemplateContainer이고, Toggle은 부모(unity-tree-view__item)의 자식
|
||||
var treeViewItem = element.parent.parent;
|
||||
var toggle = treeViewItem?.Q<Toggle>("unity-tree-view__item-toggle");
|
||||
if (toggle != null)
|
||||
{
|
||||
bool hasChildren = item.children != null && item.children.Count > 0;
|
||||
toggle.style.width = hasChildren ? StyleKeyword.Auto : 0;
|
||||
}
|
||||
|
||||
// 1. 항목 이름 레이블 설정
|
||||
var label = element.Q<Label>("item-label");
|
||||
if (label != null) label.text = item.name;
|
||||
@@ -998,12 +1095,25 @@ namespace UVC.UIToolkit.Window
|
||||
|
||||
#region 검색 기능 (Search Functionality)
|
||||
/// <summary>
|
||||
/// 검색 필드 값 변경 콜백입니다.
|
||||
/// 검색 필드에서 Enter 키를 눌렀을 때 검색을 실행합니다.
|
||||
/// </summary>
|
||||
/// <param name="evt">값 변경 이벤트</param>
|
||||
private void OnSearchValueChanged(ChangeEvent<string> evt)
|
||||
/// <param name="evt">키 입력 이벤트</param>
|
||||
private void OnSearchFieldKeyDown(KeyDownEvent evt)
|
||||
{
|
||||
OnSearch(evt.newValue);
|
||||
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
|
||||
{
|
||||
OnSearch(_searchField?.value ?? string.Empty);
|
||||
evt.StopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 검색 필드가 포커스를 잃었을 때 검색을 실행합니다.
|
||||
/// </summary>
|
||||
/// <param name="evt">포커스 아웃 이벤트</param>
|
||||
private void OnSearchFieldFocusOut(FocusOutEvent evt)
|
||||
{
|
||||
OnSearch(_searchField?.value ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1018,6 +1128,7 @@ namespace UVC.UIToolkit.Window
|
||||
{
|
||||
_treeView.SetRootItems<UTKTreeListItemData>(_rootData);
|
||||
_treeView.Rebuild();
|
||||
ExpandByData(_originalRoots);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1136,7 +1247,8 @@ namespace UVC.UIToolkit.Window
|
||||
// 검색 필드 이벤트 해제
|
||||
if (_searchField != null)
|
||||
{
|
||||
_searchField.UnregisterValueChangedCallback(OnSearchValueChanged);
|
||||
_searchField.UnregisterCallback<KeyDownEvent>(OnSearchFieldKeyDown);
|
||||
_searchField.UnregisterCallback<FocusOutEvent>(OnSearchFieldFocusOut);
|
||||
}
|
||||
|
||||
// TreeView 이벤트 핸들러 해제
|
||||
@@ -1144,6 +1256,7 @@ namespace UVC.UIToolkit.Window
|
||||
{
|
||||
_treeView.selectionChanged -= OnTreeViewSelectionChanged;
|
||||
_treeView.itemsChosen -= OnTreeViewItemsChosen;
|
||||
_treeView.itemExpandedChanged -= OnTreeViewItemExpandedChanged;
|
||||
_treeView.UnregisterCallback<KeyDownEvent>(OnTreeViewKeyDown);
|
||||
_treeView.bindItem = null;
|
||||
_treeView.makeItem = null;
|
||||
|
||||
Reference in New Issue
Block a user