UIToolkitTreeList 개발 중

This commit is contained in:
logonkhi
2025-12-24 19:05:54 +09:00
parent a75d48265a
commit b5b815170f
22 changed files with 1537 additions and 0 deletions

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,143 @@
fileFormatVersion: 2
guid: 9dd52303744ec104bb941cd8271e0668
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 64
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WindowsStoreApps
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 91d854fabfbb60445ba500eaffcaea07
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 32
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -0,0 +1,143 @@
fileFormatVersion: 2
guid: 4737b068c8483f348aef876954e2afee
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 32
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WindowsStoreApps
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,254 @@
/*
* UIToolkitTreeList.uss - UIToolkitTreeList 컴포넌트 스타일시트
*
* [개요]
* UIToolkitTreeList.uxml과 UIToolkitTreeListItem.uxml에서 사용하는 스타일을 정의합니다.
* 어두운 테마(Dark Theme)에 맞춰 디자인되었습니다.
*
* [스타일 구조]
* 1. .tree-menu-container : 메인 컨테이너 (어두운 배경, 240px 너비)
* 2. .search-field : 검색 입력 필드 (흰색 배경, 둥근 모서리)
* 3. #main-tree-view : TreeView 기본 설정 (들여쓰기 10px)
* 4. .unity-tree-view__item-toggle : 펼침/접기 토글 버튼
* 5. .unity-collection-view__item : 항목 배경색 (기본/호버/선택)
* 6. .visibility-toggle : 가시성 토글 버튼 (눈 아이콘)
*
* [색상 팔레트]
* - 배경: rgb(40, 44, 52) - 어두운 회색
* - 호버: rgba(0, 10, 37, 0.25) - 반투명 파랑
* - 선택: rgba(0, 10, 37, 0.5) - 더 진한 반투명 파랑
* - 텍스트: rgb(255, 255, 255) - 흰색
*
* [연관 파일]
* - UIToolkitTreeList.uxml : 메인 레이아웃
* - UIToolkitTreeListItem.uxml : 개별 항목 템플릿
* - UIToolkitTreeList.cs : C# 컨트롤러
*/
/* ===================================
메인 컨테이너 스타일
사이드 패널 전체를 감싸는 컨테이너
=================================== */
.tree-menu-container {
background-color: rgb(40, 44, 52); /* 어두운 회색 배경 */
flex-grow: 1; /* 부모 영역 채우기 */
padding: 5px;
padding-top: 25px;
padding-bottom: 25px;
padding-left: 20px;
padding-right: 20px;
width: 240px; /* 고정 너비 */
}
/* ===================================
검색 입력 필드 스타일
흰색 배경의 둥근 입력창
=================================== */
.search-field {
margin-bottom: 20px;
background-color: rgb(255, 255, 255); /* 흰색 배경 */
border-radius: 4px; /* 둥근 모서리 */
height: 30px;
width: auto;
margin-top: 0;
margin-right: 0;
margin-left: 0;
-unity-font-definition: resource('Fonts/Pretendard/Pretendard-Regular');
}
/* 검색 필드 내부 텍스트 입력 영역 */
#search-field > #unity-text-input {
padding-top: 4px;
padding-right: 24px; /* 오른쪽 아이콘 공간 확보 */
padding-bottom: 4px;
padding-left: 4px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
border-top-width: 0; /* 테두리 제거 */
border-right-width: 0;
border-bottom-width: 0;
border-left-width: 0;
}
/* ===================================
체크박스 스타일
=================================== */
#unity-checkmark {
-unity-background-image-tint-color: rgb(255, 255, 255);
}
/* ===================================
TreeView 기본 설정
=================================== */
#main-tree-view {
--unity-item-indent-width: 10; /* 계층별 들여쓰기 너비 */
}
/* 펼침/접기 토글 아이콘 컨테이너 */
#unity-tree-view__item-toggle > VisualElement > VisualElement {
margin-left: 0;
margin-right: 0;
width: 22px; /* 토글 아이콘 너비 */
}
/* 펼침/접기 토글 버튼 */
.unity-tree-view__item-toggle {
margin-right: 5px; /* 라벨과의 간격 */
}
/* ===================================
항목 배경색 스타일
기본, 호버, 선택 상태별 배경색
=================================== */
/* 기본 상태: 투명 배경 */
.unity-collection-view__item {
background-color: rgba(50, 50, 50, 0);
}
/* 호버 상태: 반투명 파란색 배경 */
.unity-collection-view__item:hover {
background-color: rgba(0, 10, 37, 0.25);
}
/* 선택 상태: 더 진한 반투명 파란색 배경 */
.unity-collection-view__item--selected {
background-color: rgba(0, 10, 37, 0.5);
}
/* ===================================
가시성 토글 버튼 스타일
눈 아이콘으로 3D 모델 가시성 제어
=================================== */
.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('SHI/Images/icon_visibility_on_64'); /* 기본: 보이는 상태 */
}
/* 가시성 켜짐 상태 (눈 열린 아이콘) */
.visibility-on {
background-image: resource('SHI/Images/icon_visibility_on_64');
}
/* 가시성 꺼짐 상태 (눈 닫힌 아이콘) */
.visibility-off {
background-image: resource('SHI/Images/icon_visibility_off_64');
}
/* ===================================
세로 스크롤바 스타일
슬림한 6px 너비의 커스텀 스크롤바
=================================== */
.unity-scroller--vertical {
width: 6px; /* 슬림한 너비 */
margin-right: 4px;
margin-bottom: 0px;
}
/* 세로 스크롤바 트랙 (배경) */
.unity-scroller--vertical .unity-base-slider__tracker {
background-color: rgba(255, 255, 255, 0); /* 흰색 배경 */
border-width: 0;
}
/* 세로 스크롤바 드래거 컨테이너 위치 조정 */
.unity-scroller--vertical .unity-base-slider__drag-container {
left: 0;
right: 0;
}
/* 세로 스크롤바 드래거 (핸들) */
.unity-scroller--vertical .unity-base-slider__dragger {
background-color: rgb(216, 216, 216); /* 밝은 회색 */
border-width: 0;
border-radius: 3px; /* 둥근 모서리 */
width: 6px;
left: 0;
}
/* 세로 스크롤바 화살표 버튼 숨김 */
.unity-scroller--vertical .unity-repeat-button {
display: none;
width: 0;
height: 0;
min-width: 0;
min-height: 0;
}
/* 세로 스크롤바 슬라이더 마진 제거 */
.unity-scroller--vertical .unity-slider {
margin: 0;
}
/* 세로 스크롤바 입력 필드 크기 조정 */
.unity-scroller--vertical .unity-base-field__input {
width: 6px;
min-width: 6px;
}
/* ===================================
가로 스크롤바 스타일
슬림한 6px 높이의 커스텀 스크롤바
=================================== */
.unity-scroller--horizontal {
height: 6px; /* 슬림한 높이 */
margin-bottom: 4px;
margin-right: 0px;
}
/* 가로 스크롤바 트랙 (배경) */
.unity-scroller--horizontal .unity-base-slider__tracker {
background-color: rgba(255, 255, 255, 0); /* 흰색 배경 */
border-width: 0;
}
/* 가로 스크롤바 드래거 컨테이너 위치 조정 */
.unity-scroller--horizontal .unity-base-slider__drag-container {
top: 0;
bottom: 0;
}
/* 가로 스크롤바 드래거 (핸들) */
.unity-scroller--horizontal .unity-base-slider__dragger {
background-color: rgb(216, 216, 216); /* 밝은 회색 */
border-width: 0;
border-radius: 3px; /* 둥근 모서리 */
height: 6px;
top: 0;
}
/* 가로 스크롤바 화살표 버튼 숨김 */
.unity-scroller--horizontal .unity-repeat-button {
display: none;
width: 0;
height: 0;
min-width: 0;
min-height: 0;
}
/* 가로 스크롤바 슬라이더 마진 제거 */
.unity-scroller--horizontal .unity-slider {
margin: 0;
}
/* 가로 스크롤바 입력 필드 크기 조정 */
.unity-scroller--horizontal .unity-base-field__input {
height: 6px;
min-height: 6px;
}

