UTKAccodion 완료. UTKComponentList 수정 중

This commit is contained in:
logonkhi
2026-01-22 20:12:22 +09:00
parent e00953de52
commit 59d473c87b
59 changed files with 2482 additions and 498 deletions

View File

@@ -22,7 +22,8 @@
"mcp__UnityMCP__manage_scene", "mcp__UnityMCP__manage_scene",
"Bash(git checkout:*)", "Bash(git checkout:*)",
"Bash(git -C \"d:/works/2025/02.Studio/dev/base/XRBase\" checkout HEAD -- \"Assets/Resources/UIToolkit/List/UTKMultiColumnTreeView.uss\" \"Assets/Resources/UIToolkit/List/UTKMultiColumnListView.uss\" \"Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss\" \"Assets/Resources/UIToolkit/Tab/UTKTabView.uss\" \"Assets/Resources/UIToolkit/List/UTKListView.uss\" \"Assets/Resources/UIToolkit/Style/UTKComponents.uss\")", "Bash(git -C \"d:/works/2025/02.Studio/dev/base/XRBase\" checkout HEAD -- \"Assets/Resources/UIToolkit/List/UTKMultiColumnTreeView.uss\" \"Assets/Resources/UIToolkit/List/UTKMultiColumnListView.uss\" \"Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss\" \"Assets/Resources/UIToolkit/Tab/UTKTabView.uss\" \"Assets/Resources/UIToolkit/List/UTKListView.uss\" \"Assets/Resources/UIToolkit/Style/UTKComponents.uss\")",
"Bash(cmd /c \"cd /d d:\\\\works\\\\2025\\\\02.Studio\\\\dev\\\\base\\\\XRBase && git log --oneline -10 -- Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss\")" "Bash(cmd /c \"cd /d d:\\\\works\\\\2025\\\\02.Studio\\\\dev\\\\base\\\\XRBase && git log --oneline -10 -- Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss\")",
"Bash(New-Item -ItemType Directory -Path \"d:\\\\works\\\\2025\\\\02.Studio\\\\dev\\\\base\\\\XRBase\\\\Assets\\\\Resources\\\\UIToolkit\\\\Sample\\\\Window\" -Force)"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []

View File

@@ -287,6 +287,9 @@ namespace UVC.UIToolkit.Editor
sb.AppendLine($" /// // 아이콘 존재 여부 확인"); sb.AppendLine($" /// // 아이콘 존재 여부 확인");
sb.AppendLine($" /// if ({className}.HasIcon(\"search\")) {{ }}"); sb.AppendLine($" /// if ({className}.HasIcon(\"search\")) {{ }}");
sb.AppendLine(" /// "); sb.AppendLine(" /// ");
sb.AppendLine($" /// // 존재하는 유니코드 문자인지 확인");
sb.AppendLine($" /// if ({className}.IsIconChar(\"\")) {{ }}");
sb.AppendLine(" /// ");
sb.AppendLine($" /// // 전체 아이콘 이름 순회"); sb.AppendLine($" /// // 전체 아이콘 이름 순회");
sb.AppendLine($" /// foreach (var name in {className}.GetAllIconNames()) {{ }}"); sb.AppendLine($" /// foreach (var name in {className}.GetAllIconNames()) {{ }}");
sb.AppendLine(" /// "); sb.AppendLine(" /// ");
@@ -397,10 +400,10 @@ namespace UVC.UIToolkit.Editor
sb.AppendLine(" /// </summary>"); sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"element\">스타일을 적용할 요소</param>"); sb.AppendLine(" /// <param name=\"element\">스타일을 적용할 요소</param>");
sb.AppendLine(" /// <param name=\"fontSize\">폰트 크기 (기본값: 24)</param>"); sb.AppendLine(" /// <param name=\"fontSize\">폰트 크기 (기본값: 24)</param>");
sb.AppendLine(" public static void ApplyIconStyle(VisualElement element, int fontSize = 24)"); sb.AppendLine(" public static void ApplyIconStyle(VisualElement element, int? fontSize = 24)");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" element.style.unityFontDefinition = GetFontDefinition();"); sb.AppendLine(" element.style.unityFontDefinition = GetFontDefinition();");
sb.AppendLine(" element.style.fontSize = fontSize;"); sb.AppendLine(" if(fontSize != null) element.style.fontSize = fontSize.Value;");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(); sb.AppendLine();
sb.AppendLine(" /// <summary>"); sb.AppendLine(" /// <summary>");
@@ -462,6 +465,7 @@ namespace UVC.UIToolkit.Editor
sb.AppendLine(" /// <returns>아이콘 문자, 없으면 빈 문자열</returns>"); sb.AppendLine(" /// <returns>아이콘 문자, 없으면 빈 문자열</returns>");
sb.AppendLine(" public static string GetIcon(string iconName)"); sb.AppendLine(" public static string GetIcon(string iconName)");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" if(IsIconChar(iconName)) return iconName;");
sb.AppendLine(" return _iconsByName.TryGetValue(iconName, out var icon) ? icon : string.Empty;"); sb.AppendLine(" return _iconsByName.TryGetValue(iconName, out var icon) ? icon : string.Empty;");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(); sb.AppendLine();
@@ -472,6 +476,12 @@ namespace UVC.UIToolkit.Editor
sb.AppendLine(" public static bool HasIcon(string iconName) => _iconsByName.ContainsKey(iconName);"); sb.AppendLine(" public static bool HasIcon(string iconName) => _iconsByName.ContainsKey(iconName);");
sb.AppendLine(); sb.AppendLine();
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 유니코드 문자로 아이콘이 존재하는지 확인합니다.");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" public static bool IsIconChar(string iconChar) => _iconsByName.Values.Contains(iconChar);");
sb.AppendLine();
sb.AppendLine(" /// <summary>"); sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 모든 아이콘 이름 목록을 반환합니다."); sb.AppendLine(" /// 모든 아이콘 이름 목록을 반환합니다.");
sb.AppendLine(" /// </summary>"); sb.AppendLine(" /// </summary>");

View File

@@ -1,5 +1,6 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ui="UnityEngine.UIElements" xmlns:ui="UnityEngine.UIElements"
xmlns:utk="UVC.UIToolkit"
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
editor-extension-mode="False"> editor-extension-mode="False">
<!-- <!--
@@ -13,7 +14,7 @@
- container: 메인 컨테이너 - container: 메인 컨테이너
- search-container: 검색 영역 - search-container: 검색 영역
- search-field: 검색어 입력 필드 - search-field: 검색어 입력 필드
- clear-btn: 검색어 지우기 버튼 - clear-btn: UTKButton 검색어 지우기 버튼
- search-result-label: 검색 결과 건수 표시 - search-result-label: 검색 결과 건수 표시
- accordion-tree-view: TreeView (가상화 지원) - accordion-tree-view: TreeView (가상화 지원)
--> -->
@@ -23,7 +24,7 @@
<ui:TextField name="search-field" <ui:TextField name="search-field"
placeholder-text="검색" placeholder-text="검색"
class="accordion-search-field"> class="accordion-search-field">
<ui:Button name="clear-btn" class="accordion-clear-button" /> <utk:UTKButton name="clear-btn" variant="Text" icon-only="true" class="accordion-clear-button" />
</ui:TextField> </ui:TextField>
<!-- 검색 결과 라벨 (기본 숨김) --> <!-- 검색 결과 라벨 (기본 숨김) -->

View File

@@ -22,22 +22,21 @@
margin: 0 0 var(--space-m) 0; margin: 0 0 var(--space-m) 0;
} }
/* ============================================
Clear 버튼 (Clear Button) - UTKButton 스타일 오버라이드
============================================ */
.accordion-clear-button { .accordion-clear-button {
width: 16px; width: 16px;
height: 16px; height: 16px;
min-width: 16px;
min-height: 16px;
border-width: 0; border-width: 0;
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: transparent;
background-image: resource('UIToolkit/Images/btn_close_16');
position: absolute; position: absolute;
right: var(--space-s); right: var(--space-s);
align-self: center; align-self: center;
-unity-background-image-tint-color: var(--color-text-secondary);
}
.accordion-clear-button:hover {
-unity-background-image-tint-color: var(--color-text-primary);
} }
.accordion-search-result-label { .accordion-search-result-label {
@@ -220,6 +219,10 @@
-unity-background-scale-mode: scale-to-fit; -unity-background-scale-mode: scale-to-fit;
} }
.accordion-head-image .unity-label {
font-size: 16px;
}
.accordion-content-button { .accordion-content-button {
flex-grow: 1; flex-grow: 1;
height: 100%; height: 100%;
@@ -228,6 +231,7 @@
padding: 0; padding: 0;
margin: 0; margin: 0;
-unity-text-align: middle-left; -unity-text-align: middle-left;
justify-content: center;
} }
.accordion-content-button:hover { .accordion-content-button:hover {

View File

@@ -1,9 +1,35 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False"> <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<Style src="project://database/Assets/Resources/UIToolkit/List/UTKComponentList.uss?fileID=7433441132597879392&amp;guid=71cac0276293ce2479851f572ffbda27&amp;type=3#UTKComponentList" /> xmlns:ui="UnityEngine.UIElements"
xmlns:utk="UVC.UIToolkit"
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
editor-extension-mode="False">
<!--
UTKComponentList.uxml
컴포넌트 리스트의 메인 레이아웃입니다.
검색 필드와 TreeView로 구성됩니다.
구조:
- container: 메인 컨테이너
- search-field: 검색어 입력 필드
- clear-btn: UTKButton 검색어 지우기 버튼
- main-tree-view: 트리뷰
-->
<Style src="project://database/Assets/Resources/UIToolkit/List/UTKComponentList.uss" />
<ui:VisualElement name="container" class="tree-menu-container"> <ui:VisualElement name="container" class="tree-menu-container">
<ui:TextField name="search-field" placeholder-text="검색" class="search-field" style="height: 24px; margin-bottom: 12px;"> <!-- 검색 필드 -->
<ui:Button name="clear-btn" style="width: 16px; height: 16px; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource(&apos;UIToolkit/Images/btn_close_16&apos;); margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; align-self: center; position: absolute; right: 4px; -unity-background-image-tint-color: rgb(180, 180, 180);" /> <ui:TextField name="search-field"
placeholder-text="검색"
class="search-field">
<utk:UTKButton name="clear-btn" variant="Text" icon-only="true" class="search-clear-button" />
</ui:TextField> </ui:TextField>
<ui:TreeView name="main-tree-view" view-data-key="model-tree-view" fixed-item-height="18" auto-expand="false" horizontal-scrolling="true" selection-type="Multiple" style="flex-grow: 1;" /> <!-- 트리뷰 -->
<ui:TreeView name="main-tree-view"
view-data-key="model-tree-view"
fixed-item-height="18"
auto-expand="false"
horizontal-scrolling="true"
selection-type="Multiple"
style="flex-grow: 1;" />
</ui:VisualElement> </ui:VisualElement>
</ui:UXML> </ui:UXML>

View File

@@ -1,7 +1,7 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False"> <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Assets/Resources/UIToolkit/Window/UTKComponentListWindow.uss?fileID=7433441132597879392&amp;guid=3df97248d26591046ab077258a2e2e44&amp;type=3#UTKComponentListWindow" /> <Style src="project://database/Assets/Resources/UIToolkit/Window/UTKComponentListWindow.uss?fileID=7433441132597879392&amp;guid=3df97248d26591046ab077258a2e2e44&amp;type=3#UTKComponentListWindow" />
<ui:VisualElement name="item-root" class="tree-item-container" style="flex-direction: row; justify-content: space-between; height: 22px; padding-left: 0; padding-right: 8px; align-items: center; padding-top: 0; padding-bottom: 0;"> <ui:VisualElement name="item-root" class="tree-item-container" style="flex-direction: row; justify-content: space-between; height: 22px; padding-left: 0; padding-right: 8px; align-items: center; padding-top: 0; padding-bottom: 0;">
<ui:Label name="item-label" text="Item Name" class="item-label-style" style="flex-grow: 1; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-left: 4px; padding-top: 0; padding-bottom: 0; padding-right: 4px; color: rgb(204, 204, 204); font-size: 11px; -unity-font-definition: resource(&apos;Fonts/Pretendard/Pretendard-Bold&apos;); -unity-text-align: middle-left; height: 22px;" /> <ui:Label name="item-label" text="Item Name" class="item-label-style" style="flex-grow: 1; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-left: 4px; padding-top: 0; padding-bottom: 0; padding-right: 4px; color: rgb(204, 204, 204); font-size: 12px; -unity-font-definition: resource(&apos;Fonts/Pretendard/Pretendard-Bold&apos;); -unity-text-align: middle-left; height: 22px;" />
<ui:Button name="setting-btn" class="visibility-toggle" style="background-image: resource(&apos;UIToolkit/Images/icon_setting_22&apos;); -unity-text-align: middle-right; width: 22px; height: 22px;" /> <ui:Button name="setting-btn" class="visibility-toggle" style="background-image: resource(&apos;UIToolkit/Images/icon_setting_22&apos;); -unity-text-align: middle-right; width: 22px; height: 22px;" />
<ui:Label name="badge-label" text="0" class="item-label-style" style="flex-grow: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-left: 4px; padding-top: 0; padding-bottom: 0; padding-right: 4px; color: rgb(255, 255, 255); font-size: 9px; -unity-font-definition: resource(&apos;Fonts/Pretendard/Pretendard-Medium&apos;); min-width: 18px; height: 18px; -unity-text-align: middle-center; align-items: center; border-top-left-radius: 9px; border-top-right-radius: 9px; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; background-color: rgb(69, 69, 69);" /> <ui:Label name="badge-label" text="0" class="item-label-style" style="flex-grow: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-left: 4px; padding-top: 0; padding-bottom: 0; padding-right: 4px; color: rgb(255, 255, 255); font-size: 9px; -unity-font-definition: resource(&apos;Fonts/Pretendard/Pretendard-Medium&apos;); min-width: 18px; height: 18px; -unity-text-align: middle-center; align-items: center; border-top-left-radius: 9px; border-top-right-radius: 9px; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; background-color: rgb(69, 69, 69);" />
</ui:VisualElement> </ui:VisualElement>

View File

@@ -1,7 +1,7 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False"> <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Assets/Resources/UIToolkit/Window/UTKComponentListWindow.uss?fileID=7433441132597879392&amp;guid=3df97248d26591046ab077258a2e2e44&amp;type=3#UTKComponentListWindow" /> <Style src="project://database/Assets/Resources/UIToolkit/Window/UTKComponentListWindow.uss?fileID=7433441132597879392&amp;guid=3df97248d26591046ab077258a2e2e44&amp;type=3#UTKComponentListWindow" />
<ui:VisualElement name="item-root" class="tree-item-container" style="flex-direction: row; justify-content: space-between; height: 22px; padding-left: 0; padding-right: 8px; align-items: center; padding-top: 0; padding-bottom: 0;"> <ui:VisualElement name="item-root" class="tree-item-container" style="flex-direction: row; justify-content: space-between; height: 22px; padding-left: 0; padding-right: 8px; align-items: center; padding-top: 0; padding-bottom: 0;">
<ui:Label name="item-label" text="Item Name" class="item-label-style" style="flex-grow: 1; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-left: 4px; padding-top: 0; padding-bottom: 0; padding-right: 4px; color: rgb(204, 204, 204); font-size: 13px; -unity-font-definition: resource(&apos;Fonts/Pretendard/Pretendard-Medium&apos;); -unity-text-align: middle-left;" /> <ui:Label name="item-label" text="Item Name" class="item-label-style" style="flex-grow: 1; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-left: 4px; padding-top: 0; padding-bottom: 0; padding-right: 4px; color: rgb(204, 204, 204); font-size: 11px; -unity-font-definition: resource(&apos;Fonts/Pretendard/Pretendard-Medium&apos;); -unity-text-align: middle-left;" />
<ui:Button name="search-btn" class="visibility-toggle" style="width: 22px; background-image: resource(&apos;UIToolkit/Images/icon_search_22x16&apos;);" /> <ui:Button name="search-btn" class="visibility-toggle" style="width: 22px; background-image: resource(&apos;UIToolkit/Images/icon_search_22x16&apos;);" />
<ui:Button name="visibility-btn" class="visibility-toggle" style="width: 22px;" /> <ui:Button name="visibility-btn" class="visibility-toggle" style="width: 22px;" />
</ui:VisualElement> </ui:VisualElement>

View File

@@ -1,5 +1,12 @@
/*
* UTKComponentList.uss
*
* UTKComponentList 컴포넌트의 스타일 정의입니다.
* 테마 지원: var(--color-*) 변수 사용
*/
.tree-menu-container { .tree-menu-container {
background-color: rgb(37, 37, 38); background-color: var(--color-bg-panel);
align-self: stretch; align-self: stretch;
padding: 5px; padding: 5px;
padding-top: 0; padding-top: 0;
@@ -14,6 +21,24 @@
margin-top: 0; margin-top: 0;
margin-right: 0; margin-right: 0;
margin-left: 0; margin-left: 0;
height: 24px;
}
/* ============================================
Clear 버튼 (Clear Button) - UTKButton 스타일 오버라이드
============================================ */
.search-clear-button {
width: 16px;
height: 16px;
min-width: 16px;
min-height: 16px;
border-width: 0;
margin: 0;
padding: 0;
align-self: center;
position: absolute;
right: 4px;
} }
/* 카테고리 확장/축소 토글 화살표 스타일 */ /* 카테고리 확장/축소 토글 화살표 스타일 */

View File

@@ -1,5 +1,6 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ui="UnityEngine.UIElements" xmlns:ui="UnityEngine.UIElements"
xmlns:utk="UVC.UIToolkit"
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
editor-extension-mode="False"> editor-extension-mode="False">
<!-- <!--
@@ -11,7 +12,7 @@
구조: 구조:
- container: 메인 컨테이너 - container: 메인 컨테이너
- search-field: 검색어 입력 필드 - search-field: 검색어 입력 필드
- clear-btn: 검색어 지우기 버튼 - clear-btn: UTKButton 검색어 지우기 버튼
- search-result-label: 검색 결과 건수 표시 - search-result-label: 검색 결과 건수 표시
- main-list-view: 아이템 목록 표시 - main-list-view: 아이템 목록 표시
--> -->
@@ -21,7 +22,7 @@
<ui:TextField name="search-field" <ui:TextField name="search-field"
placeholder-text="검색" placeholder-text="검색"
class="search-field"> class="search-field">
<ui:Button name="clear-btn" class="clear-button" /> <utk:UTKButton name="clear-btn" variant="Text" icon-only="true" class="clear-button" />
</ui:TextField> </ui:TextField>
<!-- 검색 결과 라벨 (기본 숨김) --> <!-- 검색 결과 라벨 (기본 숨김) -->

View File

@@ -1,5 +1,5 @@
.image-list-container { .image-list-container {
background-color: rgb(37, 37, 38); background-color: var(--color-bg-panel);
flex-grow: 1; flex-grow: 1;
padding: 0; padding: 0;
width: 100%; width: 100%;
@@ -13,27 +13,26 @@
margin-left: 0; margin-left: 0;
} }
/* ============================================
Clear 버튼 (Clear Button) - UTKButton 스타일 오버라이드
============================================ */
.clear-button { .clear-button {
width: 16px; width: 16px;
height: 16px; height: 16px;
min-width: 16px;
min-height: 16px;
border-width: 0; border-width: 0;
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: rgba(255, 255, 255, 0);
background-image: resource('UIToolkit/Images/btn_close_16');
position: absolute; position: absolute;
right: 4px; right: 4px;
align-self: center; align-self: center;
-unity-background-image-tint-color: rgb(180, 180, 180);
}
.clear-button:hover {
-unity-background-image-tint-color: rgb(220, 220, 220);
} }
.search-result-label { .search-result-label {
color: rgb(180, 180, 180); color: var(--color-text-secondary);
font-size: 11px; font-size: var(--font-size-label4);
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium'); -unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium');
margin-bottom: 8px; margin-bottom: 8px;
padding-left: 4px; padding-left: 4px;
@@ -127,8 +126,8 @@
} }
.item-label { .item-label {
color: rgb(204, 204, 204); color: var(--color-text-primary);
font-size: 11px; font-size: var(--font-size-label4);
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium'); -unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium');
-unity-text-align: upper-left; -unity-text-align: upper-left;
white-space: normal; white-space: normal;

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a5464101e260c7c47924298006864957
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit">
<Style src="../UTKSampleCommon.uss" />
<Style src="UTKWindowSample.uss" />
<VisualElement class="utk-sample-container">
<Label class="utk-sample-desc" text="아코디언 리스트를 윈도우 형태로 래핑한 컴포넌트입니다. 헤더(타이틀, 닫기 버튼)와 내부 UTKAccordionList로 구성됩니다." />
<VisualElement class="utk-sample-section">
<Label class="utk-sample-section__title" text="UTKAccordionListWindow" />
<VisualElement name="accordion-list-window-container" class="utk-window-sample-container" />
</VisualElement>
<!-- Code Sample -->
<VisualElement class="utk-code-sample-container">
<utk:UTKCodeBlock title="C#" code="// 1. 윈도우 참조 획득&#10;var accordionWindow = root.Q&lt;UTKAccordionListWindow&gt;(&quot;accordion-window&quot;);&#10;&#10;// 2. 윈도우 제목 및 닫기 버튼 설정&#10;accordionWindow.Title = &quot;프리팹 라이브러리&quot;;&#10;accordionWindow.ShowCloseButton = true;&#10;&#10;// 3. 데이터 구성&#10;var data = new List&lt;UTKAccordionItemData&gt;&#10;{&#10; new UTKAccordionItemData&#10; {&#10; nodeType = UTKAccordionNodeType.Section,&#10; name = &quot;가구&quot;,&#10; isExpanded = true,&#10; children = new List&lt;UTKAccordionItemData&gt;&#10; {&#10; new UTKAccordionItemData&#10; {&#10; nodeType = UTKAccordionNodeType.GridItem,&#10; name = &quot;의자&quot;,&#10; imagePath = &quot;Thumbnails/chair&quot;,&#10; prefabPath = &quot;Prefabs/Chair&quot;&#10; }&#10; }&#10; }&#10;};&#10;accordionWindow.SetData(data);&#10;&#10;// 4. 아이템 클릭 이벤트&#10;accordionWindow.OnItemClick += (item) =&gt; Debug.Log($&quot;클릭: {item.name}&quot;);&#10;&#10;// 5. 드래그 앤 드롭 이벤트&#10;accordionWindow.OnItemDrop += (item) =&gt;&#10;{&#10; var prefab = Resources.Load&lt;GameObject&gt;(item.prefabPath);&#10; Instantiate(prefab);&#10;};&#10;&#10;// 6. 윈도우 닫힘 이벤트&#10;accordionWindow.OnClosed += () =&gt; Debug.Log(&quot;윈도우 닫힘&quot;);" />
<utk:UTKCodeBlock title="UXML" code="&lt;utk:UTKAccordionListWindow name=&quot;accordion-window&quot; /&gt;" />
</VisualElement>
</VisualElement>
</UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d73f28c7e05421d4092bae5eff8c041a
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit">
<Style src="../UTKSampleCommon.uss" />
<Style src="UTKWindowSample.uss" />
<VisualElement class="utk-sample-container">
<Label class="utk-sample-desc" text="컴포넌트 리스트를 윈도우 형태로 래핑한 컴포넌트입니다. 계층 구조(카테고리/아이템)와 가시성 토글, 검색 기능을 제공합니다." />
<VisualElement class="utk-sample-section">
<Label class="utk-sample-section__title" text="UTKComponentListWindow" />
<VisualElement name="component-list-window-container" class="utk-window-sample-container" />
</VisualElement>
<!-- Code Sample -->
<VisualElement class="utk-code-sample-container">
<utk:UTKCodeBlock title="C#" code="// 1. 윈도우 참조 획득&#10;var componentWindow = root.Q&lt;UTKComponentListWindow&gt;(&quot;component-window&quot;);&#10;&#10;// 2. 윈도우 제목 및 닫기 버튼 설정&#10;componentWindow.Title = &quot;모델 리스트&quot;;&#10;componentWindow.ShowCloseButton = true;&#10;&#10;// 3. 데이터 구성 - 카테고리와 아이템&#10;var data = new List&lt;UTKComponentListItemDataBase&gt;&#10;{&#10; new UTKComponentListCategoryData&#10; {&#10; name = &quot;캐릭터&quot;,&#10; isExpanded = true,&#10; children = new List&lt;UTKComponentListItemDataBase&gt;&#10; {&#10; new UTKComponentListItemData&#10; {&#10; name = &quot;플레이어&quot;,&#10; ExternalKey = &quot;player_001&quot;,&#10; IsVisible = true&#10; }&#10; }&#10; }&#10;};&#10;componentWindow.SetData(data);&#10;&#10;// 4. 선택 이벤트&#10;componentWindow.OnItemSelected = (items) =&gt;&#10;{&#10; foreach (var item in items)&#10; Debug.Log($&quot;선택됨: {item.name}&quot;);&#10;};&#10;&#10;// 5. 가시성 변경 이벤트 (눈 아이콘)&#10;componentWindow.OnItemVisibilityChanged += (item, isVisible) =&gt;&#10;{&#10; FindGameObject(item.ExternalKey)?.SetActive(isVisible);&#10;};&#10;&#10;// 6. 삭제 이벤트 (Delete 키)&#10;componentWindow.EnabledDeleteItem = true;&#10;componentWindow.OnItemDeleted = (item) =&gt;&#10;{&#10; componentWindow.DeleteItem(item);&#10;};&#10;&#10;// 7. 윈도우 표시&#10;componentWindow.Show();" />
<utk:UTKCodeBlock title="UXML" code="&lt;utk:UTKComponentListWindow name=&quot;component-window&quot; /&gt;" />
</VisualElement>
</VisualElement>
</UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 25afcb79f01aa7f4ba3c5cb0105332f8
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit">
<Style src="../UTKSampleCommon.uss" />
<Style src="UTKWindowSample.uss" />
<VisualElement class="utk-sample-container">
<Label class="utk-sample-desc" text="컴포넌트 리스트에 탭 기능을 추가한 윈도우입니다. 카테고리별 자동 탭 생성과 검색 기능을 제공합니다." />
<VisualElement class="utk-sample-section">
<Label class="utk-sample-section__title" text="UTKComponentTabListWindow" />
<VisualElement name="component-tab-list-window-container" class="utk-window-sample-container" />
</VisualElement>
<!-- Code Sample -->
<VisualElement class="utk-code-sample-container">
<utk:UTKCodeBlock title="C#" code="// 1. 윈도우 참조 획득&#10;var tabWindow = root.Q&lt;UTKComponentTabListWindow&gt;(&quot;tab-window&quot;);&#10;&#10;// 2. 윈도우 제목 설정&#10;tabWindow.Title = &quot;모델 라이브러리&quot;;&#10;tabWindow.ShowCloseButton = true;&#10;&#10;// 3. 데이터 구성 (카테고리가 탭으로 자동 생성)&#10;var data = new List&lt;UTKComponentListItemDataBase&gt;&#10;{&#10; new UTKComponentListCategoryData&#10; {&#10; name = &quot;캐릭터&quot;, // -&gt; 첫 번째 탭&#10; children = new List&lt;UTKComponentListItemDataBase&gt;&#10; {&#10; new UTKComponentListItemData { name = &quot;Player&quot; }&#10; }&#10; },&#10; new UTKComponentListCategoryData&#10; {&#10; name = &quot;환경&quot;, // -&gt; 두 번째 탭&#10; children = new List&lt;UTKComponentListItemDataBase&gt;&#10; {&#10; new UTKComponentListItemData { name = &quot;Tree&quot; }&#10; }&#10; }&#10;};&#10;tabWindow.SetData(data);&#10;&#10;// 4. 탭 선택 (인덱스 기반)&#10;tabWindow.SelectTab(-1); // 전체 (All)&#10;tabWindow.SelectTab(0); // 첫 번째 카테고리&#10;&#10;// 5. 탭 변경 이벤트&#10;tabWindow.OnTabSelected = (tabIndex) =&gt;&#10;{&#10; Debug.Log($&quot;선택된 탭: {tabIndex}&quot;);&#10;};&#10;&#10;// 6. 선택/가시성/삭제 이벤트 (UTKComponentListWindow와 동일)&#10;tabWindow.OnItemSelected = (items) =&gt; { };&#10;tabWindow.OnItemVisibilityChanged += (item, visible) =&gt; { };&#10;tabWindow.OnItemDeleted = (item) =&gt; { };" />
<utk:UTKCodeBlock title="UXML" code="&lt;utk:UTKComponentTabListWindow name=&quot;tab-window&quot; /&gt;" />
</VisualElement>
</VisualElement>
</UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 35ee99024ae0d734184ea6faf770fd30
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit">
<Style src="../UTKSampleCommon.uss" />
<Style src="UTKWindowSample.uss" />
<VisualElement class="utk-sample-container">
<Label class="utk-sample-desc" text="이미지 그리드 리스트를 윈도우 형태로 래핑한 컴포넌트입니다. 썸네일 이미지와 드래그 앤 드롭 기능을 제공합니다." />
<VisualElement class="utk-sample-section">
<Label class="utk-sample-section__title" text="UTKImageListWindow" />
<VisualElement name="image-list-window-container" class="utk-window-sample-container" />
</VisualElement>
<!-- Code Sample -->
<VisualElement class="utk-code-sample-container">
<utk:UTKCodeBlock title="C#" code="// 1. 윈도우 참조 획득&#10;var imageWindow = root.Q&lt;UTKImageListWindow&gt;(&quot;image-window&quot;);&#10;&#10;// 2. 윈도우 제목 설정&#10;imageWindow.Title = &quot;텍스처 라이브러리&quot;;&#10;imageWindow.ShowCloseButton = true;&#10;&#10;// 3. 데이터 구성&#10;var data = new List&lt;UTKImageListItemData&gt;&#10;{&#10; new UTKImageListItemData&#10; {&#10; itemName = &quot;Texture_01&quot;,&#10; imagePath = &quot;Textures/texture_01&quot;,&#10; externalId = &quot;tex_001&quot;&#10; },&#10; new UTKImageListItemData&#10; {&#10; itemName = &quot;Texture_02&quot;,&#10; imagePath = &quot;Textures/texture_02&quot;,&#10; externalId = &quot;tex_002&quot;&#10; }&#10;};&#10;imageWindow.SetData(data);&#10;&#10;// 4. 클릭 이벤트&#10;imageWindow.OnItemClicked = (item) =&gt;&#10;{&#10; Debug.Log($&quot;클릭: {item.itemName}&quot;);&#10;};&#10;&#10;// 5. 드래그 앤 드롭 이벤트&#10;imageWindow.OnItemBeginDrag += (item, screenPos) =&gt;&#10;{&#10; Debug.Log($&quot;드래그 시작: {item.itemName}&quot;);&#10;};&#10;&#10;imageWindow.OnItemDrop += (item) =&gt;&#10;{&#10; Debug.Log($&quot;드롭: {item.itemName}&quot;);&#10;};&#10;&#10;// 6. 리스트 영역 벗어남 감지&#10;imageWindow.OnDragExitList += (item, screenPos) =&gt;&#10;{&#10; Show3DPreview(item.externalId);&#10;};&#10;&#10;// 7. 드래그 고스트 표시&#10;imageWindow.ShowDragGhost = true;&#10;&#10;// 8. 아이템 추가/삭제&#10;imageWindow.AddItem(newItem);&#10;imageWindow.RemoveItem(item);" />
<utk:UTKCodeBlock title="UXML" code="&lt;utk:UTKImageListWindow name=&quot;image-window&quot; /&gt;" />
</VisualElement>
</VisualElement>
</UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 500d626a4cd6fa3468ecb877e41f84d2
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit">
<Style src="../UTKSampleCommon.uss" />
<Style src="UTKWindowSample.uss" />
<VisualElement class="utk-sample-container">
<Label class="utk-sample-desc" text="트리뷰 리스트를 윈도우 형태로 래핑한 컴포넌트입니다. 계층적 데이터 구조와 가시성 토글, 검색 기능을 제공합니다." />
<VisualElement class="utk-sample-section">
<Label class="utk-sample-section__title" text="UTKTreeListWindow" />
<VisualElement name="tree-list-window-container" class="utk-window-sample-container" />
</VisualElement>
<!-- Code Sample -->
<VisualElement class="utk-code-sample-container">
<utk:UTKCodeBlock title="C#" code="// 1. 윈도우 참조 획득&#10;var treeWindow = root.Q&lt;UTKTreeListWindow&gt;(&quot;tree-window&quot;);&#10;&#10;// 2. 윈도우 제목 설정&#10;treeWindow.Title = &quot;씬 계층 구조&quot;;&#10;treeWindow.ShowCloseButton = true;&#10;&#10;// 3. 트리 데이터 구성&#10;var data = new List&lt;UTKTreeListItemData&gt;&#10;{&#10; new UTKTreeListItemData&#10; {&#10; name = &quot;Root&quot;,&#10; isExpanded = true,&#10; children = new List&lt;UTKTreeListItemData&gt;&#10; {&#10; new UTKTreeListItemData&#10; {&#10; name = &quot;Child 1&quot;,&#10; ExternalKey = &quot;obj_001&quot;,&#10; IsVisible = true&#10; },&#10; new UTKTreeListItemData&#10; {&#10; name = &quot;Child 2&quot;,&#10; children = new List&lt;UTKTreeListItemData&gt;&#10; {&#10; new UTKTreeListItemData { name = &quot;Grandchild&quot; }&#10; }&#10; }&#10; }&#10; }&#10;};&#10;treeWindow.SetData(data);&#10;&#10;// 4. 선택 이벤트&#10;treeWindow.OnItemSelected = (items) =&gt;&#10;{&#10; foreach (var item in items)&#10; HighlightGameObject(item.ExternalKey);&#10;};&#10;&#10;// 5. 가시성 변경 이벤트&#10;treeWindow.OnItemVisibilityChanged += (item, isVisible) =&gt;&#10;{&#10; FindGameObject(item.ExternalKey)?.SetActive(isVisible);&#10;};&#10;&#10;// 6. 삭제 이벤트&#10;treeWindow.EnabledDeleteItem = true;&#10;treeWindow.OnItemDeleted = (item) =&gt;&#10;{&#10; treeWindow.DeleteItem(item);&#10;};&#10;&#10;// 7. 프로그래밍 방식 선택&#10;treeWindow.SelectByItemId(itemId);&#10;treeWindow.ClearSelection();" />
<utk:UTKCodeBlock title="UXML" code="&lt;utk:UTKTreeListWindow name=&quot;tree-window&quot; /&gt;" />
</VisualElement>
</VisualElement>
</UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f7ffda55e7f57d14cbfa736a81e6edb4
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,24 @@
/*
* ===================================
* UTKWindowSample.uss
* Styles for UTK Window component samples
* ===================================
*/
/* ===================================
Window Sample Container
=================================== */
.utk-window-sample-container {
width: 350px;
height: 400px;
border-width: 1px;
border-color: var(--color-border-light);
border-radius: 8px;
background-color: var(--color-bg-tertiary);
overflow: hidden;
}
.utk-window-sample-container > * {
flex-grow: 1;
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 3df97248d26591046ab077258a2e2e44 guid: f29f5250983c71746a3384dd4af8a5c4
ScriptedImporter: ScriptedImporter:
internalIDToNameTable: [] internalIDToNameTable: []
externalObjects: {} externalObjects: {}

View File

@@ -412,90 +412,6 @@
flex-shrink: 1; flex-shrink: 1;
} }
/* ===================================
Dropdown Popup Menu
=================================== */
.unity-base-dropdown__container-outer {
border-radius: var(--radius-m);
border-width: var(--border-width);
border-color: var(--color-base-06);
overflow: hidden;
}
.unity-base-dropdown {
background-color: var(--color-base-01);
padding: var(--space-xs);
margin-top: 2px;
}
.unity-base-dropdown__item {
height: 28px;
padding-left: var(--space-m);
padding-right: var(--space-m);
padding-top: var(--space-xs);
padding-bottom: var(--space-xs);
background-color: transparent;
border-radius: var(--radius-xs);
transition-duration: var(--anim-fast);
transition-property: background-color;
cursor: resource('UIToolkit/Images/cursor_point_white_32') 14 5;
}
.unity-base-dropdown__item:hover:enabled {
background-color: var(--color-collection-item-hover) ;
}
.unity-base-dropdown__item:checked {
background-color: var(--color-blue-05);
}
.unity-base-dropdown__item:checked:hover {
background-color: var(--color-blue-06);
}
/* 드롭다운 항목 콘텐츠 */
.unity-base-dropdown__item-content {
flex-direction: row;
align-items: center;
}
.unity-base-dropdown__item:hover .unity-base-dropdown__item-content {
background-color: var(--color-collection-item-hover);
}
.unity-base-dropdown__item:checked .unity-base-dropdown__item-content {
background-color: var(--color-blue-05);
}
.unity-base-dropdown__item:checked:hover .unity-base-dropdown__item-content {
background-color: var(--color-blue-06);
}
/* 드롭다운 항목 라벨 */
.unity-base-dropdown__label {
font-size: 12px;
color: var(--color-base-20);
-unity-text-align: middle-left;
padding-left: 0;
padding-right: 0;
}
.unity-base-dropdown__item:checked .unity-base-dropdown__label {
color: var(--color-base-01);
}
/* 드롭다운 체크마크 */
.unity-base-dropdown__item .unity-base-dropdown__checkmark {
width: 16px;
height: 16px;
min-width: 16px;
min-height: 16px;
-unity-background-image-tint-color: var(--color-base-01);
}
/* =================================== /* ===================================
Border Radius Utilities Border Radius Utilities
=================================== */ =================================== */

View File

@@ -3,3 +3,5 @@
@import url("UTKDefaultStyle.uss"); @import url("UTKDefaultStyle.uss");

View File

@@ -37,6 +37,9 @@
/* 드롭다운 팝업 컨테이너 - border 제거 (최외곽 컨테이너에만 border 적용) */ /* 드롭다운 팝업 컨테이너 - border 제거 (최외곽 컨테이너에만 border 적용) */
.unity-base-dropdown { .unity-base-dropdown {
background-color: var(--color-base-01);
padding: var(--space-xs);
margin-top: 2px;
border-width: 0 ; border-width: 0 ;
} }
@@ -53,10 +56,58 @@
background-color: var(--color-collection-item-hover) ; background-color: var(--color-collection-item-hover) ;
} }
/* 드롭다운 항목 콘텐츠 */
.unity-base-dropdown__item-content {
flex-direction: row;
align-items: center;
}
.unity-base-dropdown__item:hover:enabled .unity-base-dropdown__item-content { .unity-base-dropdown__item:hover:enabled .unity-base-dropdown__item-content {
background-color: var(--color-collection-item-hover) ; background-color: var(--color-collection-item-hover) ;
} }
.unity-base-dropdown__item:checked {
background-color: var(--color-blue-05);
}
.unity-base-dropdown__item:checked:hover {
background-color: var(--color-blue-06);
}
.unity-base-dropdown__item:hover .unity-base-dropdown__item-content {
background-color: var(--color-collection-item-hover);
}
.unity-base-dropdown__item:checked .unity-base-dropdown__item-content {
background-color: var(--color-blue-05);
}
.unity-base-dropdown__item:checked:hover .unity-base-dropdown__item-content {
background-color: var(--color-blue-06);
}
/* 드롭다운 항목 라벨 */
.unity-base-dropdown__label {
font-size: 12px;
color: var(--color-base-20);
-unity-text-align: middle-left;
padding-left: 0;
padding-right: 0;
}
.unity-base-dropdown__item:checked .unity-base-dropdown__label {
color: var(--color-base-01);
}
/* 드롭다운 체크마크 */
.unity-base-dropdown__item .unity-base-dropdown__checkmark {
width: 16px;
height: 16px;
min-width: 16px;
min-height: 16px;
-unity-background-image-tint-color: var(--color-base-01);
}
/* =================================== /* ===================================
ListView/TreeView 항목 텍스트 스타일 ListView/TreeView 항목 텍스트 스타일
@@ -92,6 +143,7 @@ ListView/TreeView 항목 텍스트 스타일
.unity-tree-view__item-content { .unity-tree-view__item-content {
align-self: center; align-self: center;
flex-direction: row;
} }
/* TreeView 항목 텍스트 색상 */ /* TreeView 항목 텍스트 색상 */
@@ -135,10 +187,10 @@ TreeView 항목 스타일
/* TreeView 토글 입력 영역 - 회전 중심 맞추기 */ /* TreeView 토글 입력 영역 - 회전 중심 맞추기 */
.unity-tree-view__item-toggle > .unity-toggle__input { .unity-tree-view__item-toggle > .unity-toggle__input {
width: 16px; width: 20px;
height: 16px; height: 20px;
min-width: 16px; min-width: 20px;
min-height: 16px; min-height: 20px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0; padding: 0;
@@ -147,10 +199,10 @@ TreeView 항목 스타일
/* TreeView 토글 체크마크(화살표 아이콘) */ /* TreeView 토글 체크마크(화살표 아이콘) */
.unity-tree-view__item-toggle > .unity-toggle__input > #unity-checkmark { .unity-tree-view__item-toggle > .unity-toggle__input > #unity-checkmark {
width: 16px; width: 20px;
height: 16px; height: 20px;
min-width: 16px; min-width: 20px;
min-height: 16px; min-height: 20px;
} }
/* TreeView 토글 체크됨(펼쳐진 상태) - 회전 없음 */ /* TreeView 토글 체크됨(펼쳐진 상태) - 회전 없음 */
@@ -170,6 +222,19 @@ TreeView 항목 스타일
min-height: 16px; min-height: 16px;
} }
.unity-base-dropdown__item {
height: 28px;
padding-left: var(--space-m);
padding-right: var(--space-m);
padding-top: var(--space-xs);
padding-bottom: var(--space-xs);
background-color: transparent;
border-radius: var(--radius-xs);
transition-duration: var(--anim-fast);
transition-property: background-color;
cursor: resource('UIToolkit/Images/cursor_point_white_32') 14 5;
}
/* 드롭다운 라벨 스타일 오버라이드 */ /* 드롭다운 라벨 스타일 오버라이드 */
.unity-base-dropdown__item .unity-base-dropdown__label { .unity-base-dropdown__item .unity-base-dropdown__label {
font-size: 12px; font-size: 12px;
@@ -185,6 +250,7 @@ TreeView 항목 스타일
margin-bottom: 0; margin-bottom: 0;
} }
#unity-tree-view__item-toggle > VisualElement > VisualElement { #unity-tree-view__item-toggle > VisualElement > VisualElement {
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
@@ -204,9 +270,9 @@ Textfield 항목 스타일
margin-left: 0; margin-left: 0;
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium'); -unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium');
/* --unity-selection-color: rgba(54, 98, 160, 0.651); 선택 색상 */ /* --unity-selection-color: rgba(54, 98, 160, 0.651); 선택 색상 */
--unity-cursor-color: rgb(255, 255, 255); /* 캐럿(커서) 색상 흰색 */ --unity-cursor-color: var(--color-base-01); /* 캐럿(커서) 색상 흰색 */
font-size: 13px; font-size: 13px;
color: rgb(204, 204, 204); color: var(--color-text-primary);
} }
.unity-base-text-field__input { .unity-base-text-field__input {
@@ -214,15 +280,10 @@ Textfield 항목 스타일
padding-right: 24px; padding-right: 24px;
padding-bottom: 4px; padding-bottom: 4px;
padding-left: 4px; padding-left: 4px;
border-top-left-radius: 0; border-radius: var(--radius-s);
border-top-right-radius: 0; border-width: var(--border-width);
border-bottom-right-radius: 0; border-color: var(--color-border);
border-bottom-left-radius: 0; background-color: var(--color-bg-input);
border-top-width: 0;
border-right-width: 0;
border-bottom-width: 0;
border-left-width: 0;
background-color: rgb(60, 60, 60);
} }
.unity-base-text-field__input--placeholder { .unity-base-text-field__input--placeholder {

View File

@@ -1,6 +1,6 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ui="UnityEngine.UIElements" xmlns:ui="UnityEngine.UIElements"
xmlns:uvc="UVC.UIToolkit.List" xmlns:utk="UVC.UIToolkit"
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
editor-extension-mode="False"> editor-extension-mode="False">
<!-- <!--
@@ -13,18 +13,18 @@
- container: 메인 컨테이너 - container: 메인 컨테이너
- header: 윈도우 헤더 - header: 윈도우 헤더
- title: 윈도우 제목 - title: 윈도우 제목
- close-btn: 닫기 버튼 - close-btn: UTKButton 닫기 버튼
- UTKAccordionList: 아코디언 리스트 컴포넌트 - UTKAccordionList: 아코디언 리스트 컴포넌트
--> -->
<Style src="project://database/Assets/Resources/UIToolkit/Window/UTKAccordionListWindow.uss" /> <!-- Style은 C# 코드에서 테마 적용 후 로드됩니다 -->
<ui:VisualElement name="container" class="accordion-list-window-container"> <ui:VisualElement name="container" class="accordion-list-window-container">
<!-- 헤더 영역 --> <!-- 헤더 영역 -->
<ui:VisualElement name="header" class="accordion-window-header"> <ui:VisualElement name="header" class="accordion-window-header">
<ui:Label name="title" text="ACCORDION" class="accordion-window-title" /> <ui:Label name="title" text="ACCORDION" class="accordion-window-title" />
<ui:Button name="close-btn" class="accordion-window-close-button" /> <utk:UTKButton name="close-btn" variant="Text" icon-only="true" class="accordion-window-close-button" />
</ui:VisualElement> </ui:VisualElement>
<!-- 내부 UTKAccordionList 컴포넌트 --> <!-- 내부 UTKAccordionList 컴포넌트 -->
<uvc:UTKAccordionList /> <utk:UTKAccordionList />
</ui:VisualElement> </ui:VisualElement>
</ui:UXML> </ui:UXML>

View File

@@ -3,6 +3,7 @@
* *
* UTKAccordionListWindow 컴포넌트의 스타일 정의입니다. * UTKAccordionListWindow 컴포넌트의 스타일 정의입니다.
* UTKAccordionList를 래핑하는 윈도우 형태의 컨테이너입니다. * UTKAccordionList를 래핑하는 윈도우 형태의 컨테이너입니다.
* 테마 지원: var(--color-*) 변수 사용
*/ */
/* ============================================ /* ============================================
@@ -22,7 +23,7 @@ UTKAccordionListWindow {
============================================ */ ============================================ */
.accordion-list-window-container { .accordion-list-window-container {
background-color: rgb(37, 37, 38); background-color: var(--color-bg-panel);
height: 100%; height: 100%;
width: 300px; width: 300px;
padding: 10px 20px 25px 20px; padding: 10px 20px 25px 20px;
@@ -43,8 +44,8 @@ UTKAccordionListWindow {
} }
.accordion-window-title { .accordion-window-title {
color: rgb(204, 204, 204); color: var(--color-text-primary);
font-size: 11px; font-size: var(--font-size-label3);
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium'); -unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium');
-unity-font-style: normal; -unity-font-style: normal;
margin: 0; margin: 0;
@@ -53,26 +54,17 @@ UTKAccordionListWindow {
} }
/* ============================================ /* ============================================
닫기 버튼 (Close Button) 닫기 버튼 (Close Button) - UTKButton 스타일 오버라이드
============================================ */ ============================================ */
.accordion-window-close-button { .accordion-window-close-button {
width: 22px; width: 22px;
height: 22px; height: 22px;
min-width: 22px;
min-height: 22px;
border-width: 0; border-width: 0;
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: rgba(0, 0, 0, 0);
background-image: resource('UIToolkit/Images/btn_close_22');
align-self: center; align-self: center;
display: none; /* 기본 숨김, 필요시 flex로 변경 */ display: none; /* 기본 숨김, 필요시 flex로 변경 */
} }
.accordion-window-close-button:hover {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 2px;
}
.accordion-window-close-button:active {
background-color: rgba(255, 255, 255, 0.2);
}

View File

@@ -1,59 +0,0 @@
UTKComponentListWindow {
height: 100%;
position: absolute;
top: 0;
left: 0;
bottom: 0;
}
UTKComponentTabListWindow {
height: 100%;
position: absolute;
top: 0;
left: 400px;
bottom: 0;
}
.container {
background-color: rgb(37, 37, 38);
height: 100%;
align-self: flex-start;
padding: 5px;
padding-top: 10px;
padding-bottom: 25px;
padding-left: 20px;
padding-right: 20px;
width: 300px;
}
.tab-button {
background-color: #1E1E1E;
border-width: 0;
border-radius: 0px;
padding-left: 12px;
padding-right: 12px;
padding-top: 4px;
padding-bottom: 4px;
margin-right: 1px;
margin-left: 0;
margin-top: 0;
margin-bottom: 0;
color: #A0A0A0;
font-size: 11px;
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium');
flex-shrink: 0;
}
.tab-button:hover {
background-color: #292929;
}
.tab-button-selected {
background-color: #303031;
color: rgb(255, 255, 255);
}
.tab-button-selected:hover {
background-color: #3A3D41;
}

View File

@@ -1,10 +1,27 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:uvc="UVC.UIToolkit.List" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False"> <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<Style src="project://database/Assets/Resources/UIToolkit/Window/UTKComponentListWindow.uss?fileID=7433441132597879392&amp;guid=3df97248d26591046ab077258a2e2e44&amp;type=3#UTKComponentListWindow" /> xmlns:ui="UnityEngine.UIElements"
xmlns:utk="UVC.UIToolkit"
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
editor-extension-mode="False">
<!--
UTKComponentListWindow.uxml
UTKComponentList를 래핑하는 윈도우 컴포넌트입니다.
헤더(타이틀, 닫기 버튼)와 내부 UTKComponentList로 구성됩니다.
구조:
- container: 메인 컨테이너
- header: 윈도우 헤더
- title: 윈도우 제목
- close-btn: UTKButton 닫기 버튼
- UTKComponentList: 컴포넌트 리스트
-->
<!-- Style은 C# 코드에서 테마 적용 후 로드됩니다 -->
<ui:VisualElement name="container" class="container" style="flex-grow: 1; flex-shrink: 0;"> <ui:VisualElement name="container" class="container" style="flex-grow: 1; flex-shrink: 0;">
<ui:VisualElement name="header" style="flex-direction: row; margin-bottom: 5px; justify-content: space-between; margin-top: 0;"> <ui:VisualElement name="header" class="component-window-header">
<ui:Label text="EXPLORER" style="color: rgb(204, 204, 204); -unity-font-style: normal; font-size: 11px; -unity-font-definition: resource(&apos;Fonts/Pretendard/Pretendard-Medium&apos;); margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; height: 24px; -unity-text-align: middle-left;" /> <ui:Label name="title" text="EXPLORER" class="component-window-title" />
<ui:Button name="close-btn" style="width: 22px; height: 22px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; background-color: rgba(188, 188, 188, 0); background-image: resource(&apos;UIToolkit/Images/btn_close_22&apos;); align-self: center; align-items: auto; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; display: none;" /> <utk:UTKButton name="close-btn" variant="Text" icon-only="true" class="component-window-close-button" />
</ui:VisualElement> </ui:VisualElement>
<uvc:UTKComponentList /> <utk:UTKComponentList />
</ui:VisualElement> </ui:VisualElement>
</ui:UXML> </ui:UXML>

View File

@@ -0,0 +1,108 @@
/*
* UTKComponentListWindow.uss
*
* UTKComponentListWindow 및 UTKComponentTabListWindow 컴포넌트의 스타일 정의입니다.
* 테마 지원: var(--color-*) 변수 사용
*/
UTKComponentListWindow {
height: 100%;
position: absolute;
top: 0;
left: 0;
bottom: 0;
}
UTKComponentTabListWindow {
height: 100%;
position: absolute;
top: 0;
left: 400px;
bottom: 0;
}
.container {
background-color: var(--color-bg-panel);
height: 100%;
align-self: flex-start;
padding: 5px;
padding-top: 10px;
padding-bottom: 25px;
padding-left: 20px;
padding-right: 20px;
width: 300px;
}
/* ============================================
헤더 (Header)
============================================ */
.component-window-header {
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
height: 24px;
flex-shrink: 0;
}
.component-window-title {
color: var(--color-text-primary);
font-size: var(--font-size-label3);
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium');
-unity-font-style: normal;
margin: 0;
padding: 0;
-unity-text-align: middle-left;
}
/* ============================================
닫기 버튼 (Close Button) - UTKButton 스타일 오버라이드
============================================ */
.component-window-close-button {
width: 22px;
height: 22px;
min-width: 22px;
min-height: 22px;
border-width: 0;
margin: 0;
padding: 0;
align-self: center;
display: none; /* 기본 숨김, 필요시 flex로 변경 */
}
/* ============================================
탭 버튼 (Tab Buttons)
============================================ */
.tab-button {
background-color: var(--color-base-03);
border-width: 0;
border-radius: 0px;
padding-left: 12px;
padding-right: 12px;
padding-top: 4px;
padding-bottom: 4px;
margin-right: 1px;
margin-left: 0;
margin-top: 0;
margin-bottom: 0;
color: var(--color-text-secondary);
font-size: var(--font-size-label3);
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium');
flex-shrink: 0;
}
.tab-button:hover {
background-color: var(--color-base-05);
}
.tab-button-selected {
background-color: var(--color-base-07);
color: var(--color-text-primary);
}
.tab-button-selected:hover {
background-color: var(--color-base-09);
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 5b2a67069d4f392478f4d1759c977c93 guid: 347141d660f7e3b4b8f54ced23166b82
ScriptedImporter: ScriptedImporter:
internalIDToNameTable: [] internalIDToNameTable: []
externalObjects: {} externalObjects: {}

View File

@@ -1,13 +1,32 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:uvc="UVC.UIToolkit.List" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False"> <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<Style src="project://database/Assets/Resources/UIToolkit/Window/UTKComponentListWindow.uss?fileID=7433441132597879392&amp;guid=3df97248d26591046ab077258a2e2e44&amp;type=3#UTKComponentListWindow" /> xmlns:ui="UnityEngine.UIElements"
xmlns:utk="UVC.UIToolkit"
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
editor-extension-mode="False">
<!--
UTKComponentTabListWindow.uxml
탭 기능이 포함된 UTKComponentList 윈도우 컴포넌트입니다.
헤더(타이틀, 닫기 버튼), 탭 영역, 내부 UTKComponentList로 구성됩니다.
구조:
- container: 메인 컨테이너
- header: 윈도우 헤더
- title: 윈도우 제목
- close-btn: UTKButton 닫기 버튼
- tab-scroll-view: 탭 스크롤 영역
- tab-container: 탭 버튼 컨테이너
- UTKComponentList: 컴포넌트 리스트
-->
<!-- Style은 C# 코드에서 테마 적용 후 로드됩니다 -->
<ui:VisualElement name="container" class="container" style="flex-grow: 1; flex-shrink: 0;"> <ui:VisualElement name="container" class="container" style="flex-grow: 1; flex-shrink: 0;">
<ui:VisualElement name="header" style="flex-direction: row; margin-bottom: 5px; justify-content: space-between; margin-top: 0;"> <ui:VisualElement name="header" class="component-window-header">
<ui:Label text="EXPLORER" style="color: rgb(204, 204, 204); -unity-font-style: normal; font-size: 11px; -unity-font-definition: resource(&apos;Fonts/Pretendard/Pretendard-Medium&apos;); margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; height: 24px; -unity-text-align: middle-left;" /> <ui:Label name="title" text="EXPLORER" class="component-window-title" />
<ui:Button name="close-btn" style="width: 22px; height: 22px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; background-color: rgba(188, 188, 188, 0); background-image: resource(&apos;UIToolkit/Images/btn_close_22&apos;); align-self: center; align-items: auto; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; display: none;" /> <utk:UTKButton name="close-btn" variant="Text" icon-only="true" class="component-window-close-button" />
</ui:VisualElement> </ui:VisualElement>
<ui:ScrollView name="tab-scroll-view" mode="Horizontal" horizontal-scroller-visibility="Hidden" vertical-scroller-visibility="Hidden" style="flex-shrink: 0; margin-bottom: 8px; max-height: 28px;"> <ui:ScrollView name="tab-scroll-view" mode="Horizontal" horizontal-scroller-visibility="Hidden" vertical-scroller-visibility="Hidden" style="flex-shrink: 0; margin-bottom: 8px; max-height: 28px;">
<ui:VisualElement name="tab-container" style="flex-direction: row; flex-shrink: 0;" /> <ui:VisualElement name="tab-container" style="flex-direction: row; flex-shrink: 0;" />
</ui:ScrollView> </ui:ScrollView>
<uvc:UTKComponentList style="flex-grow: 1; width: 100%;" /> <utk:UTKComponentList style="flex-grow: 1; width: 100%;" />
</ui:VisualElement> </ui:VisualElement>
</ui:UXML> </ui:UXML>

View File

@@ -1,6 +1,6 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ui="UnityEngine.UIElements" xmlns:ui="UnityEngine.UIElements"
xmlns:uvc="UVC.UIToolkit.List" xmlns:utk="UVC.UIToolkit"
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
editor-extension-mode="False"> editor-extension-mode="False">
<!-- <!--
@@ -13,18 +13,18 @@
- container: 메인 컨테이너 - container: 메인 컨테이너
- header: 윈도우 헤더 - header: 윈도우 헤더
- title: 윈도우 제목 - title: 윈도우 제목
- close-btn: 닫기 버튼 - close-btn: UTKButton 닫기 버튼
- UTKImageList: 이미지 리스트 컴포넌트 - UTKImageList: 이미지 리스트 컴포넌트
--> -->
<Style src="project://database/Assets/Resources/UIToolkit/Window/UTKImageListWindow.uss" /> <!-- Style은 C# 코드에서 테마 적용 후 로드됩니다 -->
<ui:VisualElement name="container" class="image-list-window-container"> <ui:VisualElement name="container" class="image-list-window-container">
<!-- 헤더 영역 --> <!-- 헤더 영역 -->
<ui:VisualElement name="header" class="window-header"> <ui:VisualElement name="header" class="window-header">
<ui:Label name="title" text="LIBRARY" class="window-title" /> <ui:Label name="title" text="LIBRARY" class="window-title" />
<ui:Button name="close-btn" class="window-close-button" /> <utk:UTKButton name="close-btn" variant="Text" icon-only="true" class="window-close-button" />
</ui:VisualElement> </ui:VisualElement>
<!-- 내부 UTKImageList 컴포넌트 --> <!-- 내부 UTKImageList 컴포넌트 -->
<uvc:UTKImageList /> <utk:UTKImageList />
</ui:VisualElement> </ui:VisualElement>
</ui:UXML> </ui:UXML>

View File

@@ -3,6 +3,7 @@
* *
* UTKImageListWindow 컴포넌트의 스타일 정의입니다. * UTKImageListWindow 컴포넌트의 스타일 정의입니다.
* UTKImageList를 래핑하는 윈도우 형태의 컨테이너입니다. * UTKImageList를 래핑하는 윈도우 형태의 컨테이너입니다.
* 테마 지원: var(--color-*) 변수 사용
*/ */
/* ============================================ /* ============================================
@@ -22,7 +23,7 @@ UTKImageListWindow {
============================================ */ ============================================ */
.image-list-window-container { .image-list-window-container {
background-color: rgb(37, 37, 38); background-color: var(--color-bg-panel);
height: 100%; height: 100%;
width: 300px; width: 300px;
padding: 10px 20px 25px 20px; padding: 10px 20px 25px 20px;
@@ -43,8 +44,8 @@ UTKImageListWindow {
} }
.window-title { .window-title {
color: rgb(204, 204, 204); color: var(--color-text-primary);
font-size: 11px; font-size: var(--font-size-label3);
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium'); -unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium');
-unity-font-style: normal; -unity-font-style: normal;
margin: 0; margin: 0;
@@ -53,26 +54,17 @@ UTKImageListWindow {
} }
/* ============================================ /* ============================================
닫기 버튼 (Close Button) 닫기 버튼 (Close Button) - UTKButton 스타일 오버라이드
============================================ */ ============================================ */
.window-close-button { .window-close-button {
width: 22px; width: 22px;
height: 22px; height: 22px;
min-width: 22px;
min-height: 22px;
border-width: 0; border-width: 0;
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: rgba(0, 0, 0, 0);
background-image: resource('UIToolkit/Images/btn_close_22');
align-self: center; align-self: center;
display: none; /* 기본 숨김, 필요시 flex로 변경 */ display: none; /* 기본 숨김, 필요시 flex로 변경 */
} }
.window-close-button:hover {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 2px;
}
.window-close-button:active {
background-color: rgba(255, 255, 255, 0.2);
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: b0076250b40d2ac45ab1bff4cd47920c guid: e1cdefa2dbb1dd7419c0f16ba456d4ab
ScriptedImporter: ScriptedImporter:
internalIDToNameTable: [] internalIDToNameTable: []
externalObjects: {} externalObjects: {}

View File

@@ -1,52 +0,0 @@
UTKTreeListWindow {
flex-grow: 1;
height: 100%;
align-self: flex-start;
}
.tree-menu-container {
background-color: rgb(37, 37, 38);
flex-grow: 1;
height: 100%;
align-self: flex-start;
padding: 5px;
padding-top: 10px;
padding-bottom: 25px;
padding-left: 20px;
padding-right: 20px;
width: 300px;
}
.search-field {
margin-bottom: 20px;
margin-top: 0;
margin-right: 0;
margin-left: 0;
}
.visibility-toggle {
background-color: rgba(0, 0, 0, 0);
border-width: 0;
width: 16px;
height: 16px;
margin-top: 0;
margin-bottom: 0;
padding-top: 0;
padding-bottom: 0;
margin-right: 0;
align-items: center;
justify-content: center;
padding-right: 0;
padding-left: 0;
margin-left: 0;
flex-shrink: 0;
background-image: resource('UIToolkit/Images/icon_eye_22x16');
}
.visibility-on {
background-image: resource('UIToolkit/Images/icon_eye_22x16');
}
.visibility-off {
background-image: resource('UIToolkit/Images/icon_eye_close_22x16');
}

View File

@@ -1,13 +1,33 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False"> <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<Style src="project://database/Assets/Resources/UIToolkit/Window/UTKTreeListWindow.uss?fileID=7433441132597879392&amp;guid=b0076250b40d2ac45ab1bff4cd47920c&amp;type=3#UTKTreeListWindow" /> xmlns:ui="UnityEngine.UIElements"
xmlns:uie="UnityEditor.UIElements"
xmlns:utk="UVC.UIToolkit"
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
editor-extension-mode="False">
<!--
UTKTreeListWindow.uxml
TreeView 기반의 계층 리스트 윈도우 컴포넌트입니다.
헤더(타이틀, 닫기 버튼), 검색 필드, TreeView로 구성됩니다.
구조:
- container: 메인 컨테이너
- header: 윈도우 헤더
- title: 윈도우 제목
- close-btn: UTKButton 닫기 버튼
- search-field: 검색 입력 필드
- clear-btn: UTKButton 검색어 지우기 버튼
- main-tree-view: TreeView
-->
<!-- Style은 C# 코드에서 테마 적용 후 로드됩니다 -->
<ui:VisualElement name="container" class="tree-menu-container"> <ui:VisualElement name="container" class="tree-menu-container">
<ui:VisualElement name="header" style="flex-direction: row; margin-bottom: 5px; justify-content: space-between; margin-top: 0;"> <ui:VisualElement name="header" class="tree-window-header">
<ui:Label text="HIERARCHY" style="color: rgb(204, 204, 204); -unity-font-style: normal; font-size: 11px; -unity-font-definition: resource(&apos;Fonts/Pretendard/Pretendard-Medium&apos;); margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; height: 24px; -unity-text-align: middle-left;" /> <ui:Label name="title" text="HIERARCHY" class="tree-window-title" />
<ui:Button name="close-btn" style="width: 22px; height: 22px; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; background-color: rgba(188, 188, 188, 0); background-image: resource(&apos;UIToolkit/Images/btn_close_22&apos;); align-self: center; align-items: auto; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; display: none;" /> <utk:UTKButton name="close-btn" variant="Ghost" icon-only="true" class="tree-window-close-button" />
</ui:VisualElement> </ui:VisualElement>
<ui:TextField name="search-field" placeholder-text="검색" class="search-field" style="height: 24px; margin-bottom: 12px;"> <ui:TextField name="search-field" placeholder-text="검색" class="search-field">
<ui:Button name="clear-btn" style="width: 16px; height: 16px; border-top-width: 0; border-right-width: 0; border-bottom-width: 0; border-left-width: 0; background-color: rgba(255, 255, 255, 0); background-image: resource(&apos;UIToolkit/Images/btn_close_16&apos;); margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; align-self: center; position: absolute; right: 4px; -unity-background-image-tint-color: rgb(180, 180, 180);" /> <utk:UTKButton name="clear-btn" variant="Text" icon-only="true" class="search-clear-button" />
</ui:TextField> </ui:TextField>
<ui:TreeView name="main-tree-view" view-data-key="model-tree-view" fixed-item-height="18" auto-expand="false" item-template="project://database/Assets/Resources/UIToolkit/List/UTKTreeListItem.uxml?fileID=9197481963319205126&amp;guid=87efc218ceca98347841e1e40ae18e7f&amp;type=3#UTKTreeListItem" horizontal-scrolling="true" selection-type="Multiple" style="flex-grow: 1;" /> <ui:TreeView name="main-tree-view" view-data-key="model-tree-view" fixed-item-height="18" auto-expand="false" item-template="project://database/Assets/Resources/UIToolkit/List/UTKTreeListItem.uxml" horizontal-scrolling="true" selection-type="Multiple" style="flex-grow: 1;" />
</ui:VisualElement> </ui:VisualElement>
</ui:UXML> </ui:UXML>

View File

@@ -0,0 +1,124 @@
/*
* UTKTreeListWindow.uss
*
* UTKTreeListWindow 컴포넌트의 스타일 정의입니다.
* 테마 지원: var(--color-*) 변수 사용
*/
UTKTreeListWindow {
flex-grow: 1;
height: 100%;
align-self: flex-start;
}
.tree-menu-container {
background-color: var(--color-bg-panel);
flex-grow: 1;
height: 100%;
align-self: flex-start;
padding: 5px;
padding-top: 10px;
padding-bottom: 25px;
padding-left: 20px;
padding-right: 20px;
width: 300px;
}
/* ============================================
헤더 (Header)
============================================ */
.tree-window-header {
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
height: 24px;
flex-shrink: 0;
}
.tree-window-title {
color: var(--color-text-primary);
font-size: var(--font-size-label3);
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Medium');
-unity-font-style: normal;
margin: 0;
padding: 0;
-unity-text-align: middle-left;
}
/* ============================================
닫기 버튼 (Close Button) - UTKButton 스타일 오버라이드
============================================ */
.tree-window-close-button {
width: 22px;
height: 22px;
min-width: 22px;
min-height: 22px;
border-width: 0;
margin: 0;
padding: 0;
align-self: center;
display: none; /* 기본 숨김, 필요시 flex로 변경 */
}
/* ============================================
검색 필드 (Search Field)
============================================ */
.search-field {
margin-bottom: 12px;
margin-top: 0;
margin-right: 0;
margin-left: 0;
height: 24px;
}
/* ============================================
Clear 버튼 (Clear Button) - UTKButton 스타일 오버라이드
============================================ */
.search-clear-button {
width: 16px;
height: 16px;
min-width: 16px;
min-height: 16px;
border-width: 0;
margin: 0;
padding: 0;
align-self: center;
position: absolute;
right: 4px;
}
/* ============================================
Visibility 토글 (Visibility Toggle)
============================================ */
.visibility-toggle {
background-color: rgba(0, 0, 0, 0);
border-width: 0;
width: 16px;
height: 16px;
margin-top: 0;
margin-bottom: 0;
padding-top: 0;
padding-bottom: 0;
margin-right: 0;
align-items: center;
justify-content: center;
padding-right: 0;
padding-left: 0;
margin-left: 0;
flex-shrink: 0;
background-image: resource('UIToolkit/Images/icon_eye_22x16');
}
.visibility-on {
background-image: resource('UIToolkit/Images/icon_eye_22x16');
}
.visibility-off {
background-image: resource('UIToolkit/Images/icon_eye_close_22x16');
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 94bace9f4fd4e854b80330dc68b0ebed
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0

View File

@@ -118,8 +118,8 @@ public class UTKAccordionListWindowSample : MonoBehaviour
Content = UTKAccordionContentSpec.FromText("Graphics", "open_graphics"), Content = UTKAccordionContentSpec.FromText("Graphics", "open_graphics"),
Tail = new List<UTKAccordionContentSpec> Tail = new List<UTKAccordionContentSpec>
{ {
UTKAccordionContentSpec.FromIconButton("Prefabs/UI/images/icon_refresh_22x22", "refresh_graphics", "새로고침"), UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_graphics", "새로고침"),
UTKAccordionContentSpec.FromIconButton("Prefabs/UI/images/icon_setting_22x22", "setting_graphics", "설정"), UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Settings, 12, "setting_graphics", "설정"),
} }
}, },
new UTKAccordionHorizontalItemData new UTKAccordionHorizontalItemData
@@ -128,8 +128,8 @@ public class UTKAccordionListWindowSample : MonoBehaviour
Content = UTKAccordionContentSpec.FromText("Audio", "open_audio"), Content = UTKAccordionContentSpec.FromText("Audio", "open_audio"),
Tail = new List<UTKAccordionContentSpec> Tail = new List<UTKAccordionContentSpec>
{ {
UTKAccordionContentSpec.FromIconButton("Prefabs/UI/images/icon_refresh_22x22", "refresh_audio", "새로고침"), UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_audio", "새로고침"),
UTKAccordionContentSpec.FromIconButton("Prefabs/UI/images/icon_setting_22x22", "setting_audio", "설정"), UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Settings, 12, "setting_audio", "설정"),
} }
}, },
new UTKAccordionHorizontalItemData new UTKAccordionHorizontalItemData
@@ -138,7 +138,7 @@ public class UTKAccordionListWindowSample : MonoBehaviour
Content = UTKAccordionContentSpec.FromText("Network", "open_network"), Content = UTKAccordionContentSpec.FromText("Network", "open_network"),
Tail = new List<UTKAccordionContentSpec> Tail = new List<UTKAccordionContentSpec>
{ {
UTKAccordionContentSpec.FromIconButton("Prefabs/UI/images/icon_refresh_22x22", "refresh_network", "새로고침"), UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_network", "새로고침"),
} }
} }
} }
@@ -695,15 +695,15 @@ public class UTKAccordionListWindowSample : MonoBehaviour
content: UTKAccordionContentSpec.FromText("Graphics", "open_graphics"), content: UTKAccordionContentSpec.FromText("Graphics", "open_graphics"),
tail: new List<UTKAccordionContentSpec> tail: new List<UTKAccordionContentSpec>
{ {
UTKAccordionContentSpec.FromIconButton("Prefabs/UI/images/icon_refresh_22x22", "refresh_graphics", "새로고침"), UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_graphics", "새로고침"),
UTKAccordionContentSpec.FromIconButton("Prefabs/UI/images/icon_setting_22x22", "setting_graphics", "설정"), UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Settings, 12, "setting_graphics", "설정"),
}) })
.AddHorizontalItem( .AddHorizontalItem(
head: UTKAccordionContentSpec.FromImage("Prefabs/UI/images/icon_side_tab_fleet_128"), head: UTKAccordionContentSpec.FromImage("Prefabs/UI/images/icon_side_tab_fleet_128"),
content: UTKAccordionContentSpec.FromText("Audio", "open_audio"), content: UTKAccordionContentSpec.FromText("Audio", "open_audio"),
tail: new List<UTKAccordionContentSpec> tail: new List<UTKAccordionContentSpec>
{ {
UTKAccordionContentSpec.FromIconButton("Prefabs/UI/images/icon_refresh_22x22", "refresh_audio", "새로고침"), UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_audio", "새로고침"),
}); });
roots.Add(settingsSection); roots.Add(settingsSection);

View File

@@ -97,6 +97,12 @@ public class UTKStyleGuideSample : MonoBehaviour
// Picker // Picker
["UTKColorPicker"] = "UIToolkit/Sample/Picker/UTKColorPickerSample", ["UTKColorPicker"] = "UIToolkit/Sample/Picker/UTKColorPickerSample",
["UTKDatePicker"] = "UIToolkit/Sample/Picker/UTKDatePickerSample", ["UTKDatePicker"] = "UIToolkit/Sample/Picker/UTKDatePickerSample",
// Window
["UTKAccordionListWindow"] = "UIToolkit/Sample/Window/UTKAccordionListWindowSample",
["UTKComponentListWindow"] = "UIToolkit/Sample/Window/UTKComponentListWindowSample",
["UTKComponentTabListWindow"] = "UIToolkit/Sample/Window/UTKComponentTabListWindowSample",
["UTKImageListWindow"] = "UIToolkit/Sample/Window/UTKImageListWindowSample",
["UTKTreeListWindow"] = "UIToolkit/Sample/Window/UTKTreeListWindowSample",
}; };
private static readonly Dictionary<string, string[]> ControlCategories = new() private static readonly Dictionary<string, string[]> ControlCategories = new()
@@ -112,6 +118,7 @@ public class UTKStyleGuideSample : MonoBehaviour
["Tab"] = new[] { "UTKTabView" }, ["Tab"] = new[] { "UTKTabView" },
["Modal"] = new[] { "UTKAlert", "UTKToast", "UTKTooltip" }, ["Modal"] = new[] { "UTKAlert", "UTKToast", "UTKTooltip" },
["Picker"] = new[] { "UTKColorPicker", "UTKDatePicker" }, ["Picker"] = new[] { "UTKColorPicker", "UTKDatePicker" },
["Window"] = new[] { "UTKAccordionListWindow", "UTKComponentListWindow", "UTKComponentTabListWindow", "UTKImageListWindow", "UTKTreeListWindow" },
}; };
/// <summary> /// <summary>
@@ -449,6 +456,21 @@ public class UTKStyleGuideSample : MonoBehaviour
case "UTKImage": case "UTKImage":
InitializeUTKImageSample(root); InitializeUTKImageSample(root);
break; break;
case "UTKAccordionListWindow":
InitializeAccordionListWindowSample(root);
break;
case "UTKComponentListWindow":
InitializeComponentListWindowSample(root);
break;
case "UTKComponentTabListWindow":
InitializeComponentTabListWindowSample(root);
break;
case "UTKImageListWindow":
InitializeImageListWindowSample(root);
break;
case "UTKTreeListWindow":
InitializeTreeListWindowSample(root);
break;
} }
} }
@@ -1642,4 +1664,303 @@ public class UTKStyleGuideSample : MonoBehaviour
} }
#endregion #endregion
#region Window Initializers
private void InitializeAccordionListWindowSample(VisualElement root)
{
var container = root.Q<VisualElement>("accordion-list-window-container");
if (container == null) return;
var accordionWindow = new UTKAccordionListWindow();
accordionWindow.Title = "프리팹 라이브러리";
accordionWindow.ShowCloseButton = true;
var data = new UTKAccordionData();
// ========================================
// 수평 레이아웃 섹션 1: Settings
// ========================================
var settingsSection = new UTKAccordionSectionData
{
Title = "Settings",
IsExpanded = true,
LayoutType = UTKAccordionLayoutType.Horizontal,
HorizontalItems = new List<UTKAccordionHorizontalItemData>
{
new UTKAccordionHorizontalItemData
{
Head = UTKAccordionContentSpec.FromImage(UTKMaterialIcons.LibraryAdd),
Content = UTKAccordionContentSpec.FromText("Graphics", "open_graphics"),
Tail = new List<UTKAccordionContentSpec>
{
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_graphics", "새로고침"),
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Settings, 12, "setting_graphics", "설정"),
}
},
new UTKAccordionHorizontalItemData
{
Head = UTKAccordionContentSpec.FromImage(UTKMaterialIcons.AudioFile),
Content = UTKAccordionContentSpec.FromText("Audio", "open_audio"),
Tail = new List<UTKAccordionContentSpec>
{
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_audio", "새로고침"),
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Settings, 12, "setting_audio", "설정"),
}
},
new UTKAccordionHorizontalItemData
{
Head = UTKAccordionContentSpec.FromImage(UTKMaterialIcons.Explore),
Content = UTKAccordionContentSpec.FromText("Network", "open_network"),
Tail = new List<UTKAccordionContentSpec>
{
UTKAccordionContentSpec.FromIconButton(UTKMaterialIcons.Refresh, 12, "refresh_network", "새로고침"),
}
}
}
};
data.Sections.Add(settingsSection);
// ========================================
// 수평 레이아웃 섹션 2: Components
// ========================================
var componentsSection = new UTKAccordionSectionData
{
Title = "Components",
IsExpanded = false,
LayoutType = UTKAccordionLayoutType.Horizontal,
HorizontalItems = new List<UTKAccordionHorizontalItemData>
{
new UTKAccordionHorizontalItemData
{
Content = UTKAccordionContentSpec.FromText("Transform", "open_transform"),
},
new UTKAccordionHorizontalItemData
{
Content = UTKAccordionContentSpec.FromText("Rigidbody", "open_rigidbody"),
},
new UTKAccordionHorizontalItemData
{
Content = UTKAccordionContentSpec.FromText("Collider", "open_collider"),
}
}
};
data.Sections.Add(componentsSection);
// ========================================
// 그리드 레이아웃 섹션 1: Vehicles
// ========================================
var vehiclesSection = new UTKAccordionSectionData
{
Title = "Vehicles",
IsExpanded = true,
LayoutType = UTKAccordionLayoutType.Grid,
GridItems = new List<UTKAccordionGridItemData>
{
new UTKAccordionGridItemData
{
Caption = "Forklift",
ImagePath = "Simulator/Images/lib_forklift_400x300",
PrefabPath = "Simulator/FreeForkLift/Prefabs/Forklift",
Tag = "vehicle"
},
new UTKAccordionGridItemData
{
Caption = "Truck",
ImagePath = "Simulator/Images/lib_forklift_400x300",
PrefabPath = "Simulator/FreeForkLift/Prefabs/Forklift",
Tag = "vehicle"
}
}
};
data.Sections.Add(vehiclesSection);
// ========================================
// 그리드 레이아웃 섹션 2: Objects
// ========================================
var objectsSection = new UTKAccordionSectionData
{
Title = "Objects",
IsExpanded = true,
LayoutType = UTKAccordionLayoutType.Grid,
GridItems = new List<UTKAccordionGridItemData>
{
new UTKAccordionGridItemData
{
Caption = "Pallet",
ImagePath = "Simulator/Images/lib_pallet_400x300",
PrefabPath = "Simulator/FreeForkLift/Prefabs/PalletEmpty",
Tag = "object"
},
new UTKAccordionGridItemData
{
Caption = "Pallet (Full)",
ImagePath = "Simulator/Images/lib_pallet_400x300",
PrefabPath = "Simulator/FreeForkLift/Prefabs/PalletEmpty",
Tag = "object"
},
new UTKAccordionGridItemData
{
Caption = "Box",
ImagePath = "Simulator/Images/lib_pallet_400x300",
PrefabPath = "Simulator/FreeForkLift/Prefabs/PalletEmpty",
Tag = "object"
}
}
};
data.Sections.Add(objectsSection);
// ========================================
// 그리드 레이아웃 섹션 3: Characters
// ========================================
var charactersSection = new UTKAccordionSectionData
{
Title = "Characters",
IsExpanded = true,
LayoutType = UTKAccordionLayoutType.Grid,
GridItems = new List<UTKAccordionGridItemData>
{
new UTKAccordionGridItemData
{
Caption = "Worker",
ImagePath = "Simulator/Images/lib_worker_400x300",
PrefabPath = "Simulator/CharCrafter Free Preset Characters Pack (Vol. 1)/Prefabs/Male Young Guy",
Tag = "character"
},
new UTKAccordionGridItemData
{
Caption = "Manager",
ImagePath = "Simulator/Images/lib_worker_400x300",
PrefabPath = "Simulator/CharCrafter Free Preset Characters Pack (Vol. 1)/Prefabs/Male Young Guy",
Tag = "character"
}
}
};
data.Sections.Add(charactersSection);
accordionWindow.SetData(data);
accordionWindow.Show();
container.Add(accordionWindow);
}
private void InitializeComponentListWindowSample(VisualElement root)
{
var container = root.Q<VisualElement>("component-list-window-container");
if (container == null) return;
var componentWindow = new UTKComponentListWindow();
componentWindow.Title = "모델 리스트";
componentWindow.ShowCloseButton = true;
// 테마 변경 이벤트 구독
UTKThemeManager.Instance.OnThemeChanged += _ => UTKThemeManager.Instance.ApplyThemeToElement(componentWindow);
// 샘플 데이터
var category1 = new UTKComponentListCategoryData { name = "캐릭터", isExpanded = true };
category1.Add(new UTKComponentListItemData { name = "플레이어", ExternalKey = "player_001", IsVisible = true });
category1.Add(new UTKComponentListItemData { name = "NPC_01", ExternalKey = "npc_001", IsVisible = true });
category1.Add(new UTKComponentListItemData { name = "NPC_02", ExternalKey = "npc_002", IsVisible = false });
var category2 = new UTKComponentListCategoryData { name = "환경", isExpanded = false };
category2.Add(new UTKComponentListItemData { name = "나무", ExternalKey = "tree_001", IsVisible = true });
category2.Add(new UTKComponentListItemData { name = "바위", ExternalKey = "rock_001", IsVisible = true });
var data = new List<UTKComponentListItemDataBase> { category1, category2 };
componentWindow.SetData(data);
componentWindow.Show();
container.Add(componentWindow);
}
private void InitializeComponentTabListWindowSample(VisualElement root)
{
var container = root.Q<VisualElement>("component-tab-list-window-container");
if (container == null) return;
var tabWindow = new UTKComponentTabListWindow();
tabWindow.Title = "모델 라이브러리";
tabWindow.ShowCloseButton = true;
// 테마 변경 이벤트 구독
UTKThemeManager.Instance.OnThemeChanged += _ => UTKThemeManager.Instance.ApplyThemeToElement(tabWindow);
// 샘플 데이터 (카테고리가 탭으로 자동 생성됨)
var category1 = new UTKComponentListCategoryData { name = "캐릭터", isExpanded = true };
category1.Add(new UTKComponentListItemData { name = "플레이어", IsVisible = true });
category1.Add(new UTKComponentListItemData { name = "몬스터", IsVisible = true });
var category2 = new UTKComponentListCategoryData { name = "환경", isExpanded = true };
category2.Add(new UTKComponentListItemData { name = "나무", IsVisible = true });
category2.Add(new UTKComponentListItemData { name = "풀", IsVisible = true });
var category3 = new UTKComponentListCategoryData { name = "아이템", isExpanded = true };
category3.Add(new UTKComponentListItemData { name = "검", IsVisible = true });
category3.Add(new UTKComponentListItemData { name = "방패", IsVisible = true });
var data = new List<UTKComponentListItemDataBase> { category1, category2, category3 };
tabWindow.SetData(data);
tabWindow.Show();
container.Add(tabWindow);
}
private void InitializeImageListWindowSample(VisualElement root)
{
var container = root.Q<VisualElement>("image-list-window-container");
if (container == null) return;
var imageWindow = new UTKImageListWindow();
imageWindow.Title = "텍스처 라이브러리";
imageWindow.ShowCloseButton = true;
// 테마 변경 이벤트 구독
UTKThemeManager.Instance.OnThemeChanged += _ => UTKThemeManager.Instance.ApplyThemeToElement(imageWindow);
// 샘플 데이터
var data = new List<UTKImageListItemData>
{
new UTKImageListItemData { itemName = "Texture_01", externalId = "tex_001" },
new UTKImageListItemData { itemName = "Texture_02", externalId = "tex_002" },
new UTKImageListItemData { itemName = "Texture_03", externalId = "tex_003" },
new UTKImageListItemData { itemName = "Texture_04", externalId = "tex_004" }
};
imageWindow.SetData(data);
imageWindow.Show();
container.Add(imageWindow);
}
private void InitializeTreeListWindowSample(VisualElement root)
{
var container = root.Q<VisualElement>("tree-list-window-container");
if (container == null) return;
var treeWindow = new UTKTreeListWindow();
treeWindow.Title = "씬 계층 구조";
treeWindow.ShowCloseButton = true;
// 테마 변경 이벤트 구독
UTKThemeManager.Instance.OnThemeChanged += _ => UTKThemeManager.Instance.ApplyThemeToElement(treeWindow);
// 샘플 데이터
var environment = new UTKTreeListItemData { name = "Environment", isExpanded = true };
environment.Add(new UTKTreeListItemData { name = "Terrain", ExternalKey = "terrain_001", IsVisible = true });
environment.Add(new UTKTreeListItemData { name = "Trees", ExternalKey = "trees_001", IsVisible = true });
var characters = new UTKTreeListItemData { name = "Characters", isExpanded = false };
characters.Add(new UTKTreeListItemData { name = "Player", ExternalKey = "player_001", IsVisible = true });
characters.Add(new UTKTreeListItemData { name = "Enemies", ExternalKey = "enemies_001", IsVisible = true });
var rootNode = new UTKTreeListItemData { name = "Root", isExpanded = true };
rootNode.Add(environment);
rootNode.Add(characters);
var data = new List<UTKTreeListItemData> { rootNode };
treeWindow.SetData(data);
treeWindow.Show();
container.Add(treeWindow);
}
#endregion
} }

View File

@@ -306,9 +306,10 @@ namespace UVC.UIToolkit
if (!string.IsNullOrEmpty(icon)) if (!string.IsNullOrEmpty(icon))
{ {
// 1순위: UTKMaterialIcons에 해당하는지 확인 // 1순위: UTKMaterialIcons에 해당하는지 확인
if (UTKMaterialIcons.GetIcon(icon) != null) string iconChar = UTKMaterialIcons.GetIcon(icon);
if (iconChar != string.Empty)
{ {
SetMaterialIcon(icon, iconSize); SetMaterialIcon(iconChar, iconSize);
} }
// 2순위: UTKImageIcons에 해당하는지 확인 // 2순위: UTKImageIcons에 해당하는지 확인
else if (!string.IsNullOrEmpty(UTKImageIcons.GetPath(icon))) else if (!string.IsNullOrEmpty(UTKImageIcons.GetPath(icon)))
@@ -536,8 +537,8 @@ namespace UVC.UIToolkit
/// <param name="fontSize">아이콘 폰트 크기 (null이면 버튼 크기에 맞춤)</param> /// <param name="fontSize">아이콘 폰트 크기 (null이면 버튼 크기에 맞춤)</param>
public void SetMaterialIconByName(string iconName, int? fontSize = null) public void SetMaterialIconByName(string iconName, int? fontSize = null)
{ {
var iconChar = UTKMaterialIcons.GetIcon(iconName); string iconChar = UTKMaterialIcons.GetIcon(iconName);
if (!string.IsNullOrEmpty(iconChar)) if (iconChar != string.Empty)
{ {
SetMaterialIcon(iconChar, fontSize); SetMaterialIcon(iconChar, fontSize);
} }

View File

@@ -6,6 +6,7 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
@@ -41,6 +42,9 @@ namespace UVC.UIToolkit
/// // 아이콘 존재 여부 확인 /// // 아이콘 존재 여부 확인
/// if (UTKMaterialIcons.HasIcon("search")) { } /// if (UTKMaterialIcons.HasIcon("search")) { }
/// ///
/// // 존재하는 유니코드 문자인지 확인
/// if (UTKMaterialIcons.IsIconChar("□")) { }
///
/// // 전체 아이콘 이름 순회 /// // 전체 아이콘 이름 순회
/// foreach (var name in UTKMaterialIcons.GetAllIconNames()) { } /// foreach (var name in UTKMaterialIcons.GetAllIconNames()) { }
/// ///
@@ -152,10 +156,10 @@ namespace UVC.UIToolkit
/// </summary> /// </summary>
/// <param name="element">스타일을 적용할 요소</param> /// <param name="element">스타일을 적용할 요소</param>
/// <param name="fontSize">폰트 크기 (기본값: 24)</param> /// <param name="fontSize">폰트 크기 (기본값: 24)</param>
public static void ApplyIconStyle(VisualElement element, int fontSize = 24) public static void ApplyIconStyle(VisualElement element, int? fontSize = 24)
{ {
element.style.unityFontDefinition = GetFontDefinition(); element.style.unityFontDefinition = GetFontDefinition();
element.style.fontSize = fontSize; if(fontSize != null) element.style.fontSize = fontSize.Value;
} }
/// <summary> /// <summary>
@@ -12501,6 +12505,7 @@ namespace UVC.UIToolkit
/// <returns>아이콘 문자, 없으면 빈 문자열</returns> /// <returns>아이콘 문자, 없으면 빈 문자열</returns>
public static string GetIcon(string iconName) public static string GetIcon(string iconName)
{ {
if(IsIconChar(iconName)) return iconName;
return _iconsByName.TryGetValue(iconName, out var icon) ? icon : string.Empty; return _iconsByName.TryGetValue(iconName, out var icon) ? icon : string.Empty;
} }
@@ -12509,6 +12514,11 @@ namespace UVC.UIToolkit
/// </summary> /// </summary>
public static bool HasIcon(string iconName) => _iconsByName.ContainsKey(iconName); public static bool HasIcon(string iconName) => _iconsByName.ContainsKey(iconName);
/// <summary>
/// 유니코드 문자로 아이콘이 존재하는지 확인합니다.
/// </summary>
public static bool IsIconChar(string iconChar) => _iconsByName.Values.Contains(iconChar);
/// <summary> /// <summary>
/// 모든 아이콘 이름 목록을 반환합니다. /// 모든 아이콘 이름 목록을 반환합니다.
/// </summary> /// </summary>

View File

@@ -694,8 +694,8 @@ namespace UVC.UIToolkit
/// <param name="fontSize">아이콘 폰트 크기 (null이면 텍스트 크기에 맞춤)</param> /// <param name="fontSize">아이콘 폰트 크기 (null이면 텍스트 크기에 맞춤)</param>
public void SetMaterialIconByName(string iconName, int? fontSize = null) public void SetMaterialIconByName(string iconName, int? fontSize = null)
{ {
var iconChar = UTKMaterialIcons.GetIcon(iconName); string iconChar = UTKMaterialIcons.GetIcon(iconName);
if (!string.IsNullOrEmpty(iconChar)) if (iconChar != string.Empty)
{ {
SetMaterialIcon(iconChar, fontSize); SetMaterialIcon(iconChar, fontSize);
} }

View File

@@ -32,6 +32,88 @@ namespace UVC.UIToolkit
/// <item>비동기 이미지 로딩 및 캐싱</item> /// <item>비동기 이미지 로딩 및 캐싱</item>
/// </list> /// </list>
/// ///
/// <para><b>UXML 사용 예시:</b></para>
/// <code><![CDATA[
/// <!-- UXML 파일에서 UTKAccordionList 사용 -->
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKAccordionList name="accordion-list" />
/// </ui:UXML>
/// ]]></code>
///
/// <para><b>C# 사용 예시:</b></para>
/// <code><![CDATA[
/// // 1. 아코디언 리스트 참조 획득
/// var accordionList = root.Q<UTKAccordionList>("accordion-list");
///
/// // 2. 데이터 구성 - 섹션(카테고리)과 그리드 아이템
/// var data = new List<UTKAccordionItemData>
/// {
/// // 섹션 생성 (카테고리)
/// new UTKAccordionItemData
/// {
/// nodeType = UTKAccordionNodeType.Section,
/// name = "가구",
/// isExpanded = true,
/// children = new List<UTKAccordionItemData>
/// {
/// // 그리드 아이템 (이미지 + 캡션)
/// new UTKAccordionItemData
/// {
/// nodeType = UTKAccordionNodeType.GridItem,
/// name = "의자",
/// caption = "사무용 의자",
/// imagePath = "Prefabs/Thumbnails/chair",
/// prefabPath = "Prefabs/Furniture/Chair"
/// },
/// new UTKAccordionItemData
/// {
/// nodeType = UTKAccordionNodeType.GridItem,
/// name = "책상",
/// caption = "사무용 책상",
/// imagePath = "Prefabs/Thumbnails/desk",
/// prefabPath = "Prefabs/Furniture/Desk"
/// }
/// }
/// }
/// };
///
/// // 3. 데이터 설정
/// accordionList.SetData(data);
///
/// // 4. 이벤트 구독 - 아이템 클릭
/// accordionList.OnItemClick += (item) =>
/// {
/// Debug.Log($"클릭된 아이템: {item.name}");
/// };
///
/// // 5. 드래그 앤 드롭 이벤트
/// accordionList.OnItemDrop += (item) =>
/// {
/// // 드롭된 위치에 프리팹 인스턴스화
/// if (!string.IsNullOrEmpty(item.prefabPath))
/// {
/// var prefab = Resources.Load<GameObject>(item.prefabPath);
/// Instantiate(prefab, dropPosition, Quaternion.identity);
/// }
/// };
///
/// // 6. 리스트 영역 밖으로 드래그 시 3D 미리보기 표시
/// accordionList.OnDragExitList += (item, screenPos) =>
/// {
/// Show3DPreview(item.prefabPath, screenPos);
/// };
///
/// // 7. 검색 실행
/// accordionList.Search("의자");
///
/// // 8. 모든 섹션 펼치기/접기
/// accordionList.ExpandAll();
/// accordionList.CollapseAll();
///
/// // 9. 리소스 해제 (OnDestroy에서 호출)
/// accordionList.Dispose();
/// ]]></code>
///
/// <para><b>관련 리소스:</b></para> /// <para><b>관련 리소스:</b></para>
/// <list type="bullet"> /// <list type="bullet">
/// <item>Resources/UIToolkit/List/UTKAccordionList.uxml - 메인 레이아웃</item> /// <item>Resources/UIToolkit/List/UTKAccordionList.uxml - 메인 레이아웃</item>
@@ -47,6 +129,7 @@ namespace UVC.UIToolkit
#region (Constants) #region (Constants)
private const string UXML_PATH = "UIToolkit/List/UTKAccordionList"; private const string UXML_PATH = "UIToolkit/List/UTKAccordionList";
private const string USS_PATH = "UIToolkit/List/UTKAccordionListUss";
private const string SECTION_UXML_PATH = "UIToolkit/List/UTKAccordionSection"; private const string SECTION_UXML_PATH = "UIToolkit/List/UTKAccordionSection";
private const string HORIZONTAL_ITEM_UXML_PATH = "UIToolkit/List/UTKAccordionHorizontalItem"; private const string HORIZONTAL_ITEM_UXML_PATH = "UIToolkit/List/UTKAccordionHorizontalItem";
private const string GRID_ITEM_UXML_PATH = "UIToolkit/List/UTKAccordionGridItem"; private const string GRID_ITEM_UXML_PATH = "UIToolkit/List/UTKAccordionGridItem";
@@ -67,7 +150,7 @@ namespace UVC.UIToolkit
#region UI (UI Component References) #region UI (UI Component References)
private TextField? _searchField; private TextField? _searchField;
private Button? _clearButton; private UTKButton? _clearButton;
private Label? _searchResultLabel; private Label? _searchResultLabel;
private TreeView? _treeView; private TreeView? _treeView;
@@ -179,9 +262,6 @@ namespace UVC.UIToolkit
public UTKAccordionList() public UTKAccordionList()
{ {
// 테마 적용
UTKThemeManager.Instance.ApplyThemeToElement(this);
// 메인 UXML 로드 // 메인 UXML 로드
var visualTree = Resources.Load<VisualTreeAsset>(UXML_PATH); var visualTree = Resources.Load<VisualTreeAsset>(UXML_PATH);
if (visualTree == null) if (visualTree == null)
@@ -194,10 +274,27 @@ namespace UVC.UIToolkit
// 템플릿 로드 // 템플릿 로드
LoadTemplates(); LoadTemplates();
// 테마 적용 및 변경 구독
UTKThemeManager.Instance.ApplyThemeToElement(this);
SubscribeToThemeChanges();
// USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// UI 요소 참조 획득 // UI 요소 참조 획득
_searchField = this.Q<TextField>("search-field"); _searchField = this.Q<TextField>("search-field");
_clearButton = this.Q<Button>("clear-btn"); _clearButton = this.Q<UTKButton>("clear-btn");
_searchResultLabel = this.Q<Label>("search-result-label"); _searchResultLabel = this.Q<Label>("search-result-label");
// Clear 버튼 아이콘 설정
if (_clearButton != null)
{
_clearButton.SetMaterialIcon(UTKMaterialIcons.Close, 12);
}
_treeView = this.Q<TreeView>("accordion-tree-view"); _treeView = this.Q<TreeView>("accordion-tree-view");
// 초기화 // 초기화
@@ -234,7 +331,7 @@ namespace UVC.UIToolkit
// 검색어 지우기 버튼 // 검색어 지우기 버튼
if (_clearButton != null) if (_clearButton != null)
{ {
_clearButton.clicked += OnClearButtonClicked; _clearButton.OnClicked += OnClearButtonClicked;
// 초기에는 숨김 // 초기에는 숨김
_clearButton.style.display = DisplayStyle.None; _clearButton.style.display = DisplayStyle.None;
} }
@@ -734,7 +831,7 @@ namespace UVC.UIToolkit
var tailBtn = CreateTailButton(tailSpec); var tailBtn = CreateTailButton(tailSpec);
if (tailBtn != null) if (tailBtn != null)
{ {
tailBtn.clicked += () => tailBtn.OnClicked += () =>
{ {
// 레거시 이벤트 // 레거시 이벤트
var legacyItem = ConvertToLegacyHorizontalItem(item); var legacyItem = ConvertToLegacyHorizontalItem(item);
@@ -897,21 +994,11 @@ namespace UVC.UIToolkit
}, TrickleDown.TrickleDown); }, TrickleDown.TrickleDown);
} }
private Button CreateTailButton(UTKAccordionContentSpec spec) private UTKButton CreateTailButton(UTKAccordionContentSpec spec)
{ {
var btn = new Button(); var btn = new UTKButton("", spec.IconName ?? "", UTKButton.ButtonVariant.Text, spec.IconSize ?? 12) { IconOnly = true };
btn.AddToClassList("accordion-tail-button"); btn.AddToClassList("accordion-tail-button");
var icon = new VisualElement();
icon.AddToClassList("accordion-tail-icon");
if (!string.IsNullOrEmpty(spec.IconName))
{
LoadIconAsync(icon, spec.IconName).Forget();
}
btn.Add(icon);
if (!string.IsNullOrEmpty(spec.Tooltip)) if (!string.IsNullOrEmpty(spec.Tooltip))
{ {
btn.tooltip = spec.Tooltip; btn.tooltip = spec.Tooltip;
@@ -924,8 +1011,24 @@ namespace UVC.UIToolkit
{ {
if (spec.Kind == UTKAccordionContentKind.Image && !string.IsNullOrEmpty(spec.ImagePath)) if (spec.Kind == UTKAccordionContentKind.Image && !string.IsNullOrEmpty(spec.ImagePath))
{ {
string iconChar = UTKMaterialIcons.GetIcon(spec.ImagePath);
Debug.Log($"[UTKAccordionList] Content image path: '{spec.ImagePath} ' → icon char: '{iconChar}'");
if (iconChar != string.Empty)
{
Debug.Log($"[UTKAccordionList] Using material icon '{spec.ImagePath}' for content image.");
// 머티리얼 아이콘 사용
Label iconLabel = new Label(iconChar);
UTKMaterialIcons.ApplyIconStyle(iconLabel, null); //uss에서 설정하기에 null 전달
imageElement.Add(iconLabel);
if(spec.ImageColor != null) iconLabel.style.color = spec.ImageColor.Value;
}
else
{
// 일반 이미지 로드
BindImageAsync(imageElement, spec.ImagePath).Forget(); BindImageAsync(imageElement, spec.ImagePath).Forget();
imageElement.style.unityBackgroundImageTintColor = spec.ImageColor; if(spec.ImageColor != null) imageElement.style.unityBackgroundImageTintColor = spec.ImageColor.Value;
}
} }
} }
@@ -1586,6 +1689,24 @@ namespace UVC.UIToolkit
#endregion #endregion
#region (Theme)
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region IDisposable #region IDisposable
public void Dispose() public void Dispose()
@@ -1593,6 +1714,9 @@ namespace UVC.UIToolkit
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
// 테마 변경 이벤트 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
// 이미지 로딩 취소 // 이미지 로딩 취소
CancelImageLoading(); CancelImageLoading();
@@ -1614,7 +1738,8 @@ namespace UVC.UIToolkit
if (_clearButton != null) if (_clearButton != null)
{ {
_clearButton.clicked -= OnClearButtonClicked; _clearButton.OnClicked -= OnClearButtonClicked;
_clearButton.Dispose();
} }
// TreeView 이벤트 해제 // TreeView 이벤트 해제

View File

@@ -47,11 +47,14 @@ namespace UVC.UIToolkit
public string? ImagePath { get; set; } public string? ImagePath { get; set; }
/// <summary>이미지 색상</summary> /// <summary>이미지 색상</summary>
public Color ImageColor { get; set; } = Color.white; public Color? ImageColor { get; set; }
/// <summary>아이콘 이름 또는 경로</summary> /// <summary>아이콘 이름 또는 경로</summary>
public string? IconName { get; set; } public string? IconName { get; set; }
/// <summary>아이콘 크기 (정사각형 기준, 픽셀 단위)</summary>
public int? IconSize { get; set; }
/// <summary>툴팁 텍스트</summary> /// <summary>툴팁 텍스트</summary>
public string? Tooltip { get; set; } public string? Tooltip { get; set; }
@@ -77,16 +80,17 @@ namespace UVC.UIToolkit
{ {
Kind = UTKAccordionContentKind.Image, Kind = UTKAccordionContentKind.Image,
ImagePath = path, ImagePath = path,
ImageColor = color ?? Color.white, ImageColor = color,
Tooltip = tooltip Tooltip = tooltip
}; };
/// <summary>아이콘 버튼 스펙을 생성합니다.</summary> /// <summary>아이콘 버튼 스펙을 생성합니다.</summary>
public static UTKAccordionContentSpec FromIconButton(string iconName, string? actionId = null, string? tooltip = null, object? userData = null) public static UTKAccordionContentSpec FromIconButton(string iconName, int iconSize, string? actionId = null, string? tooltip = null, object? userData = null)
=> new UTKAccordionContentSpec => new UTKAccordionContentSpec
{ {
Kind = UTKAccordionContentKind.IconButton, Kind = UTKAccordionContentKind.IconButton,
IconName = iconName, IconName = iconName,
IconSize = iconSize,
ActionId = actionId, ActionId = actionId,
Tooltip = tooltip, Tooltip = tooltip,
UserData = userData UserData = userData

View File

@@ -27,22 +27,123 @@ namespace UVC.UIToolkit
/// <item>선택 이벤트 처리</item> /// <item>선택 이벤트 처리</item>
/// </list> /// </list>
/// ///
/// <para><b>UXML에서 사용:</b></para> /// <para><b>UXML 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// <UVC.UIToolkit.Window.UTKComponentList name="tree-list" /> /// <!-- UXML 파일에서 UTKComponentList 사용 -->
/// </code> /// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKComponentList name="component-list" />
/// </ui:UXML>
/// ]]></code>
/// ///
/// <para><b>코드에서 사용:</b></para> /// <para><b>C# 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// var list = root.Q<UTKComponentList>(); /// // 1. 컴포넌트 리스트 참조 획득
/// list.OnSelectionChanged += (item) => Debug.Log($"선택: {item.name}"); /// var componentList = root.Q<UTKComponentList>("component-list");
/// list.OnVisibilityChanged += (item) => model.SetActive(item.id, item.IsVisible); ///
/// list.SetData(treeItems); /// // 2. 데이터 구성 - 카테고리(그룹)와 일반 아이템
/// </code> /// var data = new List<UTKComponentListItemDataBase>
/// {
/// // 카테고리(그룹) 생성
/// new UTKComponentListCategoryData
/// {
/// name = "모델 그룹 A",
/// isExpanded = true,
/// children = new List<UTKComponentListItemDataBase>
/// {
/// // 일반 아이템
/// new UTKComponentListItemData
/// {
/// name = "의자 모델",
/// ExternalKey = "chair_001",
/// IsVisible = true
/// },
/// new UTKComponentListItemData
/// {
/// name = "책상 모델",
/// ExternalKey = "desk_001",
/// IsVisible = true
/// }
/// }
/// }
/// };
///
/// // 3. 데이터 설정
/// componentList.SetData(data);
///
/// // 4. 선택 이벤트 구독 - 아이템 선택 시 호출
/// componentList.OnItemSelected += (selectedItems) =>
/// {
/// foreach (var item in selectedItems)
/// {
/// Debug.Log($"선택됨: {item.name}");
/// }
/// };
///
/// // 5. 선택 해제 이벤트 구독
/// componentList.OnItemDeselected += (deselectedItems) =>
/// {
/// foreach (var item in deselectedItems)
/// {
/// Debug.Log($"선택 해제: {item.name}");
/// }
/// };
///
/// // 6. 가시성 변경 이벤트 구독 - 눈 아이콘 클릭 시 호출
/// componentList.OnItemVisibilityChanged += (item, isVisible) =>
/// {
/// // 3D 모델의 GameObject 활성화/비활성화
/// var gameObject = FindGameObjectByKey(item.ExternalKey);
/// if (gameObject != null)
/// {
/// gameObject.SetActive(isVisible);
/// }
/// };
///
/// // 7. 삭제 이벤트 구독 (Delete/Backspace 키)
/// componentList.EnabledDeleteItem = true; // 삭제 기능 활성화 필수
/// componentList.OnItemDeleted += (item) =>
/// {
/// Debug.Log($"삭제 요청: {item.name}");
/// componentList.DeleteItem(item); // 리스트에서 제거
/// };
///
/// // 8. 더블클릭 이벤트 구독
/// componentList.OnItemDoubleClicked += (item) =>
/// {
/// Debug.Log($"더블클릭: {item.name}");
/// // 카메라 포커스 등의 동작 수행
/// };
///
/// // 9. 아이콘 클릭 이벤트 (setting-btn, search-btn)
/// componentList.OnItemIconClicked += (iconName, item) =>
/// {
/// if (iconName == "setting-btn")
/// {
/// ShowCategorySettings(item);
/// }
/// };
///
/// // 10. 검색 실행
/// componentList.ApplySearch("의자");
///
/// // 11. 프로그래밍 방식 선택
/// componentList.SelectItem("의자 모델", notify: true);
/// componentList.DeselectItem("의자 모델", notify: false);
/// componentList.ClearSelection();
///
/// // 12. 아이템 추가/삭제
/// var newItem = new UTKComponentListItemData { name = "새 아이템" };
/// componentList.AddItem(newItem); // 루트에 추가
/// componentList.AddItem(categoryData, newItem); // 특정 카테고리에 추가
/// componentList.DeleteItem(newItem); // 삭제
///
/// // 13. 리소스 해제 (OnDestroy에서 호출)
/// componentList.Dispose();
/// ]]></code>
/// ///
/// <para><b>관련 리소스:</b></para> /// <para><b>관련 리소스:</b></para>
/// <list type="bullet"> /// <list type="bullet">
/// <item>Resources/UIToolkit/Window/UTKComponentList.uxml - 메인 레이아웃</item> /// <item>Resources/UIToolkit/List/UTKComponentList.uxml - 메인 레이아웃</item>
/// <item>Resources/UIToolkit/List/UTKComponentListItem.uxml - 개별 항목 템플릿</item> /// <item>Resources/UIToolkit/List/UTKComponentListItem.uxml - 개별 항목 템플릿</item>
/// <item>Resources/UIToolkit/List/UTKComponentListGroupItem.uxml - 그룹 항목 템플릿</item> /// <item>Resources/UIToolkit/List/UTKComponentListGroupItem.uxml - 그룹 항목 템플릿</item>
/// </list> /// </list>
@@ -63,6 +164,8 @@ namespace UVC.UIToolkit
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary> /// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
private const string UXML_PATH = "UIToolkit/List/UTKComponentList"; private const string UXML_PATH = "UIToolkit/List/UTKComponentList";
private const string USS_PATH = "UIToolkit/List/UTKComponentListUss";
/// <summary>일반 항목 UXML 파일 경로 (Resources 폴더 기준)</summary> /// <summary>일반 항목 UXML 파일 경로 (Resources 폴더 기준)</summary>
private const string ITEM_UXML_PATH = "UIToolkit/List/UTKComponentListItem"; private const string ITEM_UXML_PATH = "UIToolkit/List/UTKComponentListItem";
@@ -85,8 +188,8 @@ namespace UVC.UIToolkit
/// <summary>Unity UI Toolkit의 TreeView 컴포넌트</summary> /// <summary>Unity UI Toolkit의 TreeView 컴포넌트</summary>
private TreeView? _treeView; private TreeView? _treeView;
/// <summary>검색어 지우기 버튼</summary> /// <summary>검색어 지우기 버튼 (UTKButton)</summary>
private Button? _clearButton; private UTKButton? _clearButton;
#endregion #endregion
#region (Internal Data) #region (Internal Data)
@@ -199,6 +302,8 @@ namespace UVC.UIToolkit
} }
visualTree!.CloneTree(this); visualTree!.CloneTree(this);
// 2. 항목 템플릿 로드 // 2. 항목 템플릿 로드
_itemTemplate = Resources.Load<VisualTreeAsset>(ITEM_UXML_PATH); _itemTemplate = Resources.Load<VisualTreeAsset>(ITEM_UXML_PATH);
_groupItemTemplate = Resources.Load<VisualTreeAsset>(GROUP_ITEM_UXML_PATH); _groupItemTemplate = Resources.Load<VisualTreeAsset>(GROUP_ITEM_UXML_PATH);
@@ -207,13 +312,29 @@ namespace UVC.UIToolkit
if (_groupItemTemplate == null) if (_groupItemTemplate == null)
Debug.LogError($"[UTKComponentList] Group Item UXML not found at: {GROUP_ITEM_UXML_PATH}"); Debug.LogError($"[UTKComponentList] Group Item UXML not found at: {GROUP_ITEM_UXML_PATH}");
// 테마 적용 및 변경 구독
UTKThemeManager.Instance.ApplyThemeToElement(this);
SubscribeToThemeChanges();
// 2. 자식 요소 참조 획득 (UXML의 name 속성으로 찾음) // USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 3. 자식 요소 참조 획득 (UXML의 name 속성으로 찾음)
_searchField = this.Q<TextField>("search-field"); _searchField = this.Q<TextField>("search-field");
_treeView = this.Q<TreeView>("main-tree-view"); _treeView = this.Q<TreeView>("main-tree-view");
_clearButton = this.Q<Button>("clear-btn"); _clearButton = this.Q<UTKButton>("clear-btn");
// 3. 이벤트 연결 및 로직 초기화 // 4. Clear 버튼 아이콘 설정
if (_clearButton != null)
{
_clearButton.SetMaterialIcon(UTKMaterialIcons.Close, 12);
}
// 5. 이벤트 연결 및 로직 초기화
InitializeLogic(); InitializeLogic();
} }
#endregion #endregion
@@ -251,15 +372,7 @@ namespace UVC.UIToolkit
if(_clearButton != null) if(_clearButton != null)
{ {
_clearButton.style.display = DisplayStyle.None; // 초기에는 숨김 _clearButton.style.display = DisplayStyle.None; // 초기에는 숨김
_clearButton.clicked += () => _clearButton.OnClicked += OnClearButtonClicked;
{
if (_searchField.value.Length > 0)
{
_searchField.value = string.Empty;
OnSearch(string.Empty);
}
_clearButton.style.display = DisplayStyle.None; // 클리어 후 숨김
};
} }
// 스크롤바 hover/active 색상 설정 // 스크롤바 hover/active 색상 설정
@@ -1372,6 +1485,22 @@ namespace UVC.UIToolkit
OnSearch(_searchField?.value ?? string.Empty); OnSearch(_searchField?.value ?? string.Empty);
} }
/// <summary>
/// Clear 버튼 클릭 이벤트를 처리합니다.
/// </summary>
private void OnClearButtonClicked()
{
if (_searchField != null && _searchField.value.Length > 0)
{
_searchField.value = string.Empty;
OnSearch(string.Empty);
}
if (_clearButton != null)
{
_clearButton.style.display = DisplayStyle.None;
}
}
/// <summary> /// <summary>
/// 검색어에 따라 트리를 필터링합니다. /// 검색어에 따라 트리를 필터링합니다.
/// 검색어가 비어있으면 원본 데이터로 복원됩니다. /// 검색어가 비어있으면 원본 데이터로 복원됩니다.
@@ -1562,6 +1691,24 @@ namespace UVC.UIToolkit
} }
#endregion #endregion
#region (Theme)
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region IDisposable #region IDisposable
/// <summary> /// <summary>
/// 리소스를 해제하고 이벤트 핸들러를 정리합니다. /// 리소스를 해제하고 이벤트 핸들러를 정리합니다.
@@ -1571,6 +1718,9 @@ namespace UVC.UIToolkit
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
// 테마 변경 이벤트 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
// 검색 필드 이벤트 해제 // 검색 필드 이벤트 해제
if (_searchField != null) if (_searchField != null)
{ {
@@ -1589,6 +1739,13 @@ namespace UVC.UIToolkit
_treeView.makeItem = null; _treeView.makeItem = null;
} }
// Clear 버튼 이벤트 해제 및 정리
if (_clearButton != null)
{
_clearButton.OnClicked -= OnClearButtonClicked;
_clearButton.Dispose();
}
// 외부 이벤트 구독자 정리 // 외부 이벤트 구독자 정리
OnItemVisibilityChanged = null; OnItemVisibilityChanged = null;
OnItemSelected = null; OnItemSelected = null;

View File

@@ -23,22 +23,125 @@ namespace UVC.UIToolkit
/// <para><b>주요 기능:</b></para> /// <para><b>주요 기능:</b></para>
/// <list type="bullet"> /// <list type="bullet">
/// <item>이미지+텍스트 형태의 아이템을 2열 그리드로 표시</item> /// <item>이미지+텍스트 형태의 아이템을 2열 그리드로 표시</item>
/// <item>실시간 검색 필터링 (3글자 이상)</item> /// <item>실시간 검색 필터링 (2글자 이상)</item>
/// <item>드래그 앤 드롭 지원</item> /// <item>드래그 앤 드롭 지원</item>
/// <item>가상화를 통한 대량 데이터 성능 최적화</item> /// <item>가상화를 통한 대량 데이터 성능 최적화</item>
/// </list> /// </list>
/// ///
/// <para><b>UXML에서 사용:</b></para> /// <para><b>UXML 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// <uvc:UTKImageList name="image-list" /> /// <!-- UXML 파일에서 UTKImageList 사용 -->
/// </code> /// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKImageList name="image-list" />
/// </ui:UXML>
/// ]]></code>
/// ///
/// <para><b>코드에서 사용:</b></para> /// <para><b>C# 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// var list = root.Q<UTKImageList>(); /// // 1. 이미지 리스트 참조 획득
/// list.OnItemClick += (item) => Debug.Log($"클릭: {item.itemName}"); /// var imageList = root.Q<UTKImageList>("image-list");
/// list.SetData(imageItems); ///
/// </code> /// // 2. 데이터 구성 - 이미지 경로와 이름을 가진 아이템들
/// var data = new List<UTKImageListItemData>
/// {
/// new UTKImageListItemData
/// {
/// itemName = "의자",
/// imagePath = "Prefabs/Thumbnails/chair",
/// prefabPath = "Prefabs/Furniture/Chair"
/// },
/// new UTKImageListItemData
/// {
/// itemName = "책상",
/// imagePath = "Prefabs/Thumbnails/desk",
/// prefabPath = "Prefabs/Furniture/Desk"
/// },
/// new UTKImageListItemData
/// {
/// itemName = "소파",
/// imagePath = "Prefabs/Thumbnails/sofa",
/// prefabPath = "Prefabs/Furniture/Sofa"
/// }
/// };
///
/// // 3. 데이터 설정
/// imageList.SetData(data);
///
/// // 4. 아이템 클릭 이벤트 구독
/// imageList.OnItemClick += (item) =>
/// {
/// Debug.Log($"클릭된 아이템: {item.itemName}");
/// };
///
/// // 5. 드래그 앤 드롭 이벤트 (씬에 프리팹 배치용)
/// imageList.OnItemBeginDrag += (item, screenPos) =>
/// {
/// Debug.Log($"드래그 시작: {item.itemName}");
/// };
///
/// imageList.OnItemDrag += (item, screenPos) =>
/// {
/// // 드래그 중 - 3D 미리보기 위치 업데이트 등
/// };
///
/// imageList.OnItemEndDrag += (item, screenPos) =>
/// {
/// Debug.Log($"드래그 종료: {item.itemName}");
/// };
///
/// imageList.OnItemDrop += (item) =>
/// {
/// // 드롭 완료 - 프리팹 인스턴스화
/// if (!string.IsNullOrEmpty(item.prefabPath))
/// {
/// var prefab = Resources.Load<GameObject>(item.prefabPath);
/// Instantiate(prefab, dropPosition, Quaternion.identity);
/// }
/// };
///
/// // 6. 리스트 영역 밖으로 드래그 시 3D 미리보기 표시
/// imageList.OnDragExitList += (item, screenPos) =>
/// {
/// // 리스트 밖 = 3D 씬 영역
/// Show3DPreview(item.prefabPath, screenPos);
/// };
///
/// // 7. 리스트 영역 안으로 다시 들어왔을 때 미리보기 숨김
/// imageList.OnDragEnterList += (item, screenPos) =>
/// {
/// Hide3DPreview();
/// };
///
/// // 8. 드래그 영역 설정 (부모 윈도우 영역 기준으로 체크)
/// imageList.DragBoundsElement = parentWindow;
///
/// // 9. 드래그 고스트 이미지 따라다니기 설정
/// imageList.DragImageFollowCursor = true;
///
/// // 10. 검색 실행 (2글자 이상)
/// imageList.ApplySearch("의자");
///
/// // 11. 현재 검색어 확인
/// string currentQuery = imageList.SearchQuery;
///
/// // 12. 아이템 추가/제거
/// var newItem = new UTKImageListItemData { itemName = "새 아이템" };
/// imageList.AddItem(newItem);
/// imageList.RemoveItem(newItem);
///
/// // 13. 전체 삭제
/// imageList.Clear();
///
/// // 14. 아이템 개수 확인
/// int count = imageList.ItemCount;
///
/// // 15. 리스트 표시/숨김
/// imageList.Show();
/// imageList.Hide();
///
/// // 16. 리소스 해제 (OnDestroy에서 호출)
/// imageList.Dispose();
/// ]]></code>
/// ///
/// <para><b>관련 리소스:</b></para> /// <para><b>관련 리소스:</b></para>
/// <list type="bullet"> /// <list type="bullet">
@@ -54,6 +157,7 @@ namespace UVC.UIToolkit
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary> /// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
private const string UXML_PATH = "UIToolkit/List/UTKImageList"; private const string UXML_PATH = "UIToolkit/List/UTKImageList";
private const string USS_PATH = "UIToolkit/List/UTKImageListUss";
/// <summary>아이템 UXML 파일 경로 (Resources 폴더 기준)</summary> /// <summary>아이템 UXML 파일 경로 (Resources 폴더 기준)</summary>
private const string ITEM_UXML_PATH = "UIToolkit/List/UTKImageListItem"; private const string ITEM_UXML_PATH = "UIToolkit/List/UTKImageListItem";
@@ -84,8 +188,8 @@ namespace UVC.UIToolkit
/// <summary>Unity UI Toolkit의 ListView 컴포넌트</summary> /// <summary>Unity UI Toolkit의 ListView 컴포넌트</summary>
private ListView? _listView; private ListView? _listView;
/// <summary>검색어 지우기 버튼</summary> /// <summary>검색어 지우기 버튼 (UTKButton)</summary>
private Button? _clearButton; private UTKButton? _clearButton;
/// <summary>검색 결과 건수 라벨</summary> /// <summary>검색 결과 건수 라벨</summary>
private Label? _searchResultLabel; private Label? _searchResultLabel;
@@ -248,13 +352,30 @@ namespace UVC.UIToolkit
Debug.LogError($"[UTKImageList] Item UXML not found at: {ITEM_UXML_PATH}"); Debug.LogError($"[UTKImageList] Item UXML not found at: {ITEM_UXML_PATH}");
} }
// 3. UI 요소 참조 획득 // 3. 테마 적용 및 변경 구독
UTKThemeManager.Instance.ApplyThemeToElement(this);
SubscribeToThemeChanges();
// USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 4. UI 요소 참조 획득
_searchField = this.Q<TextField>("search-field"); _searchField = this.Q<TextField>("search-field");
_listView = this.Q<ListView>("main-list-view"); _listView = this.Q<ListView>("main-list-view");
_clearButton = this.Q<Button>("clear-btn"); _clearButton = this.Q<UTKButton>("clear-btn");
_searchResultLabel = this.Q<Label>("search-result-label"); _searchResultLabel = this.Q<Label>("search-result-label");
// 4. 이벤트 연결 및 로직 초기화 // 5. Clear 버튼 아이콘 설정
if (_clearButton != null)
{
_clearButton.SetMaterialIcon(UTKMaterialIcons.Close, 12);
}
// 6. 이벤트 연결 및 로직 초기화
InitializeLogic(); InitializeLogic();
} }
@@ -277,7 +398,7 @@ namespace UVC.UIToolkit
// 검색어 지우기 버튼 이벤트 등록 // 검색어 지우기 버튼 이벤트 등록
if (_clearButton != null) if (_clearButton != null)
{ {
_clearButton.clicked += OnClearButtonClicked; _clearButton.OnClicked += OnClearButtonClicked;
_clearButton.style.display = DisplayStyle.None; // 초기에는 숨김 _clearButton.style.display = DisplayStyle.None; // 초기에는 숨김
} }
@@ -1144,6 +1265,24 @@ namespace UVC.UIToolkit
#endregion #endregion
#region (Theme)
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region IDisposable #region IDisposable
/// <summary> /// <summary>
@@ -1154,6 +1293,9 @@ namespace UVC.UIToolkit
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
// 테마 변경 이벤트 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
// 이미지 로딩 취소 // 이미지 로딩 취소
CancelImageLoading(); CancelImageLoading();
@@ -1176,7 +1318,8 @@ namespace UVC.UIToolkit
if (_clearButton != null) if (_clearButton != null)
{ {
_clearButton.clicked -= OnClearButtonClicked; _clearButton.OnClicked -= OnClearButtonClicked;
_clearButton.Dispose();
} }
// 드래그 고스트 정리 // 드래그 고스트 정리

View File

@@ -22,6 +22,113 @@ namespace UVC.UIToolkit
/// <item>드래그 앤 드롭 이벤트 전달</item> /// <item>드래그 앤 드롭 이벤트 전달</item>
/// </list> /// </list>
/// ///
/// <para><b>UXML 사용 예시:</b></para>
/// <code><![CDATA[
/// <!-- UXML 파일에서 UTKAccordionListWindow 사용 -->
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKAccordionListWindow name="accordion-window" />
/// </ui:UXML>
/// ]]></code>
///
/// <para><b>C# 사용 예시:</b></para>
/// <code><![CDATA[
/// // 1. 윈도우 참조 획득
/// var accordionWindow = root.Q<UTKAccordionListWindow>("accordion-window");
///
/// // 2. 윈도우 제목 설정
/// accordionWindow.Title = "프리팹 라이브러리";
///
/// // 3. 닫기 버튼 표시 설정
/// accordionWindow.ShowCloseButton = true;
///
/// // 4. 데이터 구성 및 설정
/// var data = new List<UTKAccordionItemData>
/// {
/// new UTKAccordionItemData
/// {
/// nodeType = UTKAccordionNodeType.Section,
/// name = "가구",
/// isExpanded = true,
/// children = new List<UTKAccordionItemData>
/// {
/// new UTKAccordionItemData
/// {
/// nodeType = UTKAccordionNodeType.GridItem,
/// name = "의자",
/// imagePath = "Prefabs/Thumbnails/chair",
/// prefabPath = "Prefabs/Furniture/Chair"
/// }
/// }
/// }
/// };
/// accordionWindow.SetData(data);
///
/// // 5. 아이템 클릭 이벤트 구독 (통합 API)
/// accordionWindow.OnItemClick += (item) =>
/// {
/// Debug.Log($"클릭된 아이템: {item.name}");
/// };
///
/// // 6. 드래그 앤 드롭 이벤트 구독
/// accordionWindow.OnItemBeginDrag += (item, screenPos) =>
/// {
/// Debug.Log($"드래그 시작: {item.name}");
/// };
///
/// accordionWindow.OnItemDrop += (item) =>
/// {
/// // 프리팹 인스턴스화
/// if (!string.IsNullOrEmpty(item.prefabPath))
/// {
/// var prefab = Resources.Load<GameObject>(item.prefabPath);
/// Instantiate(prefab, dropPosition, Quaternion.identity);
/// }
/// };
///
/// // 7. 리스트 영역 밖으로 드래그 시 3D 미리보기 표시
/// accordionWindow.OnDragExitList += (gridItem, screenPos) =>
/// {
/// Show3DPreview(gridItem.PrefabPath, screenPos);
/// };
///
/// // 8. 리스트 영역으로 다시 들어오면 미리보기 숨김
/// accordionWindow.OnDragEnterList += (gridItem, screenPos) =>
/// {
/// Hide3DPreview();
/// };
///
/// // 9. 섹션 펼침/접힘 이벤트
/// accordionWindow.OnSectionToggled += (section, isExpanded) =>
/// {
/// Debug.Log($"섹션 '{section.Title}': {(isExpanded ? "펼침" : "접힘")}");
/// };
///
/// // 10. 윈도우 닫힘 이벤트
/// accordionWindow.OnClosed += () =>
/// {
/// Debug.Log("윈도우가 닫혔습니다.");
/// };
///
/// // 11. 드래그 고스트 이미지 설정
/// accordionWindow.ShowDragGhost = true;
///
/// // 12. 검색 실행
/// accordionWindow.Search("의자");
/// accordionWindow.ClearSearch();
///
/// // 13. 섹션 제어
/// accordionWindow.ExpandAll();
/// accordionWindow.CollapseAll();
/// accordionWindow.SetSectionExpanded(sectionId, true);
///
/// // 14. 윈도우 표시/닫기
/// accordionWindow.Show();
/// accordionWindow.Close();
///
/// // 15. 리소스 해제 (OnDestroy에서 호출)
/// accordionWindow.Dispose();
/// ]]></code>
///
/// <para><b>관련 리소스:</b></para> /// <para><b>관련 리소스:</b></para>
/// <list type="bullet"> /// <list type="bullet">
/// <item>Resources/UIToolkit/Window/UTKAccordionListWindow.uxml - 윈도우 레이아웃</item> /// <item>Resources/UIToolkit/Window/UTKAccordionListWindow.uxml - 윈도우 레이아웃</item>
@@ -34,13 +141,14 @@ namespace UVC.UIToolkit
#region (Constants) #region (Constants)
private const string UXML_PATH = "UIToolkit/Window/UTKAccordionListWindow"; private const string UXML_PATH = "UIToolkit/Window/UTKAccordionListWindow";
private const string USS_PATH = "UIToolkit/Window/UTKAccordionListWindowUss";
#endregion #endregion
#region UI (UI Component References) #region UI (UI Component References)
private UTKAccordionList? _accordionList; private UTKAccordionList? _accordionList;
private Button? _closeButton; private UTKButton? _closeButton;
private Label? _titleLabel; private Label? _titleLabel;
#endregion #endregion
@@ -226,6 +334,17 @@ namespace UVC.UIToolkit
} }
visualTree.CloneTree(this); visualTree.CloneTree(this);
// 테마 적용 및 변경 구독
UTKThemeManager.Instance.ApplyThemeToElement(this);
SubscribeToThemeChanges();
// USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 내부 UTKAccordionList 찾기 // 내부 UTKAccordionList 찾기
_accordionList = this.Q<UTKAccordionList>(); _accordionList = this.Q<UTKAccordionList>();
if (_accordionList == null) if (_accordionList == null)
@@ -240,12 +359,13 @@ namespace UVC.UIToolkit
// 헤더 요소 참조 // 헤더 요소 참조
_titleLabel = this.Q<Label>("title"); _titleLabel = this.Q<Label>("title");
_closeButton = this.Q<Button>("close-btn"); _closeButton = this.Q<UTKButton>("close-btn");
// 닫기 버튼 이벤트 // 닫기 버튼 설정 및 이벤트 연결
if (_closeButton != null) if (_closeButton != null)
{ {
_closeButton.clicked += OnCloseButtonClicked; _closeButton.SetMaterialIcon(UTKMaterialIcons.Close, 16);
_closeButton.OnClicked += OnCloseButtonClicked;
} }
} }
@@ -326,6 +446,24 @@ namespace UVC.UIToolkit
#endregion #endregion
#region (Theme)
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region IDisposable #region IDisposable
public void Dispose() public void Dispose()
@@ -333,14 +471,18 @@ namespace UVC.UIToolkit
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
// 테마 변경 이벤트 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
// 내부 UTKAccordionList 정리 // 내부 UTKAccordionList 정리
_accordionList?.Dispose(); _accordionList?.Dispose();
_accordionList = null; _accordionList = null;
// 닫기 버튼 이벤트 해제 // 닫기 버튼 이벤트 해제 및 정리
if (_closeButton != null) if (_closeButton != null)
{ {
_closeButton.clicked -= OnCloseButtonClicked; _closeButton.OnClicked -= OnCloseButtonClicked;
_closeButton.Dispose();
} }
// 외부 이벤트 정리 // 외부 이벤트 정리

View File

@@ -14,17 +14,119 @@ namespace UVC.UIToolkit
/// 윈도우 형태의 컴포넌트입니다. 모든 트리 관련 기능은 내부 UTKComponentList에 위임됩니다. /// 윈도우 형태의 컴포넌트입니다. 모든 트리 관련 기능은 내부 UTKComponentList에 위임됩니다.
/// </para> /// </para>
/// ///
/// <para><b>UXML에서 사용:</b></para> /// <para><b>UXML 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// <UVC.UIToolkit.Window.UTKComponentListWindow name="tree-list-window" /> /// <!-- UXML 파일에서 UTKComponentListWindow 사용 -->
/// </code> /// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKComponentListWindow name="component-window" />
/// </ui:UXML>
/// ]]></code>
/// ///
/// <para><b>코드에서 사용:</b></para> /// <para><b>C# 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// var window = root.Q<UTKComponentListWindow>(); /// // 1. 윈도우 참조 획득
/// window.OnItemSelected += (items) => Debug.Log($"선택: {items[0].name}"); /// var componentWindow = root.Q<UTKComponentListWindow>("component-window");
/// window.SetData(treeItems); ///
/// </code> /// // 2. 윈도우 제목 및 닫기 버튼 설정
/// componentWindow.Title = "모델 리스트";
/// componentWindow.ShowCloseButton = true;
///
/// // 3. 데이터 구성 - 카테고리와 아이템
/// var data = new List<UTKComponentListItemDataBase>
/// {
/// new UTKComponentListCategoryData
/// {
/// name = "캐릭터",
/// isExpanded = true,
/// children = new List<UTKComponentListItemDataBase>
/// {
/// new UTKComponentListItemData
/// {
/// name = "플레이어",
/// ExternalKey = "player_001",
/// IsVisible = true
/// },
/// new UTKComponentListItemData
/// {
/// name = "NPC",
/// ExternalKey = "npc_001",
/// IsVisible = true
/// }
/// }
/// }
/// };
/// componentWindow.SetData(data);
///
/// // 4. 선택 이벤트 구독
/// componentWindow.OnItemSelected = (selectedItems) =>
/// {
/// foreach (var item in selectedItems)
/// {
/// Debug.Log($"선택됨: {item.name}");
/// // 3D 뷰에서 해당 모델 하이라이트
/// HighlightModel(item.ExternalKey);
/// }
/// };
///
/// // 5. 선택 해제 이벤트
/// componentWindow.OnItemDeselected = (deselectedItems) =>
/// {
/// foreach (var item in deselectedItems)
/// {
/// UnhighlightModel(item.ExternalKey);
/// }
/// };
///
/// // 6. 가시성 변경 이벤트 (눈 아이콘)
/// componentWindow.OnItemVisibilityChanged += (item, isVisible) =>
/// {
/// var gameObject = FindGameObjectByKey(item.ExternalKey);
/// if (gameObject != null)
/// {
/// gameObject.SetActive(isVisible);
/// }
/// };
///
/// // 7. 삭제 이벤트 (Delete/Backspace 키)
/// componentWindow.EnabledDeleteItem = true;
/// componentWindow.OnItemDeleted = (item) =>
/// {
/// Debug.Log($"삭제 요청: {item.name}");
/// componentWindow.DeleteItem(item);
/// };
///
/// // 8. 더블클릭 이벤트
/// componentWindow.OnItemDoubleClicked = (item) =>
/// {
/// // 카메라 포커스
/// FocusCameraOn(item.ExternalKey);
/// };
///
/// // 9. 윈도우 닫힘 이벤트
/// componentWindow.OnClosed += () =>
/// {
/// Debug.Log("윈도우가 닫혔습니다.");
/// };
///
/// // 10. 프로그래밍 방식 선택
/// componentWindow.SelectItem("플레이어", notify: true);
/// componentWindow.DeselectItem("플레이어", notify: false);
/// componentWindow.ClearSelection();
/// componentWindow.SelectByItemId(itemId);
///
/// // 11. 아이템 추가/삭제
/// var newItem = new UTKComponentListItemData { name = "새 캐릭터" };
/// componentWindow.AddItem(newItem);
/// componentWindow.AddItem(categoryData, newItem);
/// componentWindow.DeleteItem(newItem);
/// componentWindow.SetItemName(newItem, "수정된 이름");
///
/// // 12. 윈도우 표시
/// componentWindow.Show();
///
/// // 13. 리소스 해제 (OnDestroy에서 호출)
/// componentWindow.Dispose();
/// ]]></code>
/// </summary> /// </summary>
[UxmlElement] [UxmlElement]
public partial class UTKComponentListWindow : VisualElement, IDisposable public partial class UTKComponentListWindow : VisualElement, IDisposable
@@ -36,14 +138,20 @@ namespace UVC.UIToolkit
#region (Constants) #region (Constants)
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary> /// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
private const string UXML_PATH = "UIToolkit/Window/UTKComponentListWindow"; private const string UXML_PATH = "UIToolkit/Window/UTKComponentListWindow";
/// <summary>USS 파일 경로 (Resources 폴더 기준)</summary>
private const string USS_PATH = "UIToolkit/Window/UTKComponentListWindowUss";
#endregion #endregion
#region UI (UI Component References) #region UI (UI Component References)
/// <summary>내부 UTKComponentList 컴포넌트</summary> /// <summary>내부 UTKComponentList 컴포넌트</summary>
private UTKComponentList? _componentList; private UTKComponentList? _componentList;
/// <summary>트리 리스트 닫기 버튼</summary> /// <summary>트리 리스트 닫기 버튼 (UTKButton)</summary>
private Button? _closeButton; private UTKButton? _closeButton;
/// <summary>윈도우 제목 라벨</summary>
private Label? _titleLabel;
#endregion #endregion
#region (Public Properties) #region (Public Properties)
@@ -57,6 +165,20 @@ namespace UVC.UIToolkit
get => _componentList?.EnabledDeleteItem ?? false; get => _componentList?.EnabledDeleteItem ?? false;
set { if (_componentList != null) _componentList.EnabledDeleteItem = value; } set { if (_componentList != null) _componentList.EnabledDeleteItem = value; }
} }
/// <summary>윈도우 제목을 가져오거나 설정합니다.</summary>
public string Title
{
get => _titleLabel?.text ?? string.Empty;
set { if (_titleLabel != null) _titleLabel.text = value; }
}
/// <summary>닫기 버튼 표시 여부를 설정합니다.</summary>
public bool ShowCloseButton
{
get => _closeButton?.style.display == DisplayStyle.Flex;
set { if (_closeButton != null) _closeButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None; }
}
#endregion #endregion
#region (Public Events) #region (Public Events)
@@ -145,16 +267,34 @@ namespace UVC.UIToolkit
return; return;
} }
// 3. 닫기 버튼 찾기 및 이벤트 연결 // 3. 테마 적용 및 변경 구독
_closeButton = this.Q<Button>("close-btn"); UTKThemeManager.Instance.ApplyThemeToElement(this);
SubscribeToThemeChanges();
// USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 4. 헤더 요소 참조
_titleLabel = this.Q<Label>("title");
_closeButton = this.Q<UTKButton>("close-btn");
// 5. 닫기 버튼 설정 및 이벤트 연결
if (_closeButton != null) if (_closeButton != null)
{ {
_closeButton.clicked += () => _closeButton.SetMaterialIcon(UTKMaterialIcons.Close, 16);
_closeButton.OnClicked += OnCloseButtonClicked;
}
}
/// <summary>닫기 버튼 클릭 이벤트 핸들러</summary>
private void OnCloseButtonClicked()
{ {
this.style.display = DisplayStyle.None; this.style.display = DisplayStyle.None;
OnClosed?.Invoke(); OnClosed?.Invoke();
};
}
} }
#endregion #endregion
@@ -263,6 +403,24 @@ namespace UVC.UIToolkit
} }
#endregion #endregion
#region (Theme)
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region IDisposable #region IDisposable
/// <summary> /// <summary>
/// 리소스를 해제하고 이벤트 핸들러를 정리합니다. /// 리소스를 해제하고 이벤트 핸들러를 정리합니다.
@@ -272,15 +430,26 @@ namespace UVC.UIToolkit
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
// 테마 변경 이벤트 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
// 내부 UTKComponentList 정리 // 내부 UTKComponentList 정리
_componentList?.Dispose(); _componentList?.Dispose();
_componentList = null; _componentList = null;
// 닫기 버튼 이벤트 해제 및 정리
if (_closeButton != null)
{
_closeButton.OnClicked -= OnCloseButtonClicked;
_closeButton.Dispose();
}
// 외부 이벤트 구독자 정리 // 외부 이벤트 구독자 정리
OnClosed = null; OnClosed = null;
// UI 참조 정리 // UI 참조 정리
_closeButton = null; _closeButton = null;
_titleLabel = null;
} }
#endregion #endregion
} }

View File

@@ -15,18 +15,132 @@ namespace UVC.UIToolkit
/// 탭 버튼들을 추가한 윈도우 형태의 컴포넌트입니다. 탭을 통해 카테고리별 필터링이 가능합니다. /// 탭 버튼들을 추가한 윈도우 형태의 컴포넌트입니다. 탭을 통해 카테고리별 필터링이 가능합니다.
/// </para> /// </para>
/// ///
/// <para><b>UXML에서 사용:</b></para> /// <para><b>UXML 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// <UVC.UIToolkit.Window.UTKComponentTabListWindow name="tab-list-window" /> /// <!-- UXML 파일에서 UTKComponentTabListWindow 사용 -->
/// </code> /// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKComponentTabListWindow name="tab-list-window" />
/// </ui:UXML>
/// ]]></code>
/// ///
/// <para><b>코드에서 사용:</b></para> /// <para><b>C# 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// var window = root.Q<UTKComponentTabListWindow>(); /// // 1. 윈도우 참조 획득
/// window.OnItemSelected += (items) => Debug.Log($"선택: {items[0].name}"); /// var tabWindow = root.Q<UTKComponentTabListWindow>("tab-list-window");
/// window.SetData(treeItems); ///
/// window.SelectTab(0); // 첫 번째 카테고리 탭 선택 /// // 2. 윈도우 제목 및 닫기 버튼 설정
/// </code> /// tabWindow.Title = "에셋 라이브러리";
/// tabWindow.ShowCloseButton = true;
///
/// // 3. 데이터 구성 - 카테고리별로 자동 탭 생성됨
/// var data = new List<UTKComponentListItemDataBase>
/// {
/// // 첫 번째 카테고리 → "캐릭터" 탭 자동 생성
/// new UTKComponentListCategoryData
/// {
/// name = "캐릭터",
/// isExpanded = true,
/// children = new List<UTKComponentListItemDataBase>
/// {
/// new UTKComponentListItemData { name = "플레이어", ExternalKey = "player" },
/// new UTKComponentListItemData { name = "NPC", ExternalKey = "npc" }
/// }
/// },
/// // 두 번째 카테고리 → "오브젝트" 탭 자동 생성
/// new UTKComponentListCategoryData
/// {
/// name = "오브젝트",
/// isExpanded = true,
/// children = new List<UTKComponentListItemDataBase>
/// {
/// new UTKComponentListItemData { name = "상자", ExternalKey = "box" },
/// new UTKComponentListItemData { name = "나무", ExternalKey = "tree" }
/// }
/// }
/// };
/// tabWindow.SetData(data);
/// // → "All" 탭과 "캐릭터", "오브젝트" 탭이 자동 생성됨
///
/// // 4. 탭 선택 (프로그래밍 방식)
/// tabWindow.SelectTab(-1); // -1 = "All" 전체 탭
/// tabWindow.SelectTab(0); // 0 = 첫 번째 카테고리 ("캐릭터")
/// tabWindow.SelectTab(1); // 1 = 두 번째 카테고리 ("오브젝트")
///
/// // 5. 선택 이벤트 구독
/// tabWindow.OnItemSelected = (selectedItems) =>
/// {
/// foreach (var item in selectedItems)
/// {
/// Debug.Log($"선택됨: {item.name}");
/// }
/// };
///
/// // 6. 선택 해제 이벤트
/// tabWindow.OnItemDeselected = (deselectedItems) =>
/// {
/// foreach (var item in deselectedItems)
/// {
/// Debug.Log($"선택 해제: {item.name}");
/// }
/// };
///
/// // 7. 가시성 변경 이벤트 (눈 아이콘)
/// tabWindow.OnItemVisibilityChanged += (item, isVisible) =>
/// {
/// var gameObject = FindGameObjectByKey(item.ExternalKey);
/// if (gameObject != null)
/// {
/// gameObject.SetActive(isVisible);
/// }
/// };
///
/// // 8. 삭제 이벤트 (Delete/Backspace 키)
/// tabWindow.EnabledDeleteItem = true;
/// tabWindow.OnItemDeleted = (item) =>
/// {
/// Debug.Log($"삭제 요청: {item.name}");
/// tabWindow.DeleteItem(item); // 탭 목록도 자동 갱신됨
/// };
///
/// // 9. 더블클릭 이벤트
/// tabWindow.OnItemDoubleClicked = (item) =>
/// {
/// FocusCameraOn(item.ExternalKey);
/// };
///
/// // 10. 윈도우 닫힘 이벤트
/// tabWindow.OnClosed += () =>
/// {
/// Debug.Log("윈도우가 닫혔습니다.");
/// };
///
/// // 11. 프로그래밍 방식 선택
/// tabWindow.SelectItem("플레이어", notify: true);
/// tabWindow.DeselectItem("플레이어", notify: false);
/// tabWindow.ClearSelection();
///
/// // 12. 아이템 추가/삭제 (탭 자동 갱신)
/// var newCategory = new UTKComponentListCategoryData { name = "이펙트" };
/// tabWindow.AddItem(newCategory); // "이펙트" 탭 자동 생성
///
/// var newItem = new UTKComponentListItemData { name = "폭발" };
/// tabWindow.AddItem(newCategory, newItem); // 카테고리에 아이템 추가
///
/// tabWindow.DeleteItem(newItem); // 아이템 삭제
///
/// // 13. 윈도우 표시
/// tabWindow.Show();
///
/// // 14. 리소스 해제 (OnDestroy에서 호출)
/// tabWindow.Dispose();
/// ]]></code>
///
/// <para><b>탭 동작 설명:</b></para>
/// <list type="bullet">
/// <item>"All" 탭: 모든 카테고리와 아이템을 표시합니다.</item>
/// <item>카테고리 탭: 해당 카테고리의 자식 아이템만 표시합니다 (부모 카테고리 없이).</item>
/// <item>탭별 검색어가 저장되어, 탭 전환 시 해당 탭의 검색어가 복원됩니다.</item>
/// </list>
/// </summary> /// </summary>
[UxmlElement] [UxmlElement]
public partial class UTKComponentTabListWindow : VisualElement, IDisposable public partial class UTKComponentTabListWindow : VisualElement, IDisposable
@@ -39,6 +153,9 @@ namespace UVC.UIToolkit
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary> /// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
private const string UXML_PATH = "UIToolkit/Window/UTKComponentTabListWindow"; private const string UXML_PATH = "UIToolkit/Window/UTKComponentTabListWindow";
/// <summary>USS 파일 경로 (Resources 폴더 기준)</summary>
private const string USS_PATH = "UIToolkit/Window/UTKComponentListWindowUss";
/// <summary>"전체" 탭을 나타내는 인덱스</summary> /// <summary>"전체" 탭을 나타내는 인덱스</summary>
private const int ALL_TAB_INDEX = -1; private const int ALL_TAB_INDEX = -1;
#endregion #endregion
@@ -50,8 +167,11 @@ namespace UVC.UIToolkit
/// <summary>탭 버튼 컨테이너</summary> /// <summary>탭 버튼 컨테이너</summary>
private VisualElement? _tabContainer; private VisualElement? _tabContainer;
/// <summary>트리 리스트 닫기 버튼</summary> /// <summary>트리 리스트 닫기 버튼 (UTKButton)</summary>
private Button? _closeButton; private UTKButton? _closeButton;
/// <summary>윈도우 제목 라벨</summary>
private Label? _titleLabel;
#endregion #endregion
#region (Tab Data) #region (Tab Data)
@@ -79,6 +199,20 @@ namespace UVC.UIToolkit
get => _componentList?.EnabledDeleteItem ?? false; get => _componentList?.EnabledDeleteItem ?? false;
set { if (_componentList != null) _componentList.EnabledDeleteItem = value; } set { if (_componentList != null) _componentList.EnabledDeleteItem = value; }
} }
/// <summary>윈도우 제목을 가져오거나 설정합니다.</summary>
public string Title
{
get => _titleLabel?.text ?? string.Empty;
set { if (_titleLabel != null) _titleLabel.text = value; }
}
/// <summary>닫기 버튼 표시 여부를 설정합니다.</summary>
public bool ShowCloseButton
{
get => _closeButton?.style.display == DisplayStyle.Flex;
set { if (_closeButton != null) _closeButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None; }
}
#endregion #endregion
#region (Public Events) #region (Public Events)
@@ -167,19 +301,37 @@ namespace UVC.UIToolkit
return; return;
} }
// 3. 탭 관련 요소 찾기 // 3. 테마 적용 및 변경 구독
UTKThemeManager.Instance.ApplyThemeToElement(this);
SubscribeToThemeChanges();
// USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 4. 탭 관련 요소 찾기
_tabContainer = this.Q<VisualElement>("tab-container"); _tabContainer = this.Q<VisualElement>("tab-container");
// 4. 닫기 버튼 찾기 및 이벤트 연결 // 5. 헤더 요소 참조
_closeButton = this.Q<Button>("close-btn"); _titleLabel = this.Q<Label>("title");
_closeButton = this.Q<UTKButton>("close-btn");
// 6. 닫기 버튼 설정 및 이벤트 연결
if (_closeButton != null) if (_closeButton != null)
{ {
_closeButton.clicked += () => _closeButton.SetMaterialIcon(UTKMaterialIcons.Close, 16);
_closeButton.OnClicked += OnCloseButtonClicked;
}
}
/// <summary>닫기 버튼 클릭 이벤트 핸들러</summary>
private void OnCloseButtonClicked()
{ {
this.style.display = DisplayStyle.None; this.style.display = DisplayStyle.None;
OnClosed?.Invoke(); OnClosed?.Invoke();
};
}
} }
#endregion #endregion
@@ -507,6 +659,24 @@ namespace UVC.UIToolkit
} }
#endregion #endregion
#region (Theme)
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region IDisposable #region IDisposable
/// <summary> /// <summary>
/// 리소스를 해제하고 이벤트 핸들러를 정리합니다. /// 리소스를 해제하고 이벤트 핸들러를 정리합니다.
@@ -516,6 +686,9 @@ namespace UVC.UIToolkit
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
// 테마 변경 이벤트 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
// 내부 UTKComponentList 정리 // 내부 UTKComponentList 정리
_componentList?.Dispose(); _componentList?.Dispose();
_componentList = null; _componentList = null;
@@ -524,6 +697,13 @@ namespace UVC.UIToolkit
_tabButtons.Clear(); _tabButtons.Clear();
_tabContainer = null; _tabContainer = null;
// 닫기 버튼 이벤트 해제 및 정리
if (_closeButton != null)
{
_closeButton.OnClicked -= OnCloseButtonClicked;
_closeButton.Dispose();
}
// 외부 이벤트 구독자 정리 // 외부 이벤트 구독자 정리
OnClosed = null; OnClosed = null;
@@ -533,6 +713,7 @@ namespace UVC.UIToolkit
// UI 참조 정리 // UI 참조 정리
_closeButton = null; _closeButton = null;
_titleLabel = null;
} }
#endregion #endregion
} }

View File

@@ -22,17 +22,116 @@ namespace UVC.UIToolkit
/// <item>드래그 앤 드롭 이벤트 전달</item> /// <item>드래그 앤 드롭 이벤트 전달</item>
/// </list> /// </list>
/// ///
/// <para><b>UXML에서 사용:</b></para> /// <para><b>UXML 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// <uvc:UTKImageListWindow name="library-window" /> /// <!-- UXML 파일에서 UTKImageListWindow 사용 -->
/// </code> /// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKImageListWindow name="library-window" />
/// </ui:UXML>
/// ]]></code>
/// ///
/// <para><b>코드에서 사용:</b></para> /// <para><b>C# 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// var window = root.Q<UTKImageListWindow>(); /// // 1. 윈도우 참조 획득
/// window.OnItemClick += (item) => Debug.Log($"클릭: {item.itemName}"); /// var imageWindow = root.Q<UTKImageListWindow>("library-window");
/// window.SetData(imageItems); ///
/// </code> /// // 2. 윈도우 제목 및 닫기 버튼 설정
/// imageWindow.Title = "프리팹 라이브러리";
/// imageWindow.ShowCloseButton = true;
///
/// // 3. 데이터 구성 - 이미지와 이름을 가진 아이템들
/// var data = new List<UTKImageListItemData>
/// {
/// new UTKImageListItemData
/// {
/// itemName = "의자",
/// imagePath = "Prefabs/Thumbnails/chair",
/// prefabPath = "Prefabs/Furniture/Chair"
/// },
/// new UTKImageListItemData
/// {
/// itemName = "책상",
/// imagePath = "Prefabs/Thumbnails/desk",
/// prefabPath = "Prefabs/Furniture/Desk"
/// }
/// };
/// imageWindow.SetData(data);
///
/// // 4. 아이템 클릭 이벤트 구독
/// imageWindow.OnItemClick = (item) =>
/// {
/// Debug.Log($"클릭된 아이템: {item.itemName}");
/// };
///
/// // 5. 드래그 시작 이벤트
/// imageWindow.OnItemBeginDrag = (item, screenPos) =>
/// {
/// Debug.Log($"드래그 시작: {item.itemName}");
/// };
///
/// // 6. 드래그 중 이벤트
/// imageWindow.OnItemDrag = (item, screenPos) =>
/// {
/// // 드래그 중 위치 업데이트
/// };
///
/// // 7. 드래그 종료 이벤트
/// imageWindow.OnItemEndDrag = (item, screenPos) =>
/// {
/// Debug.Log($"드래그 종료: {item.itemName}");
/// };
///
/// // 8. 드롭 이벤트 (프리팹 인스턴스화)
/// imageWindow.OnItemDrop = (item) =>
/// {
/// if (!string.IsNullOrEmpty(item.prefabPath))
/// {
/// var prefab = Resources.Load<GameObject>(item.prefabPath);
/// Instantiate(prefab, dropPosition, Quaternion.identity);
/// }
/// };
///
/// // 9. 리스트 영역 밖으로 드래그 시 3D 미리보기 표시
/// imageWindow.OnDragExitList = (item, screenPos) =>
/// {
/// // 리스트 밖 = 3D 씬 영역
/// Show3DPreview(item.prefabPath, screenPos);
/// };
///
/// // 10. 리스트 영역으로 다시 들어오면 미리보기 숨김
/// imageWindow.OnDragEnterList = (item, screenPos) =>
/// {
/// Hide3DPreview();
/// };
///
/// // 11. 윈도우 닫힘 이벤트
/// imageWindow.OnClosed += () =>
/// {
/// Debug.Log("윈도우가 닫혔습니다.");
/// };
///
/// // 12. 드래그 고스트 이미지 설정
/// imageWindow.DragImageFollowCursor = true;
///
/// // 13. 검색 실행
/// imageWindow.ApplySearch("의자");
///
/// // 14. 아이템 추가/제거
/// var newItem = new UTKImageListItemData { itemName = "새 아이템" };
/// imageWindow.AddItem(newItem);
/// imageWindow.RemoveItem(newItem);
/// imageWindow.Clear();
///
/// // 15. 아이템 개수 확인
/// int count = imageWindow.ItemCount;
///
/// // 16. 윈도우 표시/닫기
/// imageWindow.Show();
/// imageWindow.Close();
///
/// // 17. 리소스 해제 (OnDestroy에서 호출)
/// imageWindow.Dispose();
/// ]]></code>
/// ///
/// <para><b>관련 리소스:</b></para> /// <para><b>관련 리소스:</b></para>
/// <list type="bullet"> /// <list type="bullet">
@@ -48,6 +147,9 @@ namespace UVC.UIToolkit
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary> /// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
private const string UXML_PATH = "UIToolkit/Window/UTKImageListWindow"; private const string UXML_PATH = "UIToolkit/Window/UTKImageListWindow";
/// <summary>USS 파일 경로 (Resources 폴더 기준)</summary>
private const string USS_PATH = "UIToolkit/Window/UTKImageListWindowUss";
#endregion #endregion
#region UI (UI Component References) #region UI (UI Component References)
@@ -55,8 +157,8 @@ namespace UVC.UIToolkit
/// <summary>내부 UTKImageList 컴포넌트</summary> /// <summary>내부 UTKImageList 컴포넌트</summary>
private UTKImageList? _imageList; private UTKImageList? _imageList;
/// <summary>윈도우 닫기 버튼</summary> /// <summary>윈도우 닫기 버튼 (UTKButton)</summary>
private Button? _closeButton; private UTKButton? _closeButton;
/// <summary>윈도우 제목 라벨</summary> /// <summary>윈도우 제목 라벨</summary>
private Label? _titleLabel; private Label? _titleLabel;
@@ -214,7 +316,18 @@ namespace UVC.UIToolkit
} }
visualTree.CloneTree(this); visualTree.CloneTree(this);
// 2. 내부 UTKImageList 찾기 (UXML에서 생성된 컴포넌트) // 2. 테마 적용 및 변경 구독
UTKThemeManager.Instance.ApplyThemeToElement(this);
SubscribeToThemeChanges();
// USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 3. 내부 UTKImageList 찾기 (UXML에서 생성된 컴포넌트)
_imageList = this.Q<UTKImageList>(); _imageList = this.Q<UTKImageList>();
if (_imageList == null) if (_imageList == null)
{ {
@@ -226,14 +339,15 @@ namespace UVC.UIToolkit
_imageList.DragBoundsElement = this; _imageList.DragBoundsElement = this;
} }
// 3. 헤더 요소 참조 // 4. 헤더 요소 참조
_titleLabel = this.Q<Label>("title"); _titleLabel = this.Q<Label>("title");
_closeButton = this.Q<Button>("close-btn"); _closeButton = this.Q<UTKButton>("close-btn");
// 4. 닫기 버튼 이벤트 연결 // 5. 닫기 버튼 설정 및 이벤트 연결
if (_closeButton != null) if (_closeButton != null)
{ {
_closeButton.clicked += OnCloseButtonClicked; _closeButton.SetMaterialIcon(UTKMaterialIcons.Close, 16);
_closeButton.OnClicked += OnCloseButtonClicked;
} }
} }
@@ -320,6 +434,24 @@ namespace UVC.UIToolkit
#endregion #endregion
#region (Theme)
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region IDisposable #region IDisposable
/// <summary> /// <summary>
@@ -330,14 +462,18 @@ namespace UVC.UIToolkit
if (_disposed) return; if (_disposed) return;
_disposed = true; _disposed = true;
// 테마 변경 이벤트 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
// 내부 UTKImageList 정리 // 내부 UTKImageList 정리
_imageList?.Dispose(); _imageList?.Dispose();
_imageList = null; _imageList = null;
// 닫기 버튼 이벤트 해제 // 닫기 버튼 이벤트 해제 및 정리
if (_closeButton != null) if (_closeButton != null)
{ {
_closeButton.clicked -= OnCloseButtonClicked; _closeButton.OnClicked -= OnCloseButtonClicked;
_closeButton.Dispose();
} }
// 외부 이벤트 구독자 정리 // 외부 이벤트 구독자 정리

View File

@@ -13,9 +13,9 @@ namespace UVC.UIToolkit
/// ///
/// <para><b>개요:</b></para> /// <para><b>개요:</b></para>
/// <para> /// <para>
/// TreeListWindow는 Unity UI Toolkit의 TreeView를 래핑하여 검색, 가시성 토글, /// UTKTreeListWindow는 Unity UI Toolkit의 TreeView를 래핑하여 검색, 가시성 토글,
/// 닫기 기능 등을 제공하는 재사용 가능한 컴포넌트입니다. /// 닫기 기능 등을 제공하는 재사용 가능한 컴포넌트입니다.
/// UXML 파일(TreeListWindow.uxml, TreeListItem.uxml)과 함께 사용됩니다. /// UXML 파일(UTKTreeListWindow.uxml, UTKTreeListItem.uxml)과 함께 사용됩니다.
/// </para> /// </para>
/// ///
/// <para><b>주요 기능:</b></para> /// <para><b>주요 기능:</b></para>
@@ -23,21 +23,116 @@ namespace UVC.UIToolkit
/// <item>계층적 트리 구조 표시 (펼치기/접기 지원)</item> /// <item>계층적 트리 구조 표시 (펼치기/접기 지원)</item>
/// <item>실시간 검색 필터링</item> /// <item>실시간 검색 필터링</item>
/// <item>항목별 가시성(눈 아이콘) 토글</item> /// <item>항목별 가시성(눈 아이콘) 토글</item>
/// <item>선택 이벤트 처리</item> /// <item>다중 선택 지원</item>
/// <item>삭제 기능 (Delete/Backspace 키)</item>
/// </list> /// </list>
/// ///
/// <para><b>UXML에서 사용:</b></para> /// <para><b>UXML 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// <UVC.UIToolkit.Window.UTKTreeListWindow name="tree-list" /> /// <!-- UXML 파일에서 UTKTreeListWindow 사용 -->
/// </code> /// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <utk:UTKTreeListWindow name="tree-list-window" />
/// </ui:UXML>
/// ]]></code>
/// ///
/// <para><b>코드에서 사용:</b></para> /// <para><b>C# 사용 예시:</b></para>
/// <code> /// <code><![CDATA[
/// var treeList = root.Q<UTKTreeListWindow>(); /// // 1. 윈도우 참조 획득
/// treeList.OnSelectionChanged += (item) => Debug.Log($"선택: {item.name}"); /// var treeWindow = root.Q<UTKTreeListWindow>("tree-list-window");
/// treeList.OnVisibilityChanged += (item) => model.SetActive(item.id, item.IsVisible); ///
/// treeList.SetData(treeItems); /// // 2. 윈도우 제목 및 닫기 버튼 설정
/// </code> /// treeWindow.Title = "씬 계층구조";
/// treeWindow.ShowCloseButton = true;
///
/// // 3. 데이터 구성 - 계층적 트리 구조
/// var data = new List<UTKTreeListItemData>
/// {
/// new UTKTreeListItemData
/// {
/// name = "루트 오브젝트",
/// isExpanded = true,
/// IsVisible = true,
/// children = new List<UTKTreeListItemData>
/// {
/// new UTKTreeListItemData
/// {
/// name = "자식 오브젝트 1",
/// IsVisible = true
/// },
/// new UTKTreeListItemData
/// {
/// name = "자식 오브젝트 2",
/// IsVisible = false,
/// children = new List<UTKTreeListItemData>
/// {
/// new UTKTreeListItemData { name = "손자 오브젝트" }
/// }
/// }
/// }
/// }
/// };
/// treeWindow.SetData(data);
///
/// // 4. 선택 변경 이벤트 구독 (선택 및 해제 모두)
/// treeWindow.OnSelectionChanged += (selectedItems) =>
/// {
/// Debug.Log($"선택된 아이템 수: {selectedItems.Count}");
/// foreach (var item in selectedItems)
/// {
/// Debug.Log($"선택: {item.name}");
/// }
/// };
///
/// // 5. 가시성 변경 이벤트 (눈 아이콘 클릭)
/// treeWindow.OnVisibilityChanged += (item) =>
/// {
/// Debug.Log($"{item.name} 가시성: {item.IsVisible}");
/// // 3D 씬에서 해당 오브젝트 활성화/비활성화
/// var gameObject = FindGameObjectByName(item.name);
/// if (gameObject != null)
/// {
/// gameObject.SetActive(item.IsVisible);
/// }
/// };
///
/// // 6. 삭제 이벤트 (Delete/Backspace 키)
/// treeWindow.EnabledDeleteItem = true;
/// treeWindow.OnItemDeleted += (item) =>
/// {
/// Debug.Log($"삭제 요청: {item.name}");
/// // 데이터에서 제거 후 UI 갱신
/// RemoveFromData(item);
/// treeWindow.Rebuild();
/// };
///
/// // 7. 윈도우 닫힘 이벤트
/// treeWindow.OnClosed += () =>
/// {
/// Debug.Log("윈도우가 닫혔습니다.");
/// };
///
/// // 8. 검색 실행
/// treeWindow.ApplySearch("자식");
/// // 검색어는 SearchQuery 속성으로 확인 가능
/// string currentQuery = treeWindow.SearchQuery;
///
/// // 9. 프로그래밍 방식 선택
/// treeWindow.SelectByItemId(itemId);
///
/// // 10. 전체 펼치기/접기
/// treeWindow.ExpandAll();
/// treeWindow.CollapseAll();
///
/// // 11. TreeView 갱신 (데이터 변경 후)
/// treeWindow.Rebuild();
///
/// // 12. 윈도우 표시/숨김
/// treeWindow.Show();
/// treeWindow.Hide();
///
/// // 13. 리소스 해제 (OnDestroy에서 호출)
/// treeWindow.Dispose();
/// ]]></code>
/// ///
/// <para><b>관련 리소스:</b></para> /// <para><b>관련 리소스:</b></para>
/// <list type="bullet"> /// <list type="bullet">
@@ -61,6 +156,8 @@ namespace UVC.UIToolkit
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary> /// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
private const string UXML_PATH = "UIToolkit/Window/UTKTreeListWindow"; private const string UXML_PATH = "UIToolkit/Window/UTKTreeListWindow";
/// <summary>USS 파일 경로 (Resources 폴더 기준)</summary>
private const string USS_PATH = "UIToolkit/Window/UTKTreeListWindowUss";
#endregion #endregion
#region UI (UI Component References) #region UI (UI Component References)
@@ -70,11 +167,14 @@ namespace UVC.UIToolkit
/// <summary>Unity UI Toolkit의 TreeView 컴포넌트</summary> /// <summary>Unity UI Toolkit의 TreeView 컴포넌트</summary>
private TreeView? _treeView; private TreeView? _treeView;
/// <summary>트리 리스트 닫기 버튼</summary> /// <summary>트리 리스트 닫기 버튼 (UTKButton)</summary>
private Button? _closeButton; private UTKButton? _closeButton;
/// <summary>검색어 지우기 버튼</summary> /// <summary>검색어 지우기 버튼 (UTKButton)</summary>
private Button? _clearButton; private UTKButton? _clearButton;
/// <summary>윈도우 제목 라벨</summary>
private Label? _titleLabel;
#endregion #endregion
#region (Internal Data) #region (Internal Data)
@@ -124,6 +224,20 @@ namespace UVC.UIToolkit
/// 기본값은 true입니다. /// 기본값은 true입니다.
/// </summary> /// </summary>
public bool EnabledDeleteItem { get; set; } = true; public bool EnabledDeleteItem { get; set; } = true;
/// <summary>윈도우 제목</summary>
public string Title
{
get => _titleLabel?.text ?? string.Empty;
set { if (_titleLabel != null) _titleLabel.text = value; }
}
/// <summary>닫기 버튼 표시 여부</summary>
public bool ShowCloseButton
{
get => _closeButton?.style.display == DisplayStyle.Flex;
set { if (_closeButton != null) _closeButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None; }
}
#endregion #endregion
#region (Public Events) #region (Public Events)
@@ -177,14 +291,36 @@ namespace UVC.UIToolkit
} }
visualTree!.CloneTree(this); visualTree!.CloneTree(this);
// 2. 테마 적용
UTKThemeManager.Instance.ApplyThemeToElement(this);
// 2. 자식 요소 참조 획득 (UXML의 name 속성으로 찾음) // USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 3. 자식 요소 참조 획득 (UXML의 name 속성으로 찾음)
_searchField = this.Q<TextField>("search-field"); _searchField = this.Q<TextField>("search-field");
_treeView = this.Q<TreeView>("main-tree-view"); _treeView = this.Q<TreeView>("main-tree-view");
_closeButton = this.Q<Button>("close-btn"); _titleLabel = this.Q<Label>("title");
_clearButton = this.Q<Button>("clear-btn"); _closeButton = this.Q<UTKButton>("close-btn");
_clearButton = this.Q<UTKButton>("clear-btn");
// 3. 이벤트 연결 및 로직 초기화 // 4. 닫기 버튼 아이콘 설정
if (_closeButton != null)
{
_closeButton.SetMaterialIcon(UTKMaterialIcons.Close, 16);
}
// 5. Clear 버튼 아이콘 설정
if (_clearButton != null)
{
_clearButton.SetMaterialIcon(UTKMaterialIcons.Close, 12);
}
// 6. 이벤트 연결 및 로직 초기화
InitializeLogic(); InitializeLogic();
} }
#endregion #endregion
@@ -218,26 +354,14 @@ namespace UVC.UIToolkit
// 닫기 버튼: 트리 리스트를 숨기고 이벤트 발생 // 닫기 버튼: 트리 리스트를 숨기고 이벤트 발생
if (_closeButton != null) if (_closeButton != null)
{ {
_closeButton.clicked += () => _closeButton.OnClicked += OnCloseButtonClicked;
{
this.style.display = DisplayStyle.None;
OnClosed?.Invoke();
};
} }
// 검색어 지우기 버튼 // 검색어 지우기 버튼
if(_clearButton != null) if(_clearButton != null)
{ {
_clearButton.style.display = DisplayStyle.None; // 초기에는 숨김 _clearButton.style.display = DisplayStyle.None; // 초기에는 숨김
_clearButton.clicked += () => _clearButton.OnClicked += OnClearButtonClicked;
{
if (_searchField.value.Length > 0)
{
_searchField.value = string.Empty;
OnSearch(string.Empty);
}
_clearButton.style.display = DisplayStyle.None; // 클리어 후 숨김
};
} }
// 스크롤바 hover/active 색상 설정 // 스크롤바 hover/active 색상 설정
@@ -1095,6 +1219,33 @@ namespace UVC.UIToolkit
} }
#endregion #endregion
#region (Button Event Handlers)
/// <summary>
/// 닫기 버튼 클릭 이벤트를 처리합니다.
/// </summary>
private void OnCloseButtonClicked()
{
this.style.display = DisplayStyle.None;
OnClosed?.Invoke();
}
/// <summary>
/// Clear 버튼 클릭 이벤트를 처리합니다.
/// </summary>
private void OnClearButtonClicked()
{
if (_searchField != null && _searchField.value.Length > 0)
{
_searchField.value = string.Empty;
OnSearch(string.Empty);
}
if (_clearButton != null)
{
_clearButton.style.display = DisplayStyle.None;
}
}
#endregion
#region (Search Functionality) #region (Search Functionality)
/// <summary> /// <summary>
/// 검색 필드에서 Enter 키를 눌렀을 때 검색을 실행합니다. /// 검색 필드에서 Enter 키를 눌렀을 때 검색을 실행합니다.
@@ -1270,6 +1421,20 @@ namespace UVC.UIToolkit
_treeView.makeItem = null; _treeView.makeItem = null;
} }
// 닫기 버튼 이벤트 해제 및 정리
if (_closeButton != null)
{
_closeButton.OnClicked -= OnCloseButtonClicked;
_closeButton.Dispose();
}
// Clear 버튼 이벤트 해제 및 정리
if (_clearButton != null)
{
_clearButton.OnClicked -= OnClearButtonClicked;
_clearButton.Dispose();
}
// 외부 이벤트 구독자 정리 // 외부 이벤트 구독자 정리
OnItemVisibilityChanged = null; OnItemVisibilityChanged = null;
OnClosed = null; OnClosed = null;
@@ -1290,6 +1455,7 @@ namespace UVC.UIToolkit
// UI 참조 정리 // UI 참조 정리
_searchField = null; _searchField = null;
_treeView = null; _treeView = null;
_titleLabel = null;
_closeButton = null; _closeButton = null;
_clearButton = null; _clearButton = null;
} }

View File

@@ -227,6 +227,35 @@ public struct AssetRef<T> where T : UnityEngine.Object
- `LoadAssetAsync` 핸들은 수명 관리 후 `Release` - `LoadAssetAsync` 핸들은 수명 관리 후 `Release`
- GameObject 직접 참조 대신 경로 문자열 직렬화 - GameObject 직접 참조 대신 경로 문자열 직렬화
### UXML/USS 파일 네이밍 규칙
**⚠️ 중요: UXML과 USS 파일명은 반드시 다르게 지정해야 합니다.**
`Resources.Load<T>(path)`는 확장자 없이 경로를 받기 때문에, 동일한 경로에 UXML과 USS가 모두 존재하면 로드 충돌이 발생할 수 있습니다.
```csharp
// ❌ 잘못된 예: 동일한 경로명 사용
private const string UXML_PATH = "UIToolkit/Window/UTKAccordionListWindow";
private const string USS_PATH = "UIToolkit/Window/UTKAccordionListWindow";
// Resources.Load<VisualTreeAsset>(UXML_PATH); // .uxml 로드
// Resources.Load<StyleSheet>(USS_PATH); // .uss 로드 실패 가능
// ✅ 올바른 예: USS 파일명에 접미사 추가
private const string UXML_PATH = "UIToolkit/Window/UTKAccordionListWindow";
private const string USS_PATH = "UIToolkit/Window/UTKAccordionListWindowUss";
// 파일 구조:
// - UTKAccordionListWindow.uxml
// - UTKAccordionListWindowUss.uss
```
**네이밍 규칙:**
| 파일 유형 | 네이밍 패턴 | 예시 |
|-----------|-------------|------|
| UXML | `{ComponentName}.uxml` | `UTKAccordionListWindow.uxml` |
| USS | `{ComponentName}Uss.uss` | `UTKAccordionListWindowUss.uss` |
--- ---
## 7) USS 스타일 가이드 ## 7) USS 스타일 가이드