UTKReorableList 개발 중

This commit is contained in:
logonkhi
2026-02-19 20:08:57 +09:00
parent 739a62eb9b
commit ad10e24d13
21 changed files with 1390 additions and 1 deletions

View File

@@ -0,0 +1,17 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ui="UnityEngine.UIElements"
xmlns:utk="UVC.UIToolkit"
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
editor-extension-mode="False">
<!-- USS는 테마 적용을 위해 C# 코드에서 로드합니다 (UXML에서 지정하지 않음) -->
<ui:VisualElement name="item-container" class="reordable-list-item">
<!-- 드래그 핸들 아이콘 (Material Icon: DragIndicator) -->
<ui:Label name="drag-handle" class="reordable-list-item__drag-handle" />
<!-- 사용 유무 체크박스 -->
<utk:UTKCheckBox name="active-checkbox" class="reordable-list-item__checkbox" />
<!-- 내용 표시/수정 입력 필드 -->
<utk:UTKInputField name="display-text-field" class="reordable-list-item__input" />
</ui:VisualElement>
</ui:UXML>

View File

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

View File

@@ -0,0 +1,56 @@
/*
* ===================================
* UTKReordableListItemUss.uss
* 재정렬 가능 리스트 아이템 스타일
* ===================================
*/
/* ===================================
Item Container
=================================== */
.reordable-list-item {
flex-direction: row;
align-items: center;
padding: var(--space-s) var(--space-m);
min-height: 36px;
}
/* ===================================
Drag Handle
=================================== */
.reordable-list-item__drag-handle {
width: 24px;
height: 24px;
-unity-font-definition: resource('Fonts/Icons/MaterialSymbolsOutlined');
font-size: 18px;
color: var(--color-text-secondary);
-unity-text-align: middle-center;
flex-shrink: 0;
margin-right: var(--space-xs);
cursor: move;
padding: 0;
}
.reordable-list-item__drag-handle:hover {
color: var(--color-text-primary);
}
/* ===================================
Checkbox
=================================== */
.reordable-list-item__checkbox {
flex-shrink: 0;
margin-right: var(--space-s);
}
/* ===================================
Input Field
=================================== */
.reordable-list-item__input {
flex-grow: 1;
min-width: 0;
}

View File

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

View File

@@ -0,0 +1,47 @@
/*
* ===================================
* UTKReordableListUss.uss
* 재정렬 가능 리스트 컨테이너 스타일
* ===================================
*/
/* ===================================
Base Container
=================================== */
.reordable-list {
flex-grow: 1;
background-color: var(--color-bg-secondary);
border-radius: var(--radius-s);
border-width: var(--border-width);
border-color: var(--color-border);
}
/* ===================================
ListView 내부 여백 조정
=================================== */
.reordable-list .utk-listview {
flex-grow: 1;
border-width: 0;
border-radius: 0;
background-color: transparent;
}
/* ===================================
드래그 중 아이템 스타일 (Unity 내장 클래스)
=================================== */
.reordable-list .unity-list-view__reorderable-item__container {
flex-direction: row;
align-items: center;
}
/* ===================================
드래그 핸들 바 (Unity 내장 reorder handle)
커스텀 드래그 핸들 사용하므로 기본 숨김
=================================== */
.reordable-list .unity-list-view__reorderable-handle-bar {
display: none;
}

View File

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

View File

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

View File

@@ -0,0 +1,47 @@
/*
* ===================================
* UTKSettingDisplayInfoTabViewUss.uss
* 설정 표시 정보 탭 뷰 컨테이너 스타일
* ===================================
*/
/* ===================================
Base Container
=================================== */
.setting-display-tab-view {
flex-grow: 1;
background-color: var(--color-bg-secondary);
border-radius: var(--radius-s);
border-width: var(--border-width);
border-color: var(--color-border);
}
/* ===================================
ListView 내부 여백 조정
=================================== */
.setting-display-tab-view .utk-listview {
flex-grow: 1;
border-width: 0;
border-radius: 0;
background-color: transparent;
}
/* ===================================
드래그 중 아이템 스타일 (Unity 내장 클래스)
=================================== */
.setting-display-tab-view .unity-list-view__reorderable-item__container {
flex-direction: row;
align-items: center;
}
/* ===================================
드래그 핸들 바 (Unity 내장 reorder handle)
커스텀 드래그 핸들 사용하므로 기본 숨김
=================================== */
.setting-display-tab-view .unity-list-view__reorderable-handle-bar {
display: none;
}

View File

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

View File

@@ -0,0 +1,497 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 10
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 13
m_BakeOnSceneLoad: 0
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 1
m_PVRFilteringGaussRadiusAO: 1
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 3
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
buildHeightMesh: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &1097328750
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1097328752}
- component: {fileID: 1097328754}
- component: {fileID: 1097328755}
m_Layer: 0
m_Name: Sample
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1097328752
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1097328750}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1097328754
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1097328750}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0}
m_Name:
m_EditorClassIdentifier: UnityEngine.dll::UnityEngine.UIElements.UIDocument
m_PanelSettings: {fileID: 11400000, guid: 5ad7007b08a97b54d927c352279a18b6, type: 2}
m_ParentUI: {fileID: 0}
sourceAsset: {fileID: 9197481963319205126, guid: 54e4f33c8b08cb54f97dbdb5edd79e1e, type: 3}
m_SortingOrder: 1
m_Position: 0
m_WorldSpaceSizeMode: 1
m_WorldSpaceWidth: 1920
m_WorldSpaceHeight: 1080
m_PivotReferenceSize: 0
m_Pivot: 0
m_WorldSpaceCollider: {fileID: 0}
--- !u!114 &1097328755
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1097328750}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 39265a781c40bdb4a90aa56b0fbf44a6, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::UVC.Sample.UIToolkit.UTKToolBarSample
_uiDocument: {fileID: 1097328754}
initialTheme: 0
--- !u!1 &1331954412
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1331954415}
- component: {fileID: 1331954414}
- component: {fileID: 1331954413}
m_Layer: 0
m_Name: EventSystem
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1331954413
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1331954412}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 01614664b831546d2ae94a42149d80ac, type: 3}
m_Name:
m_EditorClassIdentifier:
m_SendPointerHoverToParent: 1
m_MoveRepeatDelay: 0.5
m_MoveRepeatRate: 0.1
m_XRTrackingOrigin: {fileID: 0}
m_ActionsAsset: {fileID: -944628639613478452, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_PointAction: {fileID: -1654692200621890270, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_MoveAction: {fileID: -8784545083839296357, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_SubmitAction: {fileID: 392368643174621059, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_CancelAction: {fileID: 7727032971491509709, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_LeftClickAction: {fileID: 3001919216989983466, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_MiddleClickAction: {fileID: -2185481485913320682, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_RightClickAction: {fileID: -4090225696740746782, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_ScrollWheelAction: {fileID: 6240969308177333660, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_TrackedDevicePositionAction: {fileID: 6564999863303420839, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_TrackedDeviceOrientationAction: {fileID: 7970375526676320489, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
m_DeselectOnBackgroundClick: 0
m_PointerBehavior: 0
m_CursorLockBehavior: 0
m_ScrollDeltaPerTick: 6
--- !u!114 &1331954414
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1331954412}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
m_Name:
m_EditorClassIdentifier:
m_FirstSelected: {fileID: 0}
m_sendNavigationEvents: 1
m_DragThreshold: 10
--- !u!4 &1331954415
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1331954412}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1414861612
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1414861614}
- component: {fileID: 1414861613}
- component: {fileID: 1414861615}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!108 &1414861613
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1414861612}
m_Enabled: 1
serializedVersion: 11
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize: 10
m_Shadows:
m_Type: 2
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!4 &1414861614
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1414861612}
serializedVersion: 2
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
--- !u!114 &1414861615
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1414861612}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
m_Name:
m_EditorClassIdentifier:
m_UsePipelineSettings: 1
m_AdditionalLightsShadowResolutionTier: 2
m_CustomShadowLayers: 0
m_LightCookieSize: {x: 1, y: 1}
m_LightCookieOffset: {x: 0, y: 0}
m_SoftShadowQuality: 0
m_RenderingLayersMask:
serializedVersion: 0
m_Bits: 1
m_ShadowRenderingLayersMask:
serializedVersion: 0
m_Bits: 1
m_Version: 4
m_LightLayerMask: 1
m_ShadowLayerMask: 1
m_RenderingLayers: 1
m_ShadowRenderingLayers: 1
--- !u!1 &2136621999
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2136622002}
- component: {fileID: 2136622001}
- component: {fileID: 2136622000}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &2136622000
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2136621999}
m_Enabled: 1
--- !u!20 &2136622001
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2136621999}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_Iso: 200
m_ShutterSpeed: 0.005
m_Aperture: 16
m_FocusDistance: 10
m_FocalLength: 50
m_BladeCount: 5
m_Curvature: {x: 2, y: 11}
m_BarrelClipping: 0.25
m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &2136622002
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2136621999}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 2136622002}
- {fileID: 1414861614}
- {fileID: 1331954415}
- {fileID: 1097328752}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7cce97e0d24c7794da00973b5ead99be
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,59 @@
#nullable enable
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit;
using UVC.UI.Commands;
using UVC.Log;
namespace UVC.Sample.UIToolkit
{
/// <summary>
/// UTKSettingModalSample 독립 실행 샘플 코드입니다.
/// </summary>
public class UTKSettingModalSample : MonoBehaviour
{
[SerializeField] private UIDocument? _uiDocument;
[SerializeField]
[Tooltip("시작 시 적용할 테마")]
private UTKTheme initialTheme = UTKTheme.Dark;
private UTKToggle? _themeToggle;
private VisualElement? _root;
private void Start()
{
// UIDocument 참조 확인
var doc = GetComponent<UIDocument>();
if (doc == null)
{
Debug.LogError("UIDocument가 할당되지 않았습니다.");
return;
}
_uiDocument = doc;
_root = _uiDocument.rootVisualElement;
UTKThemeManager.Instance.RegisterRoot(_root);
UTKThemeManager.Instance.SetTheme(initialTheme);
// 테마 토글
_themeToggle = _root.Q<UTKToggle>("toggle");
if (_themeToggle != null)
{
_themeToggle.OnValueChanged += (isOn) =>
{
UTKThemeManager.Instance.SetTheme(!isOn ? UTKTheme.Dark : UTKTheme.Light);
};
}
}
private void OnDestroy()
{
ULog.Debug("UTKSettingModalSample 정리 완료");
}
}
}

View File

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

View File

@@ -0,0 +1,6 @@
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
<VisualElement style="width: 100%; height: 100%; flex-direction: column;">
<VisualElement name="toolbar-area" style="flex-grow: 1; flex-direction: column; padding: 8px;" />
<utk:UTKToggle name="toggle" label="테마 변경" style="position: absolute; top: 8px; right: 10px; z-index: 10;" />
</VisualElement>
</UXML>

View File

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

View File

@@ -0,0 +1,383 @@
#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 설정 표시 정보 아이템 데이터.
/// </summary>
public class ReordableListItemData
{
/// <summary>표시 순서 (ListView 인덱스 기준)</summary>
public int Order { get; set; }
/// <summary>사용 유무</summary>
public bool IsActive { get; set; }
/// <summary>표시 내용</summary>
public string DisplayText { get; set; } = "";
}
/// <summary>
/// 설정 표시 정보 탭 뷰.
/// UTKListView를 활용하여 마우스 드래그로 항목 순서를 변경할 수 있는 설정 목록 뷰입니다.
/// 각 항목은 드래그 핸들, 체크박스(사용 유무), 입력 필드(내용 수정)로 구성됩니다.
/// </summary>
[UxmlElement]
public partial class UTKReordableList : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/List/UTKReordableListUss";
private const string ITEM_UXML_PATH = "UIToolkit/List/UTKReordableListItem";
private const string ITEM_USS_PATH = "UIToolkit/List/UTKReordableListItemUss";
private const float ITEM_HEIGHT = 36f;
#endregion
#region Fields
private bool _disposed;
private UTKListView? _listView;
private List<ReordableListItemData> _items = new();
private VisualTreeAsset? _itemTemplate;
private StyleSheet? _itemStyleSheet;
#endregion
#region Events
/// <summary>순서 변경 시 발생</summary>
public event Action? OnOrderChanged;
/// <summary>데이터(체크/텍스트) 변경 시 발생</summary>
public event Action? OnDataChanged;
#endregion
#region Constructor
public UTKReordableList() : base()
{
// 1. 테마 적용
UTKThemeManager.Instance.ApplyThemeToElement(this);
// 2. USS 로드
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 3. UI 생성
CreateUI();
// 4. 테마 변경 구독
SubscribeToThemeChanges();
}
#endregion
#region Setup
private void CreateUI()
{
AddToClassList("reordable-list");
SetupListView();
}
private void SetupListView()
{
_listView = new UTKListView();
_listView.makeItem = MakeItem;
_listView.bindItem = BindItem;
_listView.unbindItem = UnbindItem;
_listView.fixedItemHeight = ITEM_HEIGHT;
_listView.selectionType = SelectionType.Single;
_listView.reorderable = true;
_listView.reorderMode = ListViewReorderMode.Animated;
_listView.itemIndexChanged += OnItemIndexChanged;
Add(_listView);
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
}
private void OnAttachToPanelForTheme(AttachToPanelEvent evt)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
private void OnDetachFromPanelForTheme(DetachFromPanelEvent evt)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region ListView Callbacks
private VisualElement MakeItem()
{
// UXML 캐싱
_itemTemplate ??= Resources.Load<VisualTreeAsset>(ITEM_UXML_PATH);
// USS 캐싱 (코드에서 로드 - UXML에서 지정하지 않음)
_itemStyleSheet ??= Resources.Load<StyleSheet>(ITEM_USS_PATH);
if (_itemTemplate != null)
{
var root = _itemTemplate.Instantiate();
// USS 적용 (코드에서 적용)
if (_itemStyleSheet != null)
root.styleSheets.Add(_itemStyleSheet);
// 드래그 핸들 아이콘 설정
var handle = root.Q<Label>("drag-handle");
if (handle != null)
handle.text = UTKMaterialIcons.DragIndicator;
return root;
}
return CreateItemFallback();
}
private VisualElement CreateItemFallback()
{
var container = new VisualElement();
container.name = "item-container";
container.AddToClassList("reordable-list-item");
container.style.flexDirection = FlexDirection.Row;
container.style.alignItems = Align.Center;
container.style.minHeight = ITEM_HEIGHT;
var handle = new Label(UTKMaterialIcons.DragIndicator);
handle.name = "drag-handle";
handle.AddToClassList("reordable-list-item__drag-handle");
container.Add(handle);
var checkbox = new UTKCheckBox();
checkbox.name = "active-checkbox";
checkbox.AddToClassList("reordable-list-item__checkbox");
container.Add(checkbox);
var inputField = new UTKInputField();
inputField.name = "display-text-field";
inputField.AddToClassList("reordable-list-item__input");
container.Add(inputField);
return container;
}
private void BindItem(VisualElement element, int index)
{
if (index < 0 || index >= _items.Count) return;
var data = _items[index];
// 요소 참조
var container = element.Q<VisualElement>("item-container");
var checkbox = container?.Q<UTKCheckBox>("active-checkbox");
var inputField = container?.Q<UTKInputField>("display-text-field");
if (checkbox == null || inputField == null) return;
// 이전 콜백 해제 (재바인딩 시 중복 방지)
CleanupItemCallbacks(element);
// 값 설정 (notify: false로 이벤트 발생 방지)
checkbox.SetChecked(data.IsActive, notify: false);
inputField.SetValue(data.DisplayText, notify: false);
// 새 콜백 등록
Action<bool> onChecked = (value) =>
{
data.IsActive = value;
OnDataChanged?.Invoke();
};
Action<string> onText = (value) =>
{
data.DisplayText = value;
OnDataChanged?.Invoke();
};
checkbox.OnValueChanged += onChecked;
inputField.OnValueChanged += onText;
// 콜백 정보 저장 (해제용)
element.userData = new ItemCallbackInfo(checkbox, inputField, onChecked, onText);
}
private void UnbindItem(VisualElement element, int index)
{
CleanupItemCallbacks(element);
}
private void CleanupItemCallbacks(VisualElement element)
{
if (element.userData is ItemCallbackInfo info)
{
info.Checkbox.OnValueChanged -= info.OnCheckedHandler;
info.InputField.OnValueChanged -= info.OnTextHandler;
element.userData = null;
}
}
private void OnItemIndexChanged(int oldIndex, int newIndex)
{
// ListView가 내부적으로 itemsSource 순서를 변경함
// Order 값만 현재 인덱스에 맞게 재계산
SyncOrderValues();
OnOrderChanged?.Invoke();
}
private void SyncOrderValues()
{
for (int i = 0; i < _items.Count; i++)
{
_items[i].Order = i;
}
}
#endregion
#region Public API
/// <summary>
/// 데이터를 설정하고 ListView를 갱신합니다.
/// </summary>
/// <param name="items">표시할 아이템 데이터 목록.</param>
public void SetData(List<ReordableListItemData> items)
{
_items = items ?? new List<ReordableListItemData>();
SyncOrderValues();
if (_listView != null)
{
_listView.itemsSource = _items;
_listView.RefreshItems();
}
}
/// <summary>
/// List&lt;Dictionary&gt;로부터 데이터를 변환하여 설정합니다.
/// Dictionary 키: "order" (순서), "active" (사용 유무), "text" (표시 내용)
/// </summary>
/// <param name="listDict">변환할 Dictionary 목록.</param>
public void SetData(List<Dictionary<string, string>> listDict)
{
var items = new List<ReordableListItemData>();
if (listDict != null)
{
for (int i = 0; i < listDict.Count; i++)
{
var dict = listDict[i];
var item = new ReordableListItemData();
item.Order = dict.TryGetValue("order", out var orderStr) && int.TryParse(orderStr, out var order)
? order
: i;
item.IsActive = dict.TryGetValue("active", out var activeStr) && bool.TryParse(activeStr, out var active)
? active
: true;
item.DisplayText = dict.TryGetValue("text", out var text)
? text ?? ""
: "";
items.Add(item);
}
}
SetData(items);
}
/// <summary>
/// Order 값이 동기화된 데이터 목록을 반환합니다.
/// </summary>
/// <returns>현재 ListView 순서가 반영된 데이터 목록.</returns>
public List<ReordableListItemData> GetData()
{
SyncOrderValues();
return new List<ReordableListItemData>(_items);
}
/// <summary>
/// 전체 아이템을 List&lt;Dictionary&lt;string, string&gt;&gt;로 변환하여 반환합니다.
/// Dictionary 키: "order" (순서), "active" (사용 유무), "text" (표시 내용)
/// </summary>
/// <returns>각 아이템을 Dictionary로 변환한 목록.</returns>
public List<Dictionary<string, string>> ToDictionary()
{
SyncOrderValues();
var result = new List<Dictionary<string, string>>(_items.Count);
foreach (var item in _items)
{
result.Add(new Dictionary<string, string>
{
["order"] = item.Order.ToString(),
["active"] = item.IsActive.ToString(),
["text"] = item.DisplayText
});
}
return result;
}
#endregion
#region Internal Types
/// <summary>바인딩 콜백 참조 추적</summary>
private class ItemCallbackInfo
{
public readonly UTKCheckBox Checkbox;
public readonly UTKInputField InputField;
public readonly Action<bool> OnCheckedHandler;
public readonly Action<string> OnTextHandler;
public ItemCallbackInfo(
UTKCheckBox checkbox,
UTKInputField inputField,
Action<bool> onCheckedHandler,
Action<string> onTextHandler)
{
Checkbox = checkbox;
InputField = inputField;
OnCheckedHandler = onCheckedHandler;
OnTextHandler = onTextHandler;
}
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
// 테마 구독 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
// ListView 이벤트 해제
if (_listView != null)
{
_listView.itemIndexChanged -= OnItemIndexChanged;
_listView.Dispose();
}
// 이벤트 정리
OnOrderChanged = null;
OnDataChanged = null;
// 캐시 정리
_itemTemplate = null;
_itemStyleSheet = null;
}
#endregion
}
}

View File

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

View File

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

View File

@@ -0,0 +1,193 @@
#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 설정 표시 정보 탭 뷰.
/// UTKReordableList를 사용하여 표시 항목의 순서, 사용 유무, 내용을 관리합니다.
/// </summary>
[UxmlElement]
public partial class UTKSettingDisplayInfoTabView : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Modal/Setting/UTKSettingDisplayInfoTabViewUss";
#endregion
#region Fields
private bool _disposed;
private UTKReordableList? _reordableList;
#endregion
#region Events
/// <summary>순서 변경 시 발생</summary>
public event Action? OnOrderChanged;
/// <summary>데이터(체크/텍스트) 변경 시 발생</summary>
public event Action? OnDataChanged;
#endregion
#region Constructor
public UTKSettingDisplayInfoTabView() : base()
{
// 1. 테마 적용
UTKThemeManager.Instance.ApplyThemeToElement(this);
// 2. USS 로드
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 3. UI 생성
CreateUI();
// 4. 테마 변경 구독
SubscribeToThemeChanges();
SampleSetAndGetWithDictionary();
}
#endregion
#region Setup
private void CreateUI()
{
AddToClassList("setting-display-tab-view");
_reordableList = new UTKReordableList();
_reordableList.style.flexGrow = 1;
_reordableList.OnOrderChanged += () => OnOrderChanged?.Invoke();
_reordableList.OnDataChanged += () => OnDataChanged?.Invoke();
Add(_reordableList);
// 하단 버튼 영역
var buttonContainer = new VisualElement();
buttonContainer.style.flexDirection = FlexDirection.Row;
buttonContainer.style.justifyContent = Justify.FlexEnd;
buttonContainer.style.paddingTop = 8;
var saveBtn = new UTKButton("저장", variant: UTKButton.ButtonVariant.Primary);
saveBtn.OnClicked += OnSaveButtonClicked;
buttonContainer.Add(saveBtn);
Add(buttonContainer);
}
private void OnSaveButtonClicked()
{
List<Dictionary<string, string>> result = ToDictionary();
foreach (var dict in result)
{
Debug.Log($"order={dict["order"]}, active={dict["active"]}, text={dict["text"]}");
}
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
}
private void OnAttachToPanelForTheme(AttachToPanelEvent evt)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
private void OnDetachFromPanelForTheme(DetachFromPanelEvent evt)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region Public API
/// <summary>
/// 데이터를 설정하고 리스트를 갱신합니다.
/// </summary>
/// <param name="items">표시할 아이템 데이터 목록.</param>
public void SetData(List<ReordableListItemData> items)
{
_reordableList?.SetData(items);
}
/// <summary>
/// List&lt;Dictionary&gt;로부터 데이터를 변환하여 설정합니다.
/// Dictionary 키: "order" (순서), "active" (사용 유무), "text" (표시 내용)
/// </summary>
/// <param name="listDict">변환할 Dictionary 목록.</param>
public void SetData(List<Dictionary<string, string>> listDict)
{
_reordableList?.SetData(listDict);
}
/// <summary>
/// Order 값이 동기화된 데이터 목록을 반환합니다.
/// </summary>
/// <returns>현재 리스트 순서가 반영된 데이터 목록.</returns>
public List<ReordableListItemData> GetData()
{
return _reordableList?.GetData() ?? new List<ReordableListItemData>();
}
/// <summary>
/// 전체 아이템을 List&lt;Dictionary&lt;string, string&gt;&gt;로 변환하여 반환합니다.
/// Dictionary 키: "order" (순서), "active" (사용 유무), "text" (표시 내용)
/// </summary>
/// <returns>각 아이템을 Dictionary로 변환한 목록.</returns>
public List<Dictionary<string, string>> ToDictionary()
{
return _reordableList?.ToDictionary() ?? new List<Dictionary<string, string>>();
}
#endregion
#region Sample
/// <summary>
/// Dictionary 기반 데이터 설정 및 조회 샘플.
/// </summary>
public void SampleSetAndGetWithDictionary()
{
// 1. Dictionary 데이터로 설정
var listDict = new List<Dictionary<string, string>>
{
new() { ["order"] = "0", ["active"] = "True", ["text"] = "온도" },
new() { ["order"] = "1", ["active"] = "False", ["text"] = "습도" },
new() { ["order"] = "2", ["active"] = "True", ["text"] = "압력" },
};
SetData(listDict);
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
// 테마 구독 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
// ReordableList 정리
_reordableList?.Dispose();
_reordableList = null;
// 이벤트 정리
OnOrderChanged = null;
OnDataChanged = null;
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 69a4b7a0f627e79418a0ff355aab75d4