View File

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

View File

@@ -0,0 +1,14 @@
<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/List/UIToolkitTreeList.uss?fileID=7433441132597879392&amp;guid=a1b1f50d423b463408e1f540fb4acfe9&amp;type=3#UIToolkitTreeList" />
<ui:VisualElement name="container" class="tree-menu-container">
<ui:VisualElement name="header" style="flex-direction: row; margin-bottom: 5px; justify-content: space-between;">
<ui:Label text="모델 검색" style="color: white; -unity-font-style: normal; font-size: 20px; -unity-font-definition: resource(&apos;Fonts/Pretendard/Pretendard-Bold&apos;);" />
<ui:Button name="hide-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;" />
</ui:VisualElement>
<ui:TextField name="search-field" placeholder-text="검색어를 입력하세요." class="search-field">
<ui:Button name="clear-btn" style="width: 18px; height: 18px; 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_cancel_64&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: 26px; -unity-background-image-tint-color: rgb(180, 180, 180);" />
<ui:VisualElement style="flex-grow: 1; position: absolute; right: 6px; width: 16px; height: 17px; justify-content: center; align-items: auto; align-self: center; background-image: resource(&apos;UIToolkit/Images/icon_search_16x17&apos;);" />
</ui:TextField>
<ui:TreeView name="main-tree-view" view-data-key="model-tree-view" fixed-item-height="32" auto-expand="true" item-template="project://database/Assets/Resources/UIToolkit/List/UIToolkitTreeListItem.uxml?fileID=9197481963319205126&amp;guid=c5b3acae6c4669946b6e6b8b36d82e88&amp;type=3#UIToolkitTreeListItem" horizontal-scrolling="true" style="flex-grow: 1;" />
</ui:VisualElement>
</ui:UXML>

View File

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

View File

@@ -0,0 +1,39 @@
<!--
UIToolkitTreeListItem.uxml - TreeView 개별 항목 템플릿
[개요]
UIToolkitTreeList의 각 항목(노드)을 표시하는 템플릿입니다.
UIToolkitTreeList.cs의 MakeItem()에서 이 템플릿을 복제하여 사용합니다.
[UI 구조]
item-root (tree-item-container)
├── item-label - 항목 이름 표시 라벨
└── visibility-btn - 가시성 토글 버튼 (눈 아이콘)
[바인딩]
- item-label.text : TreeListItemData.name에 바인딩
- visibility-btn.class : IsVisible 상태에 따라 visibility-on/off 클래스 토글
[이벤트]
- visibility-btn 클릭 시 해당 항목과 하위 항목의 3D 모델 가시성 토글
[스타일]
- UIToolkitTreeList.uss의 .visibility-toggle 클래스 적용
- 높이 32px 고정 (UIToolkitTreeList.uxml의 fixed-item-height와 일치)
[연관 파일]
- UIToolkitTreeList.uxml : 부모 컨테이너
- UIToolkitTreeList.uss : 스타일시트
- UIToolkitTreeList.cs : 바인딩 및 이벤트 처리
- TreeListItemData.cs : 데이터 모델
-->
<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/List/UIToolkitTreeList.uss?fileID=7433441132597879392&amp;guid=a1b1f50d423b463408e1f540fb4acfe9&amp;type=3#UIToolkitTreeList" />
<!-- 항목 루트 컨테이너: 가로 배치, 라벨과 버튼 양 끝 정렬 -->
<ui:VisualElement name="item-root" class="tree-item-container" style="flex-direction: row; justify-content: space-between; height: 32px; padding-left: 0; padding-right: 8px; align-items: center;">
<!-- 항목 이름 라벨: TreeListItemData.name에 바인딩됨 -->
<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(255, 255, 255); font-size: 14px;" />
<!-- 가시성 토글 버튼: 클릭 시 3D 모델 표시/숨김 전환 -->
<ui:Button name="visibility-btn" class="visibility-toggle" />
</ui:VisualElement>
</ui:UXML>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,623 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit.List
{
/// <summary>
/// 계층적 트리 구조를 표시하는 커스텀 UI Toolkit 컴포넌트입니다.
///
/// <para><b>개요:</b></para>
/// <para>
/// TreeList는 Unity UI Toolkit의 TreeView를 래핑하여 검색, 가시성 토글,
/// 닫기 기능 등을 제공하는 재사용 가능한 컴포넌트입니다.
/// UXML 파일(TreeList.uxml, TreeListItem.uxml)과 함께 사용됩니다.
/// </para>
///
/// <para><b>주요 기능:</b></para>
/// <list type="bullet">
/// <item>계층적 트리 구조 표시 (펼치기/접기 지원)</item>
/// <item>실시간 검색 필터링</item>
/// <item>항목별 가시성(눈 아이콘) 토글</item>
/// <item>선택 이벤트 처리</item>
/// </list>
///
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <UVC.UIToolkit.List.UIToolkitTreeList name="tree-list" />
/// </code>
///
/// <para><b>코드에서 사용:</b></para>
/// <code>
/// var treeList = root.Q<UIToolkitTreeList>();
/// treeList.OnSelectionChanged += (item) => Debug.Log($"선택: {item.name}");
/// treeList.OnVisibilityChanged += (item) => model.SetActive(item.id, item.IsVisible);
/// treeList.SetData(treeItems);
/// </code>
///
/// <para><b>관련 리소스:</b></para>
/// <list type="bullet">
/// <item>Resources/UIToolkit/List/UIToolkitTreeList.uxml - 메인 레이아웃</item>
/// <item>Resources/UIToolkit/List/UIToolkitTreeListItem.uxml - 개별 항목 템플릿</item>
/// </list>
/// </summary>
public partial class UIToolkitTreeList : VisualElement, IDisposable
{
#region IDisposable
private bool _disposed = false;
#endregion
#region (Constants)
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
private const string UXML_PATH = "SHI/Modal/TreeList";
#endregion
#region UI (UI Component References)
/// <summary>검색어 입력 필드</summary>
private TextField? _searchField;
/// <summary>Unity UI Toolkit의 TreeView 컴포넌트</summary>
private TreeView? _treeView;
/// <summary>트리 리스트 닫기 버튼</summary>
private Button? _closeButton;
/// <summary>검색어 지우기 버튼</summary>
private Button? _clearButton;
#endregion
#region (Internal Data)
/// <summary>
/// 원본 루트 데이터입니다.
/// 검색 필터 해제 시 원래 데이터로 복원하는 데 사용됩니다.
/// </summary>
private List<UIToolkitTreeListItemData> _originalRoots = new();
/// <summary>
/// TreeView에 바인딩되는 데이터 소스입니다.
/// TreeViewItemData는 Unity의 TreeView가 요구하는 래퍼 타입입니다.
/// </summary>
private List<TreeViewItemData<UIToolkitTreeListItemData>>? _rootData;
/// <summary>
/// 항목 ID 자동 생성을 위한 시드 값입니다.
/// SetData() 호출 시 id가 0인 항목에 순차적으로 ID를 할당합니다.
/// </summary>
private int _idSeed = 1;
/// <summary>
/// 이전에 선택된 항목입니다.
/// 선택 해제 이벤트 발송에 사용됩니다.
/// </summary>
private UIToolkitTreeListItemData? _previouslySelectedItem;
#endregion
#region (Public Events)
/// <summary>
/// 항목의 가시성(눈 아이콘)이 변경될 때 발생합니다.
/// 3D 모델의 GameObject 활성화/비활성화에 연동합니다.
/// </summary>
public event Action<UIToolkitTreeListItemData>? OnVisibilityChanged;
/// <summary>
/// 항목 선택 상태가 변경될 때 발생합니다.
/// 선택 및 선택 해제 모두에서 발생하며, item.isSelected로 구분합니다.
/// </summary>
public event Action<UIToolkitTreeListItemData>? OnSelectionChanged;
/// <summary>
/// 트리 리스트가 닫힐 때(숨겨질 때) 발생합니다.
/// 닫기 버튼 클릭 시 트리거됩니다.
/// </summary>
public event Action? OnClosed;
#endregion
#region (Constructor)
/// <summary>
/// TreeList 컴포넌트를 초기화합니다.
/// UXML 템플릿을 로드하고 내부 컴포넌트를 설정합니다.
/// </summary>
public TreeList()
{
// 1. 메인 UXML 로드 및 복제
// CloneTree(this)로 UXML 내용이 이 클래스의 자식으로 추가됨
var visualTree = Resources.Load<VisualTreeAsset>(UXML_PATH);
if (visualTree == null)
{
Debug.LogError($"[TreeMenu] UXML not found at: {UXML_PATH}");
return;
}
visualTree!.CloneTree(this);
// 2. 자식 요소 참조 획득 (UXML의 name 속성으로 찾음)
_searchField = this.Q<TextField>("search-field");
_treeView = this.Q<TreeView>("main-tree-view");
_closeButton = this.Q<Button>("hide-btn");
_clearButton = this.Q<Button>("clear-btn");
// 3. 이벤트 연결 및 로직 초기화
InitializeLogic();
}
#endregion
#region (Initialization)
/// <summary>
/// 내부 로직과 이벤트 핸들러를 초기화합니다.
/// </summary>
private void InitializeLogic()
{
// 검색창 이벤트: 입력 값이 변경될 때마다 필터링 실행
if (_searchField != null)
{
_searchField.RegisterValueChangedCallback(OnSearchValueChanged);
}
// TreeView 설정
// makeItem: 새 항목 UI 생성 시 호출
// bindItem: 데이터를 UI에 바인딩할 때 호출
// selectionChanged: 선택 변경 시 호출
if (_treeView != null)
{
_treeView.bindItem = BindTreeItem;
_treeView.selectionChanged += OnTreeViewSelectionChanged;
}
// 닫기 버튼: 트리 리스트를 숨기고 이벤트 발생
if (_closeButton != null)
{
_closeButton.clicked += () =>
{
this.style.display = DisplayStyle.None;
OnClosed?.Invoke();
};
}
// 검색어 지우기 버튼
if(_clearButton != null)
{
_clearButton.clicked += () =>
{
if (_searchField.value.Length > 0)
{
_searchField.value = string.Empty;
OnSearch(string.Empty);
}
};
}
}
#endregion
#region (Public Methods)
/// <summary>
/// 트리 리스트를 화면에 표시합니다.
/// </summary>
public void Show()
{
this.style.display = DisplayStyle.Flex;
}
/// <summary>
/// 트리 데이터를 설정합니다.
/// UIToolkitTreeListItemData 리스트를 받아 TreeView에 바인딩합니다.
/// </summary>
/// <param name="roots">루트 항목들의 리스트 (계층 구조의 최상위 항목들)</param>
public void SetData(List<UIToolkitTreeListItemData> roots)
{
// 원본 데이터 저장 (필터 복원용)
_originalRoots = roots ?? new List<UIToolkitTreeListItemData>();
// ID 시드 초기화
_idSeed = 1;
// 순환 참조 감지를 위한 방문 집합
var visited = new HashSet<UIToolkitTreeListItemData>();
// TreeView 형식으로 데이터 변환
_rootData = ConvertToTreeViewData(_originalRoots, visited, 0);
// TreeView에 데이터 설정
_treeView!.SetRootItems<UIToolkitTreeListItemData>(_rootData);
// UI 갱신 및 모든 항목 펼치기
_treeView!.Rebuild();
_treeView!.ExpandAll();
}
/// <summary>
/// 지정된 ID의 항목을 프로그래밍 방식으로 선택합니다.
/// </summary>
/// <param name="itemId">선택할 항목의 ID</param>
public void SelectByItemId(int itemId)
{
_treeView.SetSelection(new List<int> { itemId });
}
/// <summary>
/// 지정된 이름 목록에 해당하는 항목만 표시하고 나머지는 숨깁니다.
/// </summary>
/// <param name="items">표시할 항목들의 이름 목록</param>
/// <param name="depth">검색 깊이 (1=1뎁스 자식만, 2=2뎁스까지, 0이하=전체)</param>
public void ShowItems(List<string> items, int depth = 1)
{
if (_originalRoots == null || _originalRoots.Count == 0) return;
var visibleNames = new HashSet<string>(items ?? new List<string>());
foreach (var root in _originalRoots)
{
SetVisibilityByNames(root, visibleNames, depth, 0);
}
// UI 갱신
_treeView?.RefreshItems();
}
/// <summary>
/// 재귀적으로 항목의 가시성을 이름 목록에 따라 설정합니다.
/// </summary>
/// <param name="node">현재 노드</param>
/// <param name="visibleNames">표시할 이름 목록</param>
/// <param name="maxDepth">최대 검색 깊이 (0이하=무제한)</param>
/// <param name="currentDepth">현재 깊이</param>
private void SetVisibilityByNames(UIToolkitTreeListItemData node, HashSet<string> visibleNames, int maxDepth, int currentDepth)
{
if (node == null) return;
// maxDepth <= 0 이면 전체 검색, 아니면 깊이 제한 체크
bool isWithinDepth = maxDepth <= 0 || currentDepth < maxDepth;
if (isWithinDepth)
{
// 이름이 목록에 있으면 visible, 없으면 hidden
node.IsVisible = visibleNames.Contains(node.name);
// 가시성 변경 이벤트 발송
OnVisibilityChanged?.Invoke(node);
}
// 자식들도 재귀적으로 처리
if (node.children != null)
{
foreach (var child in node.children)
{
SetVisibilityByNames(child, visibleNames, maxDepth, currentDepth + 1);
}
}
}
#endregion
#region (Selection Handling)
/// <summary>
/// TreeView의 선택 변경 이벤트를 처리합니다.
/// 외부에 OnSelectionChanged 이벤트를 발송합니다.
/// </summary>
/// <param name="selectedItems">선택된 항목들</param>
private void OnTreeViewSelectionChanged(System.Collections.Generic.IEnumerable<object> selectedItems)
{
// 단일 선택 기준 (첫 번째 항목만 처리)
var currentItem = selectedItems.FirstOrDefault() as UIToolkitTreeListItemData;
// 이전 선택 항목이 있고 현재와 다르면 선택 해제 이벤트 발송
if (_previouslySelectedItem != null && _previouslySelectedItem != currentItem)
{
_previouslySelectedItem.isSelected = false;
OnSelectionChanged?.Invoke(_previouslySelectedItem);
}
// 현재 선택 항목이 있으면 선택 이벤트 발송
if (currentItem != null)
{
currentItem.isSelected = true;
OnSelectionChanged?.Invoke(currentItem);
}
// 현재 선택 상태 저장
_previouslySelectedItem = currentItem;
}
#endregion
#region (Data Conversion)
/// <summary>
/// UIToolkitTreeListItemData 리스트를 TreeView가 요구하는 TreeViewItemData 형식으로 변환합니다.
/// 재귀적으로 자식 항목도 변환하며, 순환 참조를 감지합니다.
/// </summary>
/// <param name="items">변환할 항목 리스트</param>
/// <param name="visited">순환 참조 감지용 방문 집합</param>
/// <param name="depth">현재 깊이 (디버깅용)</param>
/// <returns>TreeViewItemData 래퍼 리스트</returns>
private List<TreeViewItemData<UIToolkitTreeListItemData>> ConvertToTreeViewData(List<UIToolkitTreeListItemData> items, HashSet<UIToolkitTreeListItemData> visited, int depth)
{
var list = new List<TreeViewItemData<UIToolkitTreeListItemData>>(items.Count);
foreach (var item in items)
{
if (item == null) continue;
// 순환 참조 체크: 이미 방문한 항목이면 자식 무시
if (!visited.Add(item))
{
Debug.LogWarning($"[TreeList] Cycle detected at item '{item.name}' → children 무시");
item.children = null;
}
// ID가 없으면 자동 할당
if (item.id == 0) item.id = _idSeed++;
// 자식 항목 재귀 변환
List<TreeViewItemData<UIToolkitTreeListItemData>>? childData = null;
if (item.children != null && item.children.Count > 0)
childData = ConvertToTreeViewData(item.children, visited, depth + 1);
// TreeViewItemData 래퍼 생성
var treeItem = new TreeViewItemData<UIToolkitTreeListItemData>(item.id, item, childData);
list.Add(treeItem);
}
return list;
}
#endregion
#region TreeView (TreeView Item Creation/Binding)
/// <summary>
/// 데이터를 UI 요소에 바인딩합니다.
/// TreeView의 bindItem 콜백으로 사용됩니다.
/// 스크롤 시 재사용되는 항목에 새 데이터를 연결합니다.
/// </summary>
/// <param name="element">바인딩할 UI 요소</param>
/// <param name="index">TreeView 내부 인덱스</param>
private void BindTreeItem(VisualElement element, int index)
{
// 인덱스로 데이터 획득
var item = _treeView.GetItemDataForIndex<UIToolkitTreeListItemData>(index);
if (item == null) return;
// 1. 항목 이름 레이블 설정
var label = element.Q<Label>("item-label");
if (label != null) label.text = item.name;
// 2. 가시성 아이콘 버튼 설정
var toggleBtn = element.Q<Button>("visibility-btn");
if (toggleBtn != null) UpdateVisibilityIcon(toggleBtn, item.IsVisible);
// 3. 가시성 버튼 클릭 이벤트 연결
// 주의: bindItem은 스크롤 시 재호출되므로 기존 이벤트 제거 후 재등록
if (toggleBtn.userData is Action oldAction) toggleBtn.clicked -= oldAction;
System.Action clickAction = () =>
{
// 가시성 상태 토글
item.IsVisible = !item.IsVisible;
UpdateVisibilityIcon(toggleBtn, item.IsVisible);
// 자식들에게 동일 상태 전파
SetChildrenVisibility(item, item.IsVisible);
// 화면에 보이는 자식 아이콘도 갱신
_treeView.RefreshItems();
// 외부에 이벤트 발송 (3D 모델 가시성 동기화)
OnVisibilityChanged?.Invoke(item);
};
toggleBtn.userData = clickAction;
toggleBtn.clicked += clickAction;
}
#endregion
#region (Visibility Handling)
/// <summary>
/// 자식 항목들의 가시성을 재귀적으로 동기화합니다.
/// 부모의 가시성이 변경되면 모든 하위 항목에 동일하게 적용됩니다.
/// </summary>
/// <param name="node">현재 노드</param>
/// <param name="isVisible">설정할 가시성 상태</param>
private void SetChildrenVisibility(UIToolkitTreeListItemData node, bool isVisible)
{
if (node.children == null || node.children.Count == 0) return;
foreach (var child in node.children)
{
if (child == null) continue;
child.IsVisible = isVisible;
// 재귀적으로 하위 자식에도 적용
SetChildrenVisibility(child, isVisible);
}
}
/// <summary>
/// 가시성 버튼의 아이콘 스타일을 업데이트합니다.
/// USS 클래스를 토글하여 아이콘 변경을 처리합니다.
/// </summary>
/// <param name="btn">가시성 토글 버튼</param>
/// <param name="isVisible">현재 가시성 상태</param>
private void UpdateVisibilityIcon(Button btn, bool isVisible)
{
if (isVisible)
{
btn.RemoveFromClassList("visibility-off");
btn.AddToClassList("visibility-on");
}
else
{
btn.RemoveFromClassList("visibility-on");
btn.AddToClassList("visibility-off");
}
}
#endregion
#region (Search Functionality)
/// <summary>
/// 검색 필드 값 변경 콜백입니다.
/// </summary>
/// <param name="evt">값 변경 이벤트</param>
private void OnSearchValueChanged(ChangeEvent<string> evt)
{
OnSearch(evt.newValue);
}
/// <summary>
/// 검색어에 따라 트리를 필터링합니다.
/// 검색어가 비어있으면 원본 데이터로 복원됩니다.
/// </summary>
/// <param name="query">검색어</param>
private void OnSearch(string query)
{
// 검색어가 없으면 원본 데이터 복원
if (string.IsNullOrEmpty(query))
{
_treeView.SetRootItems<UIToolkitTreeListItemData>(_rootData);
_treeView.Rebuild();
return;
}
// 대소문자 무시 검색
string qLower = query.Trim().ToLowerInvariant();
var filteredWrappers = FilterTree(qLower);
// 필터링된 결과로 TreeView 갱신
_treeView.SetRootItems<UIToolkitTreeListItemData>(filteredWrappers);
_treeView.Rebuild();
// 검색 결과 모두 펼치기
ExpandAll(filteredWrappers);
}
/// <summary>
/// 모든 루트 항목에 대해 필터링을 수행합니다.
/// </summary>
/// <param name="qLower">소문자로 변환된 검색어</param>
/// <returns>필터링된 TreeViewItemData 리스트</returns>
private List<TreeViewItemData<UIToolkitTreeListItemData>> FilterTree(string qLower)
{
var result = new List<TreeViewItemData<UIToolkitTreeListItemData>>();
foreach (var root in _originalRoots)
{
TreeViewItemData<UIToolkitTreeListItemData>? filtered = FilterNode(root, qLower);
if (filtered != null) result.Add(filtered.Value);
}
return result;
}
/// <summary>
/// 단일 노드를 필터링합니다.
/// 자신 또는 자식이 검색어와 매치되면 결과에 포함됩니다.
/// </summary>
/// <param name="node">필터링할 노드</param>
/// <param name="qLower">소문자로 변환된 검색어</param>
/// <returns>매치된 경우 TreeViewItemData, 아니면 null</returns>
private TreeViewItemData<UIToolkitTreeListItemData>? FilterNode(UIToolkitTreeListItemData node, string qLower)
{
// 자기 자신이 매치되는지 확인
bool selfMatch = NodeMatches(node, qLower);
List<TreeViewItemData<UIToolkitTreeListItemData>>? childFiltered = null;
// 자식들도 재귀적으로 필터링
if (node.children != null && node.children.Count > 0)
{
foreach (var child in node.children)
{
TreeViewItemData<UIToolkitTreeListItemData>? f = FilterNode(child, qLower);
if (f != null)
{
childFiltered ??= new List<TreeViewItemData<UIToolkitTreeListItemData>>();
childFiltered.Add(f.Value);
}
}
}
// 자신이 매치되거나 매치된 자식이 있으면 포함
if (selfMatch || (childFiltered != null && childFiltered.Count > 0))
{
return new TreeViewItemData<UIToolkitTreeListItemData>(node.id, node, childFiltered);
}
return null;
}
/// <summary>
/// 노드가 검색어와 매치되는지 확인합니다.
/// name, option, ExternalKey 필드를 검색 대상으로 합니다.
/// </summary>
/// <param name="item">검사할 항목</param>
/// <param name="qLower">소문자로 변환된 검색어</param>
/// <returns>매치 여부</returns>
private bool NodeMatches(UIToolkitTreeListItemData item, string qLower)
{
if (item.name != null && item.name.ToLowerInvariant().Contains(qLower)) return true;
if (!string.IsNullOrEmpty(item.option) && item.option.ToLowerInvariant().Contains(qLower)) return true;
if (!string.IsNullOrEmpty(item.ExternalKey) && item.ExternalKey.ToLowerInvariant().Contains(qLower)) return true;
return false;
}
/// <summary>
/// 필터링된 결과의 모든 항목을 펼칩니다.
/// </summary>
/// <param name="roots">펼칠 루트 항목들</param>
private void ExpandAll(List<TreeViewItemData<UIToolkitTreeListItemData>> roots)
{
foreach (var r in roots)
ExpandRecursive(r);
}
/// <summary>
/// 노드와 그 자식들을 재귀적으로 펼칩니다.
/// </summary>
/// <param name="wrapper">펼칠 TreeViewItemData</param>
private void ExpandRecursive(TreeViewItemData<UIToolkitTreeListItemData> wrapper)
{
_treeView.ExpandItem(wrapper.id);
if (wrapper.children != null)
{
foreach (var c in wrapper.children)
ExpandRecursive(c);
}
}
#endregion
#region IDisposable
/// <summary>
/// 리소스를 해제하고 이벤트 핸들러를 정리합니다.
/// </summary>
public void Dispose()
{
if (_disposed) return;
_disposed = true;
// 검색 필드 이벤트 해제
if (_searchField != null)
{
_searchField.UnregisterValueChangedCallback(OnSearchValueChanged);
}
// TreeView 이벤트 핸들러 해제
if (_treeView != null)
{
_treeView.selectionChanged -= OnTreeViewSelectionChanged;
_treeView.bindItem = null;
_treeView.makeItem = null;
}
// 외부 이벤트 구독자 정리
OnVisibilityChanged = null;
OnSelectionChanged = null;
OnClosed = null;
// 데이터 정리
_originalRoots.Clear();
_rootData?.Clear();
_rootData = null;
_previouslySelectedItem = null;
// ID 시드 초기화
_idSeed = 1;
// UI 참조 정리
_searchField = null;
_treeView = null;
_closeButton = null;
_clearButton = null;
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7b5f2fdf672eeec4eb39bcd609520e3c

View File

@@ -0,0 +1,121 @@
#nullable enable
using System.Collections.Generic;
namespace UVC.UIToolkit.List
{
/// <summary>
/// 트리 구조 리스트의 개별 항목 데이터를 나타내는 클래스입니다.
///
/// <para><b>개요:</b></para>
/// <para>
/// UIToolkitTreeListItemData는 계층적 트리 구조에서 각 노드(항목)를 표현합니다.
/// 부모-자식 관계를 통해 중첩된 트리 구조를 구성할 수 있으며,
/// UIToolkitTreeList(UI)에서 사용됩니다.
/// </para>
///
/// <para><b>주요 용도:</b></para>
/// <list type="bullet">
/// <item>3D glTF 모델의 계층 구조를 UI 트리로 표현</item>
/// <item>트리 리스트에서 항목 선택/확장/가시성 상태 관리</item>
/// <item>모델 뷰어와 트리 리스트 간의 데이터 동기화</item>
/// </list>
///
/// <para><b>사용 예시:</b></para>
/// <code>
/// // 루트 항목 생성
/// var root = new UIToolkitTreeListItemData { id = 1, name = "루트" };
///
/// // 자식 항목 추가
/// var child1 = new UIToolkitTreeListItemData { id = 2, name = "자식1" };
/// var child2 = new UIToolkitTreeListItemData { id = 3, name = "자식2" };
/// root.Add(child1);
/// root.Add(child2);
///
/// // 손자 항목 추가
/// var grandChild = new UIToolkitTreeListItemData { id = 4, name = "손자" };
/// child1.Add(grandChild);
/// </code>
/// </summary>
public class UIToolkitTreeListItemData
{
/// <summary>
/// 항목의 고유 식별자입니다.
/// TreeView에서 항목을 구분하고 선택하는 데 사용됩니다.
/// 일반적으로 TreeList.SetData() 호출 시 자동으로 할당됩니다.
/// </summary>
public int id = 0;
/// <summary>
/// 항목의 표시 이름입니다.
/// UI의 TreeView에서 사용자에게 보여지는 텍스트입니다.
/// glTF 모델 로드 시 GameObject 이름이 자동으로 설정됩니다.
/// </summary>
public string name = string.Empty;
/// <summary>
/// 추가 옵션 또는 설명 텍스트입니다.
/// 검색 필터링 시 name과 함께 검색 대상이 됩니다.
/// </summary>
public string option = string.Empty;
/// <summary>
/// 트리뷰에서 해당 항목이 펼쳐져 있는지 여부입니다.
/// true: 자식 항목들이 표시됨 / false: 자식 항목들이 숨겨짐
/// </summary>
public bool isExpanded = false;
/// <summary>
/// 해당 항목이 현재 선택되어 있는지 여부입니다.
/// 선택 상태가 변경되면 TreeList.OnSelectionChanged 이벤트가 발생합니다.
/// </summary>
public bool isSelected = false;
/// <summary>
/// 부모 항목에 대한 참조입니다.
/// 루트 항목의 경우 null입니다.
/// Add() 메서드로 자식을 추가하면 자동으로 설정됩니다.
/// </summary>
public UIToolkitTreeListItemData parent;
/// <summary>
/// 자식 항목들의 리스트입니다.
/// 재귀적 트리 구조를 형성합니다.
/// Add() 메서드를 통해 자식을 추가할 수 있습니다.
/// </summary>
public List<UIToolkitTreeListItemData> children = new List<UIToolkitTreeListItemData>();
/// <summary>
/// 해당 항목(및 연결된 3D 오브젝트)의 가시성 상태입니다.
/// UI의 눈 아이콘 버튼으로 토글되며,
/// 변경 시 UIToolkitTreeList.OnVisibilityChanged 이벤트를 통해
/// 3D 모델의 GameObject.SetActive()가 호출됩니다.
/// </summary>
public bool IsVisible = true;
/// <summary>
/// 외부 시스템(예: 간트 차트)과의 연동을 위한 외부 키입니다.
/// 검색 필터링 시 검색 대상에 포함됩니다.
/// 모델과 차트 간의 동기화에 사용될 수 있습니다.
/// </summary>
public string ExternalKey = string.Empty;
/// <summary>
/// 자식 항목을 추가합니다.
/// 추가되는 자식의 parent 속성이 자동으로 현재 항목으로 설정됩니다.
/// </summary>
/// <param name="child">추가할 자식 항목</param>
/// <example>
/// <code>
/// var parent = new UIToolkitTreeListItemData { name = "부모" };
/// var child = new UIToolkitTreeListItemData { name = "자식" };
/// parent.Add(child);
/// // child.parent == parent (자동 설정됨)
/// </code>
/// </example>
public void Add(UIToolkitTreeListItemData child)
{
child.parent = this;
children.Add(child);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2f4c4ff74d0c7a64d86f24f01ae94434

View File

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