UTKAccordion 개발 완료. UTKComponentList 완료

This commit is contained in:
logonkhi
2026-01-06 18:51:37 +09:00
parent b7776f3af0
commit ef4e86820c
9 changed files with 532 additions and 233 deletions

View File

@@ -534,8 +534,17 @@ namespace UVC.UIToolkit.List
// 이전 바인딩에서 추가된 클래스 제거
treeViewItem?.RemoveFromClassList("accordion-section-row");
// 들여쓰기 요소 너비 강제 제거
var indentElement = treeViewItem?.Q<VisualElement>("unity-tree-view__item-indent");
if (indentElement != null)
{
indentElement.style.width = 0;
}
// 토글(화살표) 영역 설정
var toggle = treeViewItem?.Q<Toggle>("unity-tree-view__item-toggle");
var checkmark = toggle?.Q<VisualElement>("unity-checkmark");
if (toggle != null)
{
// 섹션만 화살표 표시
@@ -544,10 +553,36 @@ namespace UVC.UIToolkit.List
toggle.style.display = hasChildren ? DisplayStyle.Flex : DisplayStyle.None;
}
// 섹션 행에 배경색 클래스 추가
// 섹션 행에 배경색 클래스 추가 및 회전 상태 설정
if (item.nodeType == UTKAccordionNodeType.Section && treeViewItem != null)
{
treeViewItem.AddToClassList("accordion-section-row");
// 섹션의 확장 상태에 따라 회전 직접 설정 (transition 없이 즉시 적용)
if (checkmark != null)
{
// transition 비활성화 후 회전 설정
checkmark.style.transitionDuration = new List<TimeValue> { new TimeValue(0f) };
float angle = item.isExpanded ? 90f : 0f;
checkmark.style.rotate = new Rotate(angle);
// 다음 프레임에 transition 복원
checkmark.schedule.Execute(() =>
{
checkmark.style.transitionDuration = new List<TimeValue> { new TimeValue(0.2f, TimeUnit.Second) };
checkmark.style.transitionProperty = new List<StylePropertyName> { new StylePropertyName("rotate") };
});
}
}
else
{
// 섹션이 아닌 경우 회전 및 transition 제거
if (checkmark != null)
{
checkmark.style.rotate = StyleKeyword.Null;
checkmark.style.transitionDuration = StyleKeyword.Null;
checkmark.style.transitionProperty = StyleKeyword.Null;
}
}
// 노드 타입에 따라 UI 생성
@@ -576,6 +611,18 @@ namespace UVC.UIToolkit.List
private void UnbindTreeItem(VisualElement element, int index)
{
// TreeView 행 요소에서 섹션 클래스 제거 (가상화 시 재사용 대비)
var treeViewItem = element.parent?.parent;
treeViewItem?.RemoveFromClassList("accordion-section-row");
// 토글 체크마크 회전 상태 초기화
var toggle = treeViewItem?.Q<Toggle>("unity-tree-view__item-toggle");
var checkmark = toggle?.Q<VisualElement>("unity-checkmark");
if (checkmark != null)
{
checkmark.style.rotate = StyleKeyword.Null;
}
// 이벤트 정리 등 필요한 작업
element.userData = null;
}
@@ -926,6 +973,9 @@ namespace UVC.UIToolkit.List
{
item.isExpanded = evt.isExpanded;
// 해당 아이템의 회전 상태 업데이트
UpdateItemRotation(evt.id, evt.isExpanded);
if (item.IsSection)
{
var legacySection = new UTKAccordionSectionData
@@ -939,6 +989,35 @@ namespace UVC.UIToolkit.List
}
}
/// <summary>특정 아이템의 회전 상태를 업데이트합니다.</summary>
private void UpdateItemRotation(int itemId, bool isExpanded)
{
if (_treeView == null) return;
// TreeView에서 해당 아이템의 index 찾기
var index = _treeView.viewController.GetIndexForId(itemId);
if (index < 0) return;
// 해당 index의 VisualElement 찾기
var itemElement = _treeView.Q<VisualElement>($"unity-tree-view__item");
// TreeView 내의 모든 행을 순회하여 해당 아이템 찾기
_treeView.Query<VisualElement>(className: "unity-tree-view__item").ForEach(treeViewItem =>
{
var container = treeViewItem.Q<VisualElement>("accordion-item-container");
if (container?.userData is UTKAccordionItemData data && data.id == itemId)
{
var toggle = treeViewItem.Q<Toggle>("unity-tree-view__item-toggle");
var checkmark = toggle?.Q<VisualElement>("unity-checkmark");
if (checkmark != null)
{
float angle = isExpanded ? 90f : 0f;
checkmark.style.rotate = new Rotate(angle);
}
}
});
}
private void ExpandByData(List<UTKAccordionItemData> items)
{
if (_treeView == null || items == null) return;

View File

@@ -95,13 +95,13 @@ namespace UVC.UIToolkit.List
/// 원본 루트 데이터입니다.
/// 검색 필터 해제 시 원래 데이터로 복원하는 데 사용됩니다.
/// </summary>
private List<UTKComponentListItemData> _originalRoots = new();
private List<UTKComponentListItemDataBase> _originalRoots = new();
/// <summary>
/// TreeView에 바인딩되는 데이터 소스입니다.
/// TreeViewItemData는 Unity의 TreeView가 요구하는 래퍼 타입입니다.
/// </summary>
private List<TreeViewItemData<UTKComponentListItemData>>? _rootData;
private List<TreeViewItemData<UTKComponentListItemDataBase>>? _rootData;
/// <summary>
/// 항목 ID 자동 생성을 위한 시드 값입니다.
@@ -113,7 +113,7 @@ namespace UVC.UIToolkit.List
/// 이전에 선택된 항목들입니다.
/// 선택 해제 이벤트 발송에 사용됩니다.
/// </summary>
private List<UTKComponentListItemData> _previouslySelectedItems = new();
private List<UTKComponentListItemDataBase> _previouslySelectedItems = new();
/// <summary>
/// 선택 이벤트 발송을 일시적으로 억제하는 플래그입니다.
@@ -152,32 +152,32 @@ namespace UVC.UIToolkit.List
/// <summary>
/// 메인/검색 리스트에서 항목이 선택될 때 발생합니다.
/// </summary>
public Action<List<UTKComponentListItemData>>? OnItemSelected;
public Action<List<UTKComponentListItemDataBase>>? OnItemSelected;
/// <summary>
/// 메인/검색 리스트에서 항목이 선택 해제될 때 발생합니다.
/// </summary>
public Action<List<UTKComponentListItemData>>? OnItemDeselected;
public Action<List<UTKComponentListItemDataBase>>? OnItemDeselected;
/// <summary>
/// 항목의 가시성(눈 아이콘)이 변경될 때 발생합니다.
/// 3D 모델의 GameObject 활성화/비활성화에 연동합니다.
/// </summary>
public event Action<UTKComponentListItemData, bool>? OnItemVisibilityChanged;
public event Action<UTKComponentListItemDataBase, bool>? OnItemVisibilityChanged;
/// <summary>
/// 메인/검색 리스트에서 항목이 삭제될 때 발생합니다 (Delete 키).
/// </summary>
public Action<UTKComponentListItemData>? OnItemDeleted;
public Action<UTKComponentListItemDataBase>? OnItemDeleted;
/// <summary>
/// 메인/검색 리스트에서 항목이 더블클릭될 때 발생합니다.
/// </summary>
public Action<UTKComponentListItemData>? OnItemDoubleClicked;
public Action<UTKComponentListItemDataBase>? OnItemDoubleClicked;
/// <summary>
/// 아이콘을 클릭할 때 발생합니다.
/// </summary>
public Action<string, UTKComponentListItemData>? OnItemIconClicked;
public Action<string, UTKComponentListItemDataBase>? OnItemIconClicked;
#endregion
@@ -405,25 +405,25 @@ namespace UVC.UIToolkit.List
/// <summary>
/// 트리 데이터를 설정합니다.
/// UTKComponentListItemData 리스트를 받아 TreeView에 바인딩합니다.
/// UTKComponentListItemDataBase 리스트를 받아 TreeView에 바인딩합니다.
/// </summary>
/// <param name="roots">루트 항목들의 리스트 (계층 구조의 최상위 항목들)</param>
public void SetData(List<UTKComponentListItemData> roots)
public void SetData(List<UTKComponentListItemDataBase> roots)
{
// 원본 데이터 저장 (필터 복원용)
_originalRoots = roots ?? new List<UTKComponentListItemData>();
_originalRoots = roots ?? new List<UTKComponentListItemDataBase>();
// ID 시드 초기화
_idSeed = 1;
// 순환 참조 감지를 위한 방문 집합
var visited = new HashSet<UTKComponentListItemData>();
var visited = new HashSet<UTKComponentListItemDataBase>();
// TreeView 형식으로 데이터 변환
_rootData = ConvertToTreeViewData(_originalRoots, visited, 0);
// TreeView에 데이터 설정
_treeView!.SetRootItems<UTKComponentListItemData>(_rootData);
_treeView!.SetRootItems<UTKComponentListItemDataBase>(_rootData);
// UI 갱신 및 데이터의 isExpanded 상태에 따라 펼치기
_treeView!.Rebuild();
@@ -434,7 +434,7 @@ namespace UVC.UIToolkit.List
/// 데이터의 isExpanded 값에 따라 항목을 펼치거나 접습니다.
/// </summary>
/// <param name="items">처리할 항목 리스트</param>
private void ExpandByData(List<UTKComponentListItemData> items)
private void ExpandByData(List<UTKComponentListItemDataBase> items)
{
if (_treeView == null || items == null) return;
@@ -455,7 +455,7 @@ namespace UVC.UIToolkit.List
/// <summary>
/// ExpandByData의 내부 재귀 구현입니다.
/// </summary>
private void ExpandByDataInternal(List<UTKComponentListItemData> items)
private void ExpandByDataInternal(List<UTKComponentListItemDataBase> items)
{
foreach (var item in items)
{
@@ -510,7 +510,7 @@ namespace UVC.UIToolkit.List
/// ID가 0이면 자동으로 할당됩니다.
/// </summary>
/// <param name="data">추가할 항목 데이터</param>
public void AddItem(UTKComponentListItemData data)
public void AddItem(UTKComponentListItemDataBase data)
{
if (data == null) return;
@@ -521,13 +521,13 @@ namespace UVC.UIToolkit.List
_originalRoots.Add(data);
// _rootData가 null이면 초기화
_rootData ??= new List<TreeViewItemData<UTKComponentListItemData>>();
_rootData ??= new List<TreeViewItemData<UTKComponentListItemDataBase>>();
// TreeViewItemData 래퍼 생성 및 추가
var childData = data.children != null && data.children.Count > 0
? ConvertToTreeViewData(data.children, new HashSet<UTKComponentListItemData>(), 1)
? ConvertToTreeViewData(data.children, new HashSet<UTKComponentListItemDataBase>(), 1)
: null;
var treeItem = new TreeViewItemData<UTKComponentListItemData>(data.id, data, childData);
var treeItem = new TreeViewItemData<UTKComponentListItemDataBase>(data.id, data, childData);
_rootData.Add(treeItem);
// TreeView 갱신
@@ -537,11 +537,11 @@ namespace UVC.UIToolkit.List
}
/// <summary>
/// 지정된 부모 항목의 자식으로 새 항목을 추가합니다.
/// 지정된 부모 카테고리의 자식으로 새 항목을 추가합니다.
/// </summary>
/// <param name="parent">부모 항목 (null이면 루트에 추가)</param>
/// <param name="parent">부모 카테고리 (null이면 루트에 추가)</param>
/// <param name="data">추가할 항목 데이터</param>
public void AddItem(UTKComponentListItemData? parent, UTKComponentListItemData data)
public void AddItem(UTKComponentListCategoryData? parent, UTKComponentListItemDataBase data)
{
if (data == null) return;
@@ -556,8 +556,7 @@ namespace UVC.UIToolkit.List
if (data.id == 0) data.id = _idSeed++;
// 부모의 children 리스트에 추가
parent.children ??= new List<UTKComponentListItemData>();
parent.children.Add(data);
parent.Add(data);
// TreeView 데이터 재구성 및 갱신
RefreshTreeViewData();
@@ -568,7 +567,7 @@ namespace UVC.UIToolkit.List
/// 자식 항목이 있는 경우 함께 삭제됩니다.
/// </summary>
/// <param name="data">삭제할 항목 데이터</param>
public void DeleteItem(UTKComponentListItemData data)
public void DeleteItem(UTKComponentListItemDataBase data)
{
if (data == null) return;
@@ -607,7 +606,7 @@ namespace UVC.UIToolkit.List
/// </summary>
/// <param name="data">변경할 항목 데이터</param>
/// <param name="newName">새 이름</param>
public void SetItemName(UTKComponentListItemData data, string newName)
public void SetItemName(UTKComponentListItemDataBase data, string newName)
{
if (data == null || string.IsNullOrEmpty(newName)) return;
@@ -734,7 +733,7 @@ namespace UVC.UIToolkit.List
/// </summary>
private void RefreshTreeViewData()
{
var visited = new HashSet<UTKComponentListItemData>();
var visited = new HashSet<UTKComponentListItemDataBase>();
_rootData = ConvertToTreeViewData(_originalRoots, visited, 0);
_treeView?.SetRootItems(_rootData);
_treeView?.Rebuild();
@@ -747,14 +746,18 @@ namespace UVC.UIToolkit.List
/// <param name="parent">부모 노드</param>
/// <param name="target">삭제할 대상 항목</param>
/// <returns>삭제 성공 여부</returns>
private bool RemoveFromChildren(UTKComponentListItemData parent, UTKComponentListItemData target)
private bool RemoveFromChildren(UTKComponentListItemDataBase parent, UTKComponentListItemDataBase target)
{
if (parent.children == null || parent.children.Count == 0) return false;
// 직접 자식에서 삭제 시도
if (parent.children.Remove(target))
// 카테고리인 경우에만 자식에서 삭제 가능
if (parent is UTKComponentListCategoryData category)
{
return true;
// 직접 자식에서 삭제 시도
if (category.Remove(target))
{
return true;
}
}
// 하위 자식들에서 재귀적으로 검색
@@ -774,7 +777,7 @@ namespace UVC.UIToolkit.List
/// </summary>
/// <param name="name">찾을 항목의 이름</param>
/// <returns>찾은 항목 또는 null</returns>
private UTKComponentListItemData? FindItemByName(string name)
private UTKComponentListItemDataBase? FindItemByName(string name)
{
foreach (var root in _originalRoots)
{
@@ -789,7 +792,7 @@ namespace UVC.UIToolkit.List
/// _previouslySelectedItems와 TreeView의 선택 상태 모두에서 제거합니다.
/// </summary>
/// <param name="data">삭제된 항목</param>
private void RemoveFromSelection(UTKComponentListItemData data)
private void RemoveFromSelection(UTKComponentListItemDataBase data)
{
// 삭제할 항목과 모든 자식들의 ID 수집
var idsToRemove = new HashSet<int>();
@@ -806,7 +809,7 @@ namespace UVC.UIToolkit.List
foreach (var index in currentSelection)
{
var selectedItem = _treeView.GetItemDataForIndex<UTKComponentListItemData>(index);
var selectedItem = _treeView.GetItemDataForIndex<UTKComponentListItemDataBase>(index);
if (selectedItem != null && !idsToRemove.Contains(selectedItem.id))
{
newSelection.Add(selectedItem.id);
@@ -825,7 +828,7 @@ namespace UVC.UIToolkit.List
/// </summary>
/// <param name="node">수집 시작 노드</param>
/// <param name="ids">ID를 저장할 HashSet</param>
private void CollectItemIds(UTKComponentListItemData node, HashSet<int> ids)
private void CollectItemIds(UTKComponentListItemDataBase node, HashSet<int> ids)
{
ids.Add(node.id);
@@ -844,7 +847,7 @@ namespace UVC.UIToolkit.List
/// <param name="node">검색 시작 노드</param>
/// <param name="name">찾을 항목의 이름</param>
/// <returns>찾은 항목 또는 null</returns>
private UTKComponentListItemData? FindItemByNameRecursive(UTKComponentListItemData node, string name)
private UTKComponentListItemDataBase? FindItemByNameRecursive(UTKComponentListItemDataBase node, string name)
{
if (node.name == name) return node;
@@ -867,7 +870,7 @@ namespace UVC.UIToolkit.List
/// <param name="visibleNames">표시할 이름 목록</param>
/// <param name="maxDepth">최대 검색 깊이 (0이하=무제한)</param>
/// <param name="currentDepth">현재 깊이</param>
private void SetVisibilityByNames(UTKComponentListItemData node, HashSet<string> visibleNames, int maxDepth, int currentDepth)
private void SetVisibilityByNames(UTKComponentListItemDataBase node, HashSet<string> visibleNames, int maxDepth, int currentDepth)
{
if (node == null) return;
@@ -908,8 +911,31 @@ namespace UVC.UIToolkit.List
// SelectionType.None인 경우 선택 변경 무시
if (_treeView?.selectionType == SelectionType.None) return;
// 현재 선택된 항목들 (TreeView가 selectionType에 따라 이미 필터링함)
var currentItems = selectedItems.OfType<UTKComponentListItemData>().ToList();
// 카테고리가 선택되었는지 확인
var allSelectedItems = selectedItems.OfType<UTKComponentListItemDataBase>().ToList();
var categorySelected = allSelectedItems.Any(item => item.IsCategory);
// 카테고리가 선택된 경우: 기존 선택 상태 유지
if (categorySelected)
{
// 기존 선택 상태로 복원
_suppressSelectionEvent = true;
try
{
var previousIds = _previouslySelectedItems.Select(item => item.id).ToList();
_treeView?.SetSelection(previousIds);
}
finally
{
_suppressSelectionEvent = false;
}
return; // 이벤트 발송하지 않고 종료
}
// 현재 선택된 항목들 (카테고리 제외)
var currentItems = allSelectedItems
.Where(item => !item.IsCategory)
.ToList();
// 선택 해제된 항목들 찾기 (이전에는 있었지만 현재는 없는 항목)
var deselectedItems = _previouslySelectedItems
@@ -957,7 +983,7 @@ namespace UVC.UIToolkit.List
/// <param name="chosenItems">선택된 항목들</param>
private void OnTreeViewItemsChosen(IEnumerable<object> chosenItems)
{
var item = chosenItems.FirstOrDefault() as UTKComponentListItemData;
var item = chosenItems.FirstOrDefault() as UTKComponentListItemDataBase;
if (item != null)
{
OnItemDoubleClicked?.Invoke(item);
@@ -998,26 +1024,55 @@ namespace UVC.UIToolkit.List
// ExpandByData() 실행 중에는 이벤트 무시 (데이터 덮어쓰기 방지)
if (_suppressExpandEvent) return;
var item = _treeView?.GetItemDataForId<UTKComponentListItemData>(evt.id);
var item = _treeView?.GetItemDataForId<UTKComponentListItemDataBase>(evt.id);
if (item != null)
{
item.isExpanded = evt.isExpanded;
}
// 해당 항목의 화살표 회전 업데이트
UpdateToggleRotation(evt.id, evt.isExpanded);
}
/// <summary>
/// 특정 항목의 토글 화살표 회전을 업데이트합니다.
/// </summary>
/// <param name="itemId">항목 ID</param>
/// <param name="isExpanded">확장 상태</param>
private void UpdateToggleRotation(int itemId, bool isExpanded)
{
if (_treeView == null) return;
// TreeView에서 해당 항목의 인덱스 찾기
int index = _treeView.viewController.GetIndexForId(itemId);
if (index < 0) return;
// 해당 인덱스의 VisualElement 찾기
var itemElement = _treeView.GetRootElementForIndex(index);
if (itemElement == null) return;
// 토글과 체크마크 찾기
var toggle = itemElement.Q<Toggle>("unity-tree-view__item-toggle");
var checkmark = toggle?.Q<VisualElement>("unity-checkmark");
if (checkmark != null)
{
checkmark.style.rotate = new Rotate(isExpanded ? 0f : -90f);
}
}
#endregion
#region (Data Conversion)
/// <summary>
/// UTKComponentListItemData 리스트를 TreeView가 요구하는 TreeViewItemData 형식으로 변환합니다.
/// UTKComponentListItemDataBase 리스트를 TreeView가 요구하는 TreeViewItemData 형식으로 변환합니다.
/// 재귀적으로 자식 항목도 변환하며, 순환 참조를 감지합니다.
/// </summary>
/// <param name="items">변환할 항목 리스트</param>
/// <param name="visited">순환 참조 감지용 방문 집합</param>
/// <param name="depth">현재 깊이 (디버깅용)</param>
/// <returns>TreeViewItemData 래퍼 리스트</returns>
private List<TreeViewItemData<UTKComponentListItemData>> ConvertToTreeViewData(List<UTKComponentListItemData> items, HashSet<UTKComponentListItemData> visited, int depth)
private List<TreeViewItemData<UTKComponentListItemDataBase>> ConvertToTreeViewData(List<UTKComponentListItemDataBase> items, HashSet<UTKComponentListItemDataBase> visited, int depth)
{
var list = new List<TreeViewItemData<UTKComponentListItemData>>(items.Count);
var list = new List<TreeViewItemData<UTKComponentListItemDataBase>>(items.Count);
foreach (var item in items)
{
if (item == null) continue;
@@ -1026,19 +1081,23 @@ namespace UVC.UIToolkit.List
if (!visited.Add(item))
{
Debug.LogWarning($"[UTKComponentList] Cycle detected at item '{item.name}' → children 무시");
item.children = null;
// 카테고리인 경우 자식 클리어
if (item is UTKComponentListCategoryData category)
{
category.Clear();
}
}
// ID가 없으면 자동 할당
if (item.id == 0) item.id = _idSeed++;
// 자식 항목 재귀 변환
List<TreeViewItemData<UTKComponentListItemData>>? childData = null;
List<TreeViewItemData<UTKComponentListItemDataBase>>? childData = null;
if (item.children != null && item.children.Count > 0)
childData = ConvertToTreeViewData(item.children, visited, depth + 1);
// TreeViewItemData 래퍼 생성
var treeItem = new TreeViewItemData<UTKComponentListItemData>(item.id, item, childData);
var treeItem = new TreeViewItemData<UTKComponentListItemDataBase>(item.id, item, childData);
list.Add(treeItem);
}
return list;
@@ -1072,19 +1131,81 @@ namespace UVC.UIToolkit.List
private void BindTreeItem(VisualElement element, int index)
{
// 인덱스로 데이터 획득
var item = _treeView!.GetItemDataForIndex<UTKComponentListItemData>(index);
var item = _treeView!.GetItemDataForIndex<UTKComponentListItemDataBase>(index);
if (item == null) return;
// 자식 유무 확인: 실제 자식이 있거나, 검색 결과 그룹인 경우 (TreeViewItemData에만 자식이 있음)
bool hasChildren = (item.children != null && item.children.Count > 0) || item.isSearchResultGroup;
// 0. 자식이 없는 항목의 토글(화살표) 영역 너비 제거
// 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)
{
toggle.style.width = hasChildren ? StyleKeyword.Auto : 0;
// 확장 상태에 따라 화살표 회전 설정
if (hasChildren)
{
var checkmark = toggle.Q<VisualElement>("unity-checkmark");
if (checkmark != null)
{
bool isExpanded = _treeView!.IsExpanded(item.id);
checkmark.style.rotate = new Rotate(isExpanded ? 0f : -90f);
}
}
}
// 카테고리 항목인 경우: 선택 클릭 이벤트 차단 (Shift/Ctrl 클릭 포함)
// treeViewItem 레벨에서 PointerDownEvent를 캡처하여 선택 로직 차단
if (item.IsCategory && treeViewItem != null)
{
// 카테고리 항목에 클래스 추가 (hover 스타일 비활성화용)
treeViewItem.AddToClassList("category-item");
// 기존 핸들러 제거를 위해 userData 사용
if (treeViewItem.userData is EventCallback<PointerDownEvent> oldHandler)
{
treeViewItem.UnregisterCallback(oldHandler, TrickleDown.TrickleDown);
}
EventCallback<PointerDownEvent> categoryClickHandler = (evt) =>
{
// 토글(화살표) 영역 클릭은 허용 (펼치기/접기 기능 유지)
if (toggle != null && toggle.worldBound.Contains(evt.position))
{
return; // 토글 클릭은 통과
}
// 카테고리 클릭 시 이벤트 전파 완전 차단
// 이렇게 하면 TreeView의 선택 로직이 실행되지 않음
evt.StopImmediatePropagation();
// 펼치기/접기 토글 (카테고리 본문 클릭 시)
if (_treeView != null)
{
bool isExpanded = _treeView.IsExpanded(item.id);
if (isExpanded)
_treeView.CollapseItem(item.id);
else
_treeView.ExpandItem(item.id);
}
};
treeViewItem.userData = categoryClickHandler;
treeViewItem.RegisterCallback(categoryClickHandler, TrickleDown.TrickleDown);
}
else if (treeViewItem != null)
{
// 일반 항목인 경우: 카테고리 클래스 제거 및 이전에 등록된 핸들러 제거
treeViewItem.RemoveFromClassList("category-item");
if (treeViewItem.userData is EventCallback<PointerDownEvent> oldHandler)
{
treeViewItem.UnregisterCallback(oldHandler, TrickleDown.TrickleDown);
treeViewItem.userData = null;
}
}
// 1. 기존 내용 제거 후 적절한 템플릿으로 교체
@@ -1194,7 +1315,7 @@ namespace UVC.UIToolkit.List
/// </summary>
/// <param name="node">현재 노드</param>
/// <param name="isVisible">설정할 가시성 상태</param>
private void SetChildrenVisibility(UTKComponentListItemData node, bool isVisible)
private void SetChildrenVisibility(UTKComponentListItemDataBase node, bool isVisible)
{
if (node.children == null || node.children.Count == 0) return;
foreach (var child in node.children)
@@ -1266,7 +1387,7 @@ namespace UVC.UIToolkit.List
// 검색어가 없으면 원본 데이터 복원
if (string.IsNullOrEmpty(query))
{
_treeView.SetRootItems<UTKComponentListItemData>(_rootData);
_treeView.SetRootItems<UTKComponentListItemDataBase>(_rootData);
_treeView.Rebuild();
ExpandByData(_originalRoots);
return;
@@ -1277,7 +1398,7 @@ namespace UVC.UIToolkit.List
var filteredWrappers = FilterTree(qLower);
// 필터링된 결과로 TreeView 갱신
_treeView.SetRootItems<UTKComponentListItemData>(filteredWrappers);
_treeView.SetRootItems<UTKComponentListItemDataBase>(filteredWrappers);
_treeView.Rebuild();
// 검색 결과 모두 펼치기
@@ -1289,16 +1410,16 @@ namespace UVC.UIToolkit.List
/// </summary>
/// <param name="qLower">소문자로 변환된 검색어</param>
/// <returns>필터링된 TreeViewItemData 리스트 (검색 결과 그룹 포함)</returns>
private List<TreeViewItemData<UTKComponentListItemData>> FilterTree(string qLower)
private List<TreeViewItemData<UTKComponentListItemDataBase>> FilterTree(string qLower)
{
// 1. 매칭되는 리프 노드들만 수집
var matchedItems = new List<UTKComponentListItemData>();
var matchedItems = new List<UTKComponentListItemDataBase>();
CollectMatchingLeafNodes(_originalRoots, qLower, matchedItems);
// 2. 검색 결과가 없으면 빈 리스트 반환
if (matchedItems.Count == 0)
{
return new List<TreeViewItemData<UTKComponentListItemData>>();
return new List<TreeViewItemData<UTKComponentListItemDataBase>>();
}
string localeName = LocalizationManager.Instance.GetString("search_result_count");
@@ -1308,8 +1429,8 @@ namespace UVC.UIToolkit.List
}
localeName = localeName.Replace("{0}", matchedItems.Count.ToString());
// 3. "검색결과 N건" 그룹 데이터 생성
var searchResultGroup = new UTKComponentListItemData
// 3. "검색결과 N건" 그룹 데이터 생성 (카테고리로 생성)
var searchResultGroup = new UTKComponentListCategoryData
{
id = -1000, // 특별한 ID (충돌 방지)
name = localeName,
@@ -1318,20 +1439,20 @@ namespace UVC.UIToolkit.List
};
// 4. 매칭된 항목들을 자식으로 래핑 (TreeViewItemData 형태로)
var childWrappers = new List<TreeViewItemData<UTKComponentListItemData>>();
var childWrappers = new List<TreeViewItemData<UTKComponentListItemDataBase>>();
foreach (var item in matchedItems)
{
childWrappers.Add(new TreeViewItemData<UTKComponentListItemData>(item.id, item));
childWrappers.Add(new TreeViewItemData<UTKComponentListItemDataBase>(item.id, item));
}
// 5. 그룹을 TreeViewItemData로 래핑
var groupWrapper = new TreeViewItemData<UTKComponentListItemData>(
var groupWrapper = new TreeViewItemData<UTKComponentListItemDataBase>(
searchResultGroup.id,
searchResultGroup,
childWrappers
);
return new List<TreeViewItemData<UTKComponentListItemData>> { groupWrapper };
return new List<TreeViewItemData<UTKComponentListItemDataBase>> { groupWrapper };
}
/// <summary>
@@ -1340,7 +1461,7 @@ namespace UVC.UIToolkit.List
/// <param name="items">검색할 항목 리스트</param>
/// <param name="qLower">소문자로 변환된 검색어</param>
/// <param name="result">매칭된 항목을 저장할 리스트</param>
private void CollectMatchingLeafNodes(List<UTKComponentListItemData> items, string qLower, List<UTKComponentListItemData> result)
private void CollectMatchingLeafNodes(List<UTKComponentListItemDataBase> items, string qLower, List<UTKComponentListItemDataBase> result)
{
foreach (var item in items)
{
@@ -1367,21 +1488,21 @@ namespace UVC.UIToolkit.List
/// <param name="node">필터링할 노드</param>
/// <param name="qLower">소문자로 변환된 검색어</param>
/// <returns>매치된 경우 TreeViewItemData, 아니면 null</returns>
private TreeViewItemData<UTKComponentListItemData>? FilterNode(UTKComponentListItemData node, string qLower)
private TreeViewItemData<UTKComponentListItemDataBase>? FilterNode(UTKComponentListItemDataBase node, string qLower)
{
// 자기 자신이 매치되는지 확인
bool selfMatch = NodeMatches(node, qLower);
List<TreeViewItemData<UTKComponentListItemData>>? childFiltered = null;
List<TreeViewItemData<UTKComponentListItemDataBase>>? childFiltered = null;
// 자식들도 재귀적으로 필터링
if (node.children != null && node.children.Count > 0)
{
foreach (var child in node.children)
{
TreeViewItemData<UTKComponentListItemData>? f = FilterNode(child, qLower);
TreeViewItemData<UTKComponentListItemDataBase>? f = FilterNode(child, qLower);
if (f != null)
{
childFiltered ??= new List<TreeViewItemData<UTKComponentListItemData>>();
childFiltered ??= new List<TreeViewItemData<UTKComponentListItemDataBase>>();
childFiltered.Add(f.Value);
}
}
@@ -1390,7 +1511,7 @@ namespace UVC.UIToolkit.List
// 자신이 매치되거나 매치된 자식이 있으면 포함
if (selfMatch || (childFiltered != null && childFiltered.Count > 0))
{
return new TreeViewItemData<UTKComponentListItemData>(node.id, node, childFiltered);
return new TreeViewItemData<UTKComponentListItemDataBase>(node.id, node, childFiltered);
}
return null;
}
@@ -1402,11 +1523,16 @@ namespace UVC.UIToolkit.List
/// <param name="item">검사할 항목</param>
/// <param name="qLower">소문자로 변환된 검색어</param>
/// <returns>매치 여부</returns>
private bool NodeMatches(UTKComponentListItemData item, string qLower)
private bool NodeMatches(UTKComponentListItemDataBase item, string qLower)
{
if (item.name != null && item.name.ToLowerInvariant().Contains(qLower)) return true;
if (!string.IsNullOrEmpty(item.option) && item.option.ToLowerInvariant().Contains(qLower)) return true;
if (!string.IsNullOrEmpty(item.ExternalKey) && item.ExternalKey.ToLowerInvariant().Contains(qLower)) return true;
// 일반 항목인 경우 option과 ExternalKey도 검색
if (item is UTKComponentListItemData itemData)
{
if (!string.IsNullOrEmpty(itemData.option) && itemData.option.ToLowerInvariant().Contains(qLower)) return true;
if (!string.IsNullOrEmpty(itemData.ExternalKey) && itemData.ExternalKey.ToLowerInvariant().Contains(qLower)) return true;
}
return false;
}
@@ -1414,7 +1540,7 @@ namespace UVC.UIToolkit.List
/// 필터링된 결과의 모든 항목을 펼칩니다.
/// </summary>
/// <param name="roots">펼칠 루트 항목들</param>
private void ExpandAll(List<TreeViewItemData<UTKComponentListItemData>> roots)
private void ExpandAll(List<TreeViewItemData<UTKComponentListItemDataBase>> roots)
{
foreach (var r in roots)
ExpandRecursive(r);
@@ -1424,7 +1550,7 @@ namespace UVC.UIToolkit.List
/// 노드와 그 자식들을 재귀적으로 펼칩니다.
/// </summary>
/// <param name="wrapper">펼칠 TreeViewItemData</param>
private void ExpandRecursive(TreeViewItemData<UTKComponentListItemData> wrapper)
private void ExpandRecursive(TreeViewItemData<UTKComponentListItemDataBase> wrapper)
{
_treeView.ExpandItem(wrapper.id);
if (wrapper.children != null)

View File

@@ -5,39 +5,22 @@ using System.Collections.Generic;
namespace UVC.UIToolkit.List
{
/// <summary>
/// 트리 구조 리스트의 개별 항목 데이터를 나타내는 클래스입니다.
/// 트리 구조 리스트의 항목 데이터를 위한 추상 기본 클래스입니다.
///
/// <para><b>개요:</b></para>
/// <para>
/// UTKComponentListItemData는 계층적 트리 구조에서 각 노드(항목)를 표현합니다.
/// 부모-자식 관계를 통해 중첩된 트리 구조를 구성할 수 있으며,
/// UTKComponentListItemDataBase는 카테고리(UTKComponentListCategoryData)와
/// 일반 항목(UTKComponentListItemData)의 공통 속성을 정의합니다.
/// UTKComponentListWindow(UI)에서 사용됩니다.
/// </para>
///
/// <para><b>주요 용도:</b></para>
/// <para><b>상속 구조:</b></para>
/// <list type="bullet">
/// <item>3D glTF 모델의 계층 구조를 UI 트리로 표현</item>
/// <item>트리 리스트에서 항목 선택/확장/가시성 상태 관리</item>
/// <item>모델 뷰어와 트리 리스트 간의 데이터 동기화</item>
/// <item>UTKComponentListCategoryData - 카테고리용 (자식 항목을 가짐)</item>
/// <item>UTKComponentListItemData - 일반 항목용 (리프 노드)</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
public abstract class UTKComponentListItemDataBase : IDisposable
{
/// <summary>
/// 항목의 고유 식별자입니다.
@@ -49,16 +32,9 @@ namespace UVC.UIToolkit.List
/// <summary>
/// 항목의 표시 이름입니다.
/// UI의 TreeView에서 사용자에게 보여지는 텍스트입니다.
/// glTF 모델 로드 시 GameObject 이름이 자동으로 설정됩니다.
/// </summary>
public string name = string.Empty;
/// <summary>
/// 추가 옵션 또는 설명 텍스트입니다.
/// 검색 필터링 시 name과 함께 검색 대상이 됩니다.
/// </summary>
public string option = string.Empty;
/// <summary>
/// 트리뷰에서 해당 항목이 펼쳐져 있는지 여부입니다.
/// true: 자식 항목들이 표시됨 / false: 자식 항목들이 숨겨짐
@@ -71,20 +47,6 @@ namespace UVC.UIToolkit.List
/// </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의 눈 아이콘 버튼으로 토글되며,
@@ -93,13 +55,6 @@ namespace UVC.UIToolkit.List
/// </summary>
public bool IsVisible = true;
/// <summary>
/// 외부 시스템(예: 간트 차트)과의 연동을 위한 외부 키입니다.
/// 검색 필터링 시 검색 대상에 포함됩니다.
/// 모델과 차트 간의 동기화에 사용될 수 있습니다.
/// </summary>
public string ExternalKey = string.Empty;
/// <summary>
/// 검색 결과를 감싸는 임시 그룹 항목인지 여부입니다.
/// true인 경우 setting-btn이 숨겨지고 클릭되지 않습니다.
@@ -107,29 +62,156 @@ namespace UVC.UIToolkit.List
/// </summary>
public bool isSearchResultGroup = false;
/// <summary>
/// 부모 항목에 대한 참조입니다.
/// 루트 항목의 경우 null입니다.
/// Add() 메서드로 자식을 추가하면 자동으로 설정됩니다.
/// </summary>
public UTKComponentListItemDataBase? parent;
/// <summary>
/// 자식 항목들의 리스트입니다.
/// 카테고리 항목에서만 사용됩니다. 일반 항목에서는 빈 리스트입니다.
/// </summary>
public abstract List<UTKComponentListItemDataBase> children { get; }
/// <summary>
/// 이 항목이 카테고리인지 여부를 반환합니다.
/// </summary>
public abstract bool IsCategory { get; }
/// <summary>
/// 리소스를 해제합니다.
/// </summary>
public virtual void Dispose()
{
parent = null;
}
}
/// <summary>
/// 카테고리(그룹) 항목을 나타내는 클래스입니다.
/// 자식 항목들을 가질 수 있습니다.
///
/// <para><b>사용 예시:</b></para>
/// <code>
/// // 카테고리 생성
/// var category = new UTKComponentListCategoryData { name = "카테고리1" };
///
/// // 일반 항목 추가
/// var item1 = new UTKComponentListItemData { name = "항목1", option = "옵션1" };
/// var item2 = new UTKComponentListItemData { name = "항목2", option = "옵션2" };
/// category.Add(item1);
/// category.Add(item2);
/// </code>
/// </summary>
public class UTKComponentListCategoryData : UTKComponentListItemDataBase
{
/// <summary>
/// 자식 항목들의 리스트입니다.
/// </summary>
private List<UTKComponentListItemDataBase> _children = new List<UTKComponentListItemDataBase>();
/// <summary>
/// 자식 항목들의 리스트를 반환합니다.
/// </summary>
public override List<UTKComponentListItemDataBase> children => _children;
/// <summary>
/// 이 항목이 카테고리임을 나타냅니다.
/// </summary>
public override bool IsCategory => true;
/// <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)
public void Add(UTKComponentListItemDataBase child)
{
child.parent = this;
children.Add(child);
_children.Add(child);
}
public void Dispose()
/// <summary>
/// 자식 항목을 제거합니다.
/// </summary>
/// <param name="child">제거할 자식 항목</param>
/// <returns>제거 성공 여부</returns>
public bool Remove(UTKComponentListItemDataBase child)
{
children.Clear();
parent = null;
if (_children.Remove(child))
{
child.parent = null;
return true;
}
return false;
}
/// <summary>
/// 모든 자식 항목을 제거합니다.
/// </summary>
public void Clear()
{
foreach (var child in _children)
{
child.parent = null;
}
_children.Clear();
}
/// <summary>
/// 리소스를 해제합니다.
/// </summary>
public override void Dispose()
{
Clear();
base.Dispose();
}
}
}
/// <summary>
/// 일반 항목(리프 노드)을 나타내는 클래스입니다.
/// 자식 항목을 가지지 않습니다.
///
/// <para><b>사용 예시:</b></para>
/// <code>
/// var item = new UTKComponentListItemData
/// {
/// name = "항목 이름",
/// option = "추가 옵션",
/// ExternalKey = "외부 연동 키"
/// };
/// </code>
/// </summary>
public class UTKComponentListItemData : UTKComponentListItemDataBase
{
/// <summary>
/// 추가 옵션 또는 설명 텍스트입니다.
/// 검색 필터링 시 name과 함께 검색 대상이 됩니다.
/// </summary>
public string option = string.Empty;
/// <summary>
/// 외부 시스템(예: 간트 차트)과의 연동을 위한 외부 키입니다.
/// 검색 필터링 시 검색 대상에 포함됩니다.
/// 모델과 차트 간의 동기화에 사용될 수 있습니다.
/// </summary>
public string ExternalKey = string.Empty;
/// <summary>
/// 일반 항목은 자식을 가지지 않으므로 빈 리스트를 반환합니다.
/// </summary>
private static readonly List<UTKComponentListItemDataBase> _emptyChildren = new List<UTKComponentListItemDataBase>();
/// <summary>
/// 자식 항목들의 리스트를 반환합니다. 항상 빈 리스트입니다.
/// </summary>
public override List<UTKComponentListItemDataBase> children => _emptyChildren;
/// <summary>
/// 이 항목이 카테고리가 아님을 나타냅니다.
/// </summary>
public override bool IsCategory => false;
}
}

View File

@@ -65,7 +65,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 메인/검색 리스트에서 항목이 선택될 때 발생합니다.
/// </summary>
public Action<List<UTKComponentListItemData>>? OnItemSelected
public Action<List<UTKComponentListItemDataBase>>? OnItemSelected
{
get => _componentList?.OnItemSelected;
set { if (_componentList != null) _componentList.OnItemSelected = value; }
@@ -74,7 +74,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 메인/검색 리스트에서 항목이 선택 해제될 때 발생합니다.
/// </summary>
public Action<List<UTKComponentListItemData>>? OnItemDeselected
public Action<List<UTKComponentListItemDataBase>>? OnItemDeselected
{
get => _componentList?.OnItemDeselected;
set { if (_componentList != null) _componentList.OnItemDeselected = value; }
@@ -83,7 +83,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 항목의 가시성(눈 아이콘)이 변경될 때 발생합니다.
/// </summary>
public event Action<UTKComponentListItemData, bool>? OnItemVisibilityChanged
public event Action<UTKComponentListItemDataBase, bool>? OnItemVisibilityChanged
{
add { if (_componentList != null) _componentList.OnItemVisibilityChanged += value; }
remove { if (_componentList != null) _componentList.OnItemVisibilityChanged -= value; }
@@ -92,7 +92,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 메인/검색 리스트에서 항목이 삭제될 때 발생합니다 (Delete 키).
/// </summary>
public Action<UTKComponentListItemData>? OnItemDeleted
public Action<UTKComponentListItemDataBase>? OnItemDeleted
{
get => _componentList?.OnItemDeleted;
set { if (_componentList != null) _componentList.OnItemDeleted = value; }
@@ -101,7 +101,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 메인/검색 리스트에서 항목이 더블클릭될 때 발생합니다.
/// </summary>
public Action<UTKComponentListItemData>? OnItemDoubleClicked
public Action<UTKComponentListItemDataBase>? OnItemDoubleClicked
{
get => _componentList?.OnItemDoubleClicked;
set { if (_componentList != null) _componentList.OnItemDoubleClicked = value; }
@@ -110,7 +110,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 아이콘을 클릭할 때 발생합니다.
/// </summary>
public Action<string, UTKComponentListItemData>? OnItemIconClicked
public Action<string, UTKComponentListItemDataBase>? OnItemIconClicked
{
get => _componentList?.OnItemIconClicked;
set { if (_componentList != null) _componentList.OnItemIconClicked = value; }
@@ -174,7 +174,7 @@ namespace UVC.UIToolkit.Window
/// 트리 데이터를 설정합니다.
/// </summary>
/// <param name="roots">루트 항목들의 리스트</param>
public void SetData(List<UTKComponentListItemData> roots)
public void SetData(List<UTKComponentListItemDataBase> roots)
{
_componentList?.SetData(roots);
}
@@ -202,17 +202,17 @@ namespace UVC.UIToolkit.Window
/// 루트 레벨에 새 항목을 추가합니다.
/// </summary>
/// <param name="data">추가할 항목 데이터</param>
public void AddItem(UTKComponentListItemData data)
public void AddItem(UTKComponentListItemDataBase data)
{
_componentList?.AddItem(data);
}
/// <summary>
/// 지정된 부모 항목의 자식으로 새 항목을 추가합니다.
/// 지정된 부모 카테고리의 자식으로 새 항목을 추가합니다.
/// </summary>
/// <param name="parent">부모 항목</param>
/// <param name="parent">부모 카테고리</param>
/// <param name="data">추가할 항목 데이터</param>
public void AddItem(UTKComponentListItemData? parent, UTKComponentListItemData data)
public void AddItem(UTKComponentListCategoryData? parent, UTKComponentListItemDataBase data)
{
_componentList?.AddItem(parent, data);
}
@@ -221,7 +221,7 @@ namespace UVC.UIToolkit.Window
/// 트리에서 항목을 완전히 삭제합니다.
/// </summary>
/// <param name="data">삭제할 항목 데이터</param>
public void DeleteItem(UTKComponentListItemData data)
public void DeleteItem(UTKComponentListItemDataBase data)
{
_componentList?.DeleteItem(data);
}
@@ -231,7 +231,7 @@ namespace UVC.UIToolkit.Window
/// </summary>
/// <param name="data">변경할 항목 데이터</param>
/// <param name="newName">새 이름</param>
public void SetItemName(UTKComponentListItemData data, string newName)
public void SetItemName(UTKComponentListItemDataBase data, string newName)
{
_componentList?.SetItemName(data, newName);
}

View File

@@ -58,7 +58,7 @@ namespace UVC.UIToolkit.Window
#region (Tab Data)
/// <summary>원본 전체 데이터</summary>
private List<UTKComponentListItemData> _originalRoots = new();
private List<UTKComponentListItemDataBase> _originalRoots = new();
/// <summary>현재 선택된 탭 인덱스 (-1: 전체)</summary>
private int _selectedTabIndex = ALL_TAB_INDEX;
@@ -87,7 +87,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 메인/검색 리스트에서 항목이 선택될 때 발생합니다.
/// </summary>
public Action<List<UTKComponentListItemData>>? OnItemSelected
public Action<List<UTKComponentListItemDataBase>>? OnItemSelected
{
get => _componentList?.OnItemSelected;
set { if (_componentList != null) _componentList.OnItemSelected = value; }
@@ -96,7 +96,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 메인/검색 리스트에서 항목이 선택 해제될 때 발생합니다.
/// </summary>
public Action<List<UTKComponentListItemData>>? OnItemDeselected
public Action<List<UTKComponentListItemDataBase>>? OnItemDeselected
{
get => _componentList?.OnItemDeselected;
set { if (_componentList != null) _componentList.OnItemDeselected = value; }
@@ -105,7 +105,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 항목의 가시성(눈 아이콘)이 변경될 때 발생합니다.
/// </summary>
public event Action<UTKComponentListItemData, bool>? OnItemVisibilityChanged
public event Action<UTKComponentListItemDataBase, bool>? OnItemVisibilityChanged
{
add { if (_componentList != null) _componentList.OnItemVisibilityChanged += value; }
remove { if (_componentList != null) _componentList.OnItemVisibilityChanged -= value; }
@@ -114,7 +114,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 메인/검색 리스트에서 항목이 삭제될 때 발생합니다 (Delete 키).
/// </summary>
public Action<UTKComponentListItemData>? OnItemDeleted
public Action<UTKComponentListItemDataBase>? OnItemDeleted
{
get => _componentList?.OnItemDeleted;
set { if (_componentList != null) _componentList.OnItemDeleted = value; }
@@ -123,7 +123,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 메인/검색 리스트에서 항목이 더블클릭될 때 발생합니다.
/// </summary>
public Action<UTKComponentListItemData>? OnItemDoubleClicked
public Action<UTKComponentListItemDataBase>? OnItemDoubleClicked
{
get => _componentList?.OnItemDoubleClicked;
set { if (_componentList != null) _componentList.OnItemDoubleClicked = value; }
@@ -132,7 +132,7 @@ namespace UVC.UIToolkit.Window
/// <summary>
/// 아이콘을 클릭할 때 발생합니다.
/// </summary>
public Action<string, UTKComponentListItemData>? OnItemIconClicked
public Action<string, UTKComponentListItemDataBase>? OnItemIconClicked
{
get => _componentList?.OnItemIconClicked;
set { if (_componentList != null) _componentList.OnItemIconClicked = value; }
@@ -200,9 +200,9 @@ namespace UVC.UIToolkit.Window
/// 데이터의 카테고리를 분석하여 탭을 자동 생성합니다.
/// </summary>
/// <param name="roots">루트 항목들의 리스트</param>
public void SetData(List<UTKComponentListItemData> roots)
public void SetData(List<UTKComponentListItemDataBase> roots)
{
_originalRoots = roots ?? new List<UTKComponentListItemData>();
_originalRoots = roots ?? new List<UTKComponentListItemDataBase>();
// 탭 생성 (카테고리 기반)
CreateTabs();
@@ -277,7 +277,7 @@ namespace UVC.UIToolkit.Window
/// 루트 레벨에 새 항목을 추가합니다.
/// </summary>
/// <param name="data">추가할 항목 데이터</param>
public void AddItem(UTKComponentListItemData data)
public void AddItem(UTKComponentListItemDataBase data)
{
_originalRoots.Add(data);
CreateTabs();
@@ -285,11 +285,11 @@ namespace UVC.UIToolkit.Window
}
/// <summary>
/// 지정된 부모 항목의 자식으로 새 항목을 추가합니다.
/// 지정된 부모 카테고리의 자식으로 새 항목을 추가합니다.
/// </summary>
/// <param name="parent">부모 항목</param>
/// <param name="parent">부모 카테고리</param>
/// <param name="data">추가할 항목 데이터</param>
public void AddItem(UTKComponentListItemData? parent, UTKComponentListItemData data)
public void AddItem(UTKComponentListCategoryData? parent, UTKComponentListItemDataBase data)
{
if (parent == null)
{
@@ -297,8 +297,7 @@ namespace UVC.UIToolkit.Window
return;
}
parent.children ??= new List<UTKComponentListItemData>();
parent.children.Add(data);
parent.Add(data);
// 탭 및 표시 갱신
CreateTabs();
@@ -309,7 +308,7 @@ namespace UVC.UIToolkit.Window
/// 트리에서 항목을 완전히 삭제합니다.
/// </summary>
/// <param name="data">삭제할 항목 데이터</param>
public void DeleteItem(UTKComponentListItemData data)
public void DeleteItem(UTKComponentListItemDataBase data)
{
if (RemoveItemFromRoots(data))
{
@@ -323,7 +322,7 @@ namespace UVC.UIToolkit.Window
/// </summary>
/// <param name="data">변경할 항목 데이터</param>
/// <param name="newName">새 이름</param>
public void SetItemName(UTKComponentListItemData data, string newName)
public void SetItemName(UTKComponentListItemDataBase data, string newName)
{
_componentList?.SetItemName(data, newName);
}
@@ -374,9 +373,9 @@ namespace UVC.UIToolkit.Window
_tabContainer.Add(allTab);
_tabButtons.Add(allTab);
// 카테고리별 탭 생성 (루트 항목들이 그룹이면 해당 이름으로 탭 생성)
// 카테고리별 탭 생성 (카테고리 타입인 항목들만 탭으로 표시)
var categories = _originalRoots
.Where(r => r.children != null && r.children.Count > 0)
.Where(r => r.IsCategory && r.children.Count > 0)
.Select(r => r.name)
.Distinct()
.ToList();
@@ -433,7 +432,7 @@ namespace UVC.UIToolkit.Window
/// </summary>
/// <param name="tabIndex">탭 인덱스 (-1: 전체)</param>
/// <returns>필터링된 데이터</returns>
private List<UTKComponentListItemData> FilterDataByTab(int tabIndex)
private List<UTKComponentListItemDataBase> FilterDataByTab(int tabIndex)
{
// 전체 탭이면 모든 데이터 반환
if (tabIndex == ALL_TAB_INDEX)
@@ -443,12 +442,12 @@ namespace UVC.UIToolkit.Window
// 카테고리 탭이면 해당 카테고리의 자식들만 반환 (부모 없이 자식만 표시)
var categories = _originalRoots
.Where(r => r.children != null && r.children.Count > 0)
.Where(r => r.IsCategory && r.children.Count > 0)
.ToList();
if (tabIndex >= 0 && tabIndex < categories.Count)
{
return categories[tabIndex].children ?? new List<UTKComponentListItemData>();
return categories[tabIndex].children ?? new List<UTKComponentListItemDataBase>();
}
return _originalRoots;
@@ -461,7 +460,7 @@ namespace UVC.UIToolkit.Window
/// </summary>
/// <param name="target">삭제할 항목</param>
/// <returns>삭제 성공 여부</returns>
private bool RemoveItemFromRoots(UTKComponentListItemData target)
private bool RemoveItemFromRoots(UTKComponentListItemDataBase target)
{
if (_originalRoots.Remove(target))
{
@@ -485,13 +484,17 @@ namespace UVC.UIToolkit.Window
/// <param name="parent">부모 노드</param>
/// <param name="target">삭제할 대상 항목</param>
/// <returns>삭제 성공 여부</returns>
private bool RemoveFromChildren(UTKComponentListItemData parent, UTKComponentListItemData target)
private bool RemoveFromChildren(UTKComponentListItemDataBase parent, UTKComponentListItemDataBase target)
{
if (parent.children == null || parent.children.Count == 0) return false;
if (parent.children.Remove(target))
// 카테고리인 경우에만 자식에서 삭제 가능
if (parent is UTKComponentListCategoryData category)
{
return true;
if (category.Remove(target))
{
return true;
}
}
foreach (var child in parent.children)