UTKToolBar 완료

This commit is contained in:
logonkhi
2026-02-20 19:17:36 +09:00
parent ad10e24d13
commit b64c3e10bc
26 changed files with 882 additions and 153 deletions

View File

@@ -12,7 +12,6 @@
.utk-checkbox {
flex-direction: row;
align-items: center;
align-self: flex-start;
cursor: resource('UIToolkit/Images/cursor_point_white_32') 14 5;
}

View File

@@ -20,7 +20,7 @@
-->
<ui:VisualElement name="group-header" class="utk-property-group__header">
<utk:UTKLabel name="expand-icon" class="utk-property-group__expand-icon" />
<utk:UTKLabel name="group-title" class="utk-property-group__title" />
<utk:UTKLabel name="group-title" class="utk-property-group__title" size="Label2" is-bold="true" />
<!-- <utk:UTKLabel name="group-count" class="utk-property-group__count" /> -->
</ui:VisualElement>
</ui:UXML>

View File

@@ -6,7 +6,7 @@
<!-- 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:UTKLabel name="drag-handle" class="reordable-list-item__drag-handle" />
<!-- 사용 유무 체크박스 -->
<utk:UTKCheckBox name="active-checkbox" class="reordable-list-item__checkbox" />

View File

@@ -14,6 +14,7 @@
align-items: center;
padding: var(--space-s) var(--space-m);
min-height: 36px;
flex-grow: 1;
}
/* ===================================

View File

@@ -10,11 +10,12 @@
=================================== */
.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);
padding-top: var(--space-s);
padding-bottom: var(--space-s);
}
/* ===================================
@@ -32,9 +33,15 @@
드래그 중 아이템 스타일 (Unity 내장 클래스)
=================================== */
.reordable-list .unity-list-view__reorderable-item {
justify-content: center;
}
.reordable-list .unity-list-view__reorderable-item__container {
flex-direction: row;
align-items: center;
padding-left: 0;
padding-right: 0;
}
/* ===================================
@@ -42,6 +49,13 @@
커스텀 드래그 핸들 사용하므로 기본 숨김
=================================== */
.reordable-list .unity-list-view__reorderable-handle {
display: none;
width: 0;
min-width: 0;
max-width: 0;
}
.reordable-list .unity-list-view__reorderable-handle-bar {
display: none;
}

View File

@@ -159,6 +159,7 @@ ListView/TreeView 항목 텍스트 스타일
.unity-list-view__item .unity-text-element {
color: var(--color-text-primary) ;
font-size: var(--font-size-body2) ;
margin: 0;
}
.unity-collection-view__item--selected,

View File

@@ -1,6 +1,6 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements">
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
<ui:VisualElement name="button-root" class="utk-toolbar-btn">
<ui:Label name="icon" class="utk-toolbar-btn__icon" />
<ui:Label name="label" class="utk-toolbar-btn__label" />
<utk:UTKLabel name="icon" class="utk-toolbar-btn__icon" />
<utk:UTKLabel name="label" class="utk-toolbar-btn__label" />
</ui:VisualElement>
</ui:UXML>

View File

@@ -51,7 +51,6 @@
font-size: var(--font-size-label4);
color: var(--color-text-secondary);
-unity-text-align: upper-center;
margin-top: 1px;
display: none;
cursor: resource('UIToolkit/Images/cursor_point_white_32') 14 5;
}

View File

@@ -1,7 +1,7 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements">
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
<ui:VisualElement name="button-root" class="utk-toolbar-btn utk-toolbar-expandable">
<ui:Label name="icon" class="utk-toolbar-btn__icon" />
<ui:Label name="label" class="utk-toolbar-btn__label" />
<utk:UTKLabel name="icon" class="utk-toolbar-btn__icon" />
<utk:UTKLabel name="label" class="utk-toolbar-btn__label" />
<ui:VisualElement name="arrow" class="utk-toolbar-expandable__arrow" />
</ui:VisualElement>
</ui:UXML>

View File

@@ -10,25 +10,31 @@
border-color: var(--color-border);
border-radius: var(--radius-m);
padding: var(--space-xs);
min-width: 120px;
min-width: 40px;
}
.utk-toolbar-submenu__container {
flex-direction: column;
}
.utk-toolbar-submenu TemplateContainer {
flex-grow: 1;
align-items: stretch;
}
/* 서브 메뉴 내 버튼은 가로로 펼침 */
.utk-toolbar-submenu .utk-toolbar-btn {
flex-direction: row;
min-width: 100px;
min-width: 28px;
min-height: 28px;
justify-content: flex-start;
padding: var(--space-xs) var(--space-m);
padding: var(--space-xs) 0;
margin: 1px 0;
flex-grow: 1;
}
.utk-toolbar-submenu .utk-toolbar-btn__icon {
margin-right: var(--space-s);
flex-grow: 1;
}
.utk-toolbar-submenu .utk-toolbar-btn__label {
@@ -36,4 +42,7 @@
font-size: var(--font-size-body2);
color: var(--color-text-primary);
-unity-text-align: middle-left;
margin-right: var(--space-m);
flex-grow: 100;
justify-content: flex-start;
}

View File

@@ -1,6 +1,6 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements">
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
<ui:VisualElement name="button-root" class="utk-toolbar-btn utk-toolbar-toggle">
<ui:Label name="icon" class="utk-toolbar-btn__icon" />
<ui:Label name="label" class="utk-toolbar-btn__label" />
<utk:UTKLabel name="icon" class="utk-toolbar-btn__icon" />
<utk:UTKLabel name="label" class="utk-toolbar-btn__label" />
</ui:VisualElement>
</ui:UXML>

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: 4bb0d1734d5c1b647ae0ffdb7879a92a, type: 3}
m_SortingOrder: 0
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: 4274ed098fc4bf048bb92836e8982c8f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::UTKReordableListSample
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: 8b1b2ce854a8a3d47ac1fc46dfaf615f
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,152 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.UIToolkit;
/// <summary>
/// UTKReordableList의 기능을 테스트하기 위한 샘플 MonoBehaviour입니다.
/// Dictionary 기반 SetData/ToDictionary, 이벤트 핸들러, 데이터 CRUD를 확인합니다.
/// </summary>
public class UTKReordableListSample : MonoBehaviour
{
[SerializeField]
public UIDocument uiDocument;
[SerializeField]
[Tooltip("시작 시 적용할 테마")]
private UTKTheme initialTheme = UTKTheme.Dark;
private UTKToggle _themeToggle;
private UTKReordableList _reordableList;
void Start()
{
// UIDocument 참조 확인
var doc = GetComponent<UIDocument>();
if (doc == null)
{
Debug.LogError("UIDocument가 할당되지 않았습니다.");
return;
}
uiDocument = doc;
var root = uiDocument.rootVisualElement;
// 테마 토글
_themeToggle = root.Q<UTKToggle>("toggle");
if (_themeToggle == null)
{
Debug.LogError("UXML에서 UTKToggle을 찾을 수 없습니다.");
return;
}
// ReordableList
_reordableList = root.Q<UTKReordableList>("window");
if (_reordableList == null)
{
Debug.LogError("UXML에서 UTKReordableList를 찾을 수 없습니다.");
return;
}
// 테마 초기화
UTKThemeManager.Instance.RegisterRoot(root);
UTKThemeManager.Instance.SetTheme(initialTheme);
_themeToggle.OnValueChanged += (isOn) =>
{
UTKThemeManager.Instance.SetTheme(!isOn ? UTKTheme.Dark : UTKTheme.Light);
};
// 이벤트 핸들러 등록
_reordableList.OnOrderChanged += () => Debug.Log("[Sample] 순서 변경됨");
_reordableList.OnDataChanged += () => Debug.Log("[Sample] 데이터 변경됨");
// 샘플 데이터 설정 (Dictionary 방식)
SetSampleData();
// 하단 버튼 영역 생성
CreateButtons(root);
}
/// <summary>
/// Dictionary 기반으로 샘플 데이터를 설정합니다.
/// </summary>
private void SetSampleData()
{
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"] = "압력" },
new() { ["order"] = "3", ["active"] = "True", ["text"] = "풍속" },
new() { ["order"] = "4", ["active"] = "False", ["text"] = "조도" },
};
_reordableList.SetData(listDict);
}
/// <summary>
/// 테스트 버튼들을 생성합니다.
/// </summary>
private void CreateButtons(VisualElement root)
{
var buttonContainer = new VisualElement();
buttonContainer.style.flexDirection = FlexDirection.Row;
buttonContainer.style.justifyContent = Justify.Center;
buttonContainer.style.paddingTop = 8;
buttonContainer.style.paddingBottom = 8;
// ToDictionary 버튼
var toDictBtn = new UTKButton("ToDictionary", variant: UTKButton.ButtonVariant.Primary);
toDictBtn.OnClicked += OnToDictionaryClicked;
toDictBtn.style.marginRight = 4;
buttonContainer.Add(toDictBtn);
// 데이터 리셋 버튼
var resetBtn = new UTKButton("리셋", variant: UTKButton.ButtonVariant.Normal);
resetBtn.OnClicked += () => SetSampleData();
resetBtn.style.marginRight = 4;
buttonContainer.Add(resetBtn);
// 아이템 추가 버튼
var addBtn = new UTKButton("추가", variant: UTKButton.ButtonVariant.OutlinePrimary);
addBtn.OnClicked += OnAddItemClicked;
buttonContainer.Add(addBtn);
root.Add(buttonContainer);
}
/// <summary>
/// ToDictionary를 호출하여 현재 데이터를 콘솔에 출력합니다.
/// </summary>
private void OnToDictionaryClicked()
{
var result = _reordableList.ToDictionary();
Debug.Log($"[Sample] ToDictionary 결과 ({result.Count}건):");
foreach (var dict in result)
{
Debug.Log($" order={dict["order"]}, active={dict["active"]}, text={dict["text"]}");
}
}
/// <summary>
/// 새 아이템을 추가합니다.
/// </summary>
private void OnAddItemClicked()
{
var currentData = _reordableList.ToDictionary();
var newIndex = currentData.Count;
currentData.Add(new Dictionary<string, string>
{
["order"] = newIndex.ToString(),
["active"] = "True",
["text"] = $"항목 {newIndex}"
});
_reordableList.SetData(currentData);
Debug.Log($"[Sample] 아이템 추가됨 (총 {currentData.Count}건)");
}
private void OnDestroy()
{
_reordableList?.Dispose();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4274ed098fc4bf048bb92836e8982c8f

View File

@@ -0,0 +1,6 @@
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
<VisualElement style="width: 100%; height: 100%;">
<utk:UTKReordableList name="window" style="width: 300px; height: 200px;" />
<utk:UTKToggle name="toggle" label="테마 변경" style="position: absolute; top: 10px; right: 10px;" />
</VisualElement>
</UXML>

View File

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

View File

@@ -371,14 +371,11 @@ public async UniTask SaveDataAsync()
}
SetCodeSamples(root,
csharpCode: @"// 1. 초기화 (앱 시작 시 한 번)
UTKTooltipManager.Instance.Initialize(rootVisualElement);
// 2. 버튼에 툴팁 연결
csharpCode: @"// 1. 버튼에 툴팁 연결
var saveButton = new UTKButton("""", UTKMaterialIcons.Save);
UTKTooltipManager.Instance.AttachTooltip(saveButton, ""저장 (Ctrl+S)"");
// 3. 다국어 키로 툴팁 연결
// 2. 다국어 키로 툴팁 연결
UTKTooltipManager.Instance.AttachTooltip(settingsButton, ""tooltip_settings"");
// 4. 아이콘 버튼에 툴팁

View File

@@ -205,7 +205,6 @@ public partial class UTKStyleGuideSample : MonoBehaviour
}
UTKThemeManager.Instance.SetTheme(initialTheme);
UTKTooltipManager.Instance.Initialize(_root);
CreateUI();
}

View File

@@ -84,7 +84,7 @@ namespace UVC.Sample.UIToolkit
/// </summary>
private void CreateHorizontalToolBar(VisualElement parent)
{
var label = new Label("Horizontal Toolbar");
var label = new Label("Horizontal Toolbar");
label.style.fontSize = 14;
label.style.marginTop = 8;
label.style.marginBottom = 4;
@@ -93,30 +93,30 @@ namespace UVC.Sample.UIToolkit
_horizontalModel = new UTKToolBarModel();
// Standard
_horizontalModel.AddStandardButton("저장", UTKMaterialIcons.Save, new DebugLogCommand("저장"), "파일 저장");
_horizontalModel.AddStandardButton("실행 취소", UTKMaterialIcons.Undo, new DebugLogCommand("실행 취소"));
_horizontalModel.AddStandardButton("다시 실행", UTKMaterialIcons.Redo, new DebugLogCommand("다시 실행"));
_horizontalModel.AddStandardButton("", UTKMaterialIcons.Save, new DebugLogCommand("저장"), "파일 저장");
_horizontalModel.AddStandardButton("", UTKMaterialIcons.Undo, new DebugLogCommand("실행 취소"));
_horizontalModel.AddStandardButton("", UTKMaterialIcons.Redo, new DebugLogCommand("다시 실행"));
_horizontalModel.AddSeparator();
// Toggle
_horizontalModel.AddToggleButton("그리드", false, UTKMaterialIcons.GridOn, UTKMaterialIcons.GridOff, tooltip: "그리드 표시/숨김");
_horizontalModel.AddToggleButton("스냅", false, UTKMaterialIcons.FilterCenterFocus, UTKMaterialIcons.CenterFocusWeak, tooltip: "스냅 활성화");
_horizontalModel.AddToggleButton("", false, UTKMaterialIcons.GridOn, UTKMaterialIcons.GridOff, tooltip: "그리드 표시/숨김");
_horizontalModel.AddToggleButton("", false, UTKMaterialIcons.FilterCenterFocus, UTKMaterialIcons.CenterFocusWeak, tooltip: "스냅 활성화");
_horizontalModel.AddSeparator();
// Radio
_horizontalModel.AddRadioButton("tool", "선택", true, UTKMaterialIcons.NearMe, tooltip: "선택 도구");
_horizontalModel.AddRadioButton("tool", "이동", false, UTKMaterialIcons.OpenWith, tooltip: "이동 도구");
_horizontalModel.AddRadioButton("tool", "회전", false, UTKMaterialIcons.Refresh, tooltip: "회전 도구");
_horizontalModel.AddRadioButton("tool", "", true, UTKMaterialIcons.NearMe, tooltip: "선택 도구");
_horizontalModel.AddRadioButton("tool", "", false, UTKMaterialIcons.OpenWith, tooltip: "이동 도구");
_horizontalModel.AddRadioButton("tool", "", false, UTKMaterialIcons.Refresh, tooltip: "회전 도구");
_horizontalModel.AddSeparator();
// Expandable
var shapeBtn = _horizontalModel.AddExpandableButton("도형", UTKMaterialIcons.Category, tooltip: "도형 추가", updateIconOnSelection: true);
shapeBtn.SubButtons.Add(new UTKToolBarStandardButtonData { Text = "사각형", IconPath = UTKMaterialIcons.CropSquare, UseMaterialIcon = true });
shapeBtn.SubButtons.Add(new UTKToolBarStandardButtonData { Text = "원형", IconPath = UTKMaterialIcons.Circle, UseMaterialIcon = true });
shapeBtn.SubButtons.Add(new UTKToolBarStandardButtonData { Text = "삼각형", IconPath = UTKMaterialIcons.ChangeHistory, UseMaterialIcon = true });
var shapeBtn = _horizontalModel.AddExpandableButton("", UTKMaterialIcons.Category, tooltip: "도형 추가", updateIconOnSelection: true);
shapeBtn.SubButtons.Add(new UTKToolBarStandardButtonData { Text = "", IconPath = UTKMaterialIcons.CropSquare, UseMaterialIcon = true, Tooltip = "사각형 추가" });
shapeBtn.SubButtons.Add(new UTKToolBarStandardButtonData { Text = "", IconPath = UTKMaterialIcons.Circle, UseMaterialIcon = true, Tooltip = "원형 추가" });
shapeBtn.SubButtons.Add(new UTKToolBarStandardButtonData { Text = "", IconPath = UTKMaterialIcons.ChangeHistory, UseMaterialIcon = true, Tooltip = "삼각형 추가" });
_horizontalToolBar = new UTKToolBar();
_horizontalToolBar.Orientation = UTKToolBarOrientation.Horizontal;

View File

@@ -10,17 +10,13 @@ namespace UVC.UIToolkit
{
/// <summary>
/// VisualElement에 툴팁을 설정합니다.
/// UTKTooltipManager가 초기화되어 있어야 합니다.
/// </summary>
/// <param name="element">대상 요소</param>
/// <param name="tooltip">툴팁 텍스트 또는 다국어 키</param>
/// <returns>체이닝을 위한 원본 요소</returns>
public static T SetTooltip<T>(this T element, string tooltip) where T : VisualElement
{
if (UTKTooltipManager.Instance.IsInitialized)
{
UTKTooltipManager.Instance.AttachTooltip(element, tooltip);
}
UTKTooltipManager.Instance.AttachTooltip(element, tooltip);
return element;
}
@@ -31,10 +27,7 @@ namespace UVC.UIToolkit
/// <returns>체이닝을 위한 원본 요소</returns>
public static T ClearTooltip<T>(this T element) where T : VisualElement
{
if (UTKTooltipManager.Instance.IsInitialized)
{
UTKTooltipManager.Instance.DetachTooltip(element);
}
UTKTooltipManager.Instance.DetachTooltip(element);
return element;
}
@@ -46,10 +39,7 @@ namespace UVC.UIToolkit
/// <returns>체이닝을 위한 원본 요소</returns>
public static T UpdateTooltip<T>(this T element, string tooltip) where T : VisualElement
{
if (UTKTooltipManager.Instance.IsInitialized)
{
UTKTooltipManager.Instance.UpdateTooltip(element, tooltip);
}
UTKTooltipManager.Instance.UpdateTooltip(element, tooltip);
return element;
}
}

View File

@@ -12,6 +12,7 @@ namespace UVC.UIToolkit
/// <summary>
/// UIToolkit 기반 툴팁 매니저.
/// VisualElement에 마우스 오버 시 툴팁을 표시하는 싱글톤 관리자입니다.
/// panel.visualTree를 사용하여 모든 UI 위에 툴팁을 표시합니다.
/// </summary>
/// <remarks>
/// <para><b>Tooltip(툴팁)이란?</b></para>
@@ -25,7 +26,7 @@ namespace UVC.UIToolkit
/// <para>
/// UTKTooltipManager는 싱글톤으로 구현되어 있습니다.
/// <c>UTKTooltipManager.Instance</c>로 접근하며, 앱 전체에서 하나의 툴팁 UI를 공유합니다.
/// 사용 전에 반드시 <c>Initialize(root)</c>를 호출해야 합니다.
/// panel.visualTree를 사용하므로 별도 Initialize 호출이 필요 없습니다.
/// </para>
///
/// <para><b>주요 기능:</b></para>
@@ -38,7 +39,6 @@ namespace UVC.UIToolkit
///
/// <para><b>주요 메서드:</b></para>
/// <list type="bullet">
/// <item><description><c>Initialize(root)</c> - 초기화 (루트 요소 지정)</description></item>
/// <item><description><c>AttachTooltip(element, text)</c> - 요소에 툴팁 연결</description></item>
/// <item><description><c>DetachTooltip(element)</c> - 툴팁 제거</description></item>
/// <item><description><c>Show(text, position)</c> - 즉시 표시</description></item>
@@ -56,20 +56,17 @@ namespace UVC.UIToolkit
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 1. 초기화 (앱 시작 시 한 번)
/// UTKTooltipManager.Instance.Initialize(rootVisualElement);
///
/// // 2. 버튼에 툴팁 연결
/// // 1. 버튼에 툴팁 연결 (Initialize 불필요)
/// var saveButton = new UTKButton("", UTKMaterialIcons.Save);
/// UTKTooltipManager.Instance.AttachTooltip(saveButton, "저장 (Ctrl+S)");
///
/// // 3. 다국어 키로 툴팁 연결
/// // 2. 다국어 키로 툴팁 연결
/// UTKTooltipManager.Instance.AttachTooltip(settingsButton, "tooltip_settings");
///
/// // 4. 툴팁 업데이트
/// // 3. 툴팁 업데이트
/// UTKTooltipManager.Instance.UpdateTooltip(button, "새로운 설명");
///
/// // 5. 툴팁 제거
/// // 4. 툴팁 제거
/// UTKTooltipManager.Instance.DetachTooltip(button);
/// </code>
/// </example>
@@ -90,12 +87,11 @@ namespace UVC.UIToolkit
#endregion
#region Fields
private VisualElement? _root;
private VisualElement? _tooltipContainer;
private Label? _tooltipLabel;
private bool _isInitialized;
private bool _isVisible;
private bool _disposed;
private StyleSheet? _loadedUss;
private CancellationTokenSource? _showDelayCts;
private readonly Dictionary<VisualElement, string> _tooltipRegistry = new();
@@ -105,24 +101,19 @@ namespace UVC.UIToolkit
#endregion
#region Properties
public bool IsInitialized => _isInitialized;
public bool IsVisible => _isVisible;
#endregion
#region Initialization
/// <summary>
/// 툴팁 매니저를 초기화합니다.
/// 툴팁 UI를 생성합니다 (아직 visual tree에 추가하지 않음).
/// </summary>
/// <param name="root">VisualElement 트리의 루트</param>
public void Initialize(VisualElement root)
private void EnsureTooltipUI()
{
if (_isInitialized)
{
Debug.LogWarning("[UTKTooltipManager] Already initialized.");
return;
}
if (_tooltipContainer != null) return;
_root = root;
// USS 로드
_loadedUss = Resources.Load<StyleSheet>(USS_PATH);
// UXML 로드 시도
var visualTree = Resources.Load<VisualTreeAsset>(UXML_PATH);
@@ -143,13 +134,11 @@ namespace UVC.UIToolkit
_tooltipContainer.style.position = Position.Absolute;
_tooltipContainer.style.display = DisplayStyle.None;
_tooltipContainer.pickingMode = PickingMode.Ignore;
_root.Add(_tooltipContainer);
}
// 테마 변경 이벤트 구독
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
_isInitialized = true;
}
/// <summary>
@@ -174,16 +163,6 @@ namespace UVC.UIToolkit
pickingMode = PickingMode.Ignore
};
// 테마 적용
UTKThemeManager.Instance.ApplyThemeToElement(_tooltipContainer);
// USS 스타일시트 로드
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
_tooltipContainer.styleSheets.Add(uss);
}
// USS 클래스로 스타일 적용
_tooltipContainer.AddToClassList("utk-tooltip-container");
@@ -196,6 +175,33 @@ namespace UVC.UIToolkit
_tooltipContainer.Add(_tooltipLabel);
}
/// <summary>
/// 툴팁 컨테이너를 대상 요소의 panel.visualTree에 추가합니다.
/// </summary>
/// <param name="element">대상 요소 (panel 접근용)</param>
private void AttachToPanel(VisualElement element)
{
if (_tooltipContainer == null || element.panel == null) return;
var visualTree = element.panel.visualTree;
// 이미 해당 visualTree에 추가되어 있으면 스킵
if (_tooltipContainer.parent == visualTree) return;
// 다른 곳에 붙어 있으면 제거
_tooltipContainer.RemoveFromHierarchy();
// panel.visualTree에 추가
visualTree.Add(_tooltipContainer);
// 테마/USS 재적용
UTKThemeManager.Instance.ApplyThemeToElement(_tooltipContainer);
if (_loadedUss != null)
{
_tooltipContainer.styleSheets.Add(_loadedUss);
}
}
#endregion
#region Public Methods
@@ -203,10 +209,10 @@ namespace UVC.UIToolkit
/// 툴팁을 즉시 표시합니다.
/// </summary>
/// <param name="text">표시할 텍스트</param>
/// <param name="position">화면 좌표</param>
/// <param name="position">월드 좌표</param>
public void Show(string text, Vector2 position)
{
if (!_isInitialized || _tooltipContainer == null || _tooltipLabel == null)
if (_tooltipContainer == null || _tooltipLabel == null)
return;
// 다국어 처리
@@ -230,7 +236,7 @@ namespace UVC.UIToolkit
/// 지연 후 툴팁을 표시합니다.
/// </summary>
/// <param name="text">표시할 텍스트</param>
/// <param name="position">화면 좌표</param>
/// <param name="position">월드 좌표</param>
/// <param name="delayMs">지연 시간 (밀리초)</param>
public async UniTaskVoid ShowDelayed(string text, Vector2 position, int delayMs = SHOW_DELAY_MS)
{
@@ -275,21 +281,22 @@ namespace UVC.UIToolkit
// 기존 등록 제거
DetachTooltip(element);
// 툴팁 UI 생성 보장
EnsureTooltipUI();
_tooltipRegistry[element] = tooltip;
// 이벤트 콜백 생성 및 등록
// 참고: evt.position은 로컬 좌표이므로, 패널 기준 좌표로 변환 필요
EventCallback<PointerEnterEvent> enterCallback = evt =>
{
if (_tooltipRegistry.TryGetValue(element, out var text))
{
// 로컬 좌표를 root 좌표로 변환
var rootPosition = element.LocalToWorld(evt.localPosition);
if (_root != null)
{
rootPosition = _root.WorldToLocal(rootPosition);
}
ShowDelayed(text, rootPosition).Forget();
// panel.visualTree에 툴팁 컨테이너 추가
AttachToPanel(element);
// worldBound 기준 좌표 사용
var worldPos = element.LocalToWorld(evt.localPosition);
ShowDelayed(text, worldPos).Forget();
}
};
@@ -299,13 +306,9 @@ namespace UVC.UIToolkit
{
if (_isVisible)
{
// 로컬 좌표를 root 좌표로 변환
var rootPosition = element.LocalToWorld(evt.localPosition);
if (_root != null)
{
rootPosition = _root.WorldToLocal(rootPosition);
}
AdjustPosition(rootPosition);
// worldBound 기준 좌표 사용
var worldPos = element.LocalToWorld(evt.localPosition);
AdjustPosition(worldPos);
}
};
@@ -406,37 +409,39 @@ namespace UVC.UIToolkit
}
/// <summary>
/// 화면 경계 내에서 위치 조정
/// 화면 경계 내에서 위치 조정 (월드 좌표 기준)
/// </summary>
private void AdjustPosition(Vector2 mousePosition)
private void AdjustPosition(Vector2 worldPosition)
{
if (_tooltipContainer == null || _root == null)
if (_tooltipContainer == null || _tooltipContainer.panel == null)
return;
var panelRoot = _tooltipContainer.panel.visualTree;
var tooltipSize = new Vector2(
_tooltipContainer.resolvedStyle.width,
_tooltipContainer.resolvedStyle.height
);
var rootSize = new Vector2(
_root.resolvedStyle.width,
_root.resolvedStyle.height
var panelSize = new Vector2(
panelRoot.resolvedStyle.width,
panelRoot.resolvedStyle.height
);
// 기본 위치: 마우스 오른쪽 아래
float x = mousePosition.x + POSITION_OFFSET;
float y = mousePosition.y + POSITION_OFFSET;
float x = worldPosition.x + POSITION_OFFSET;
float y = worldPosition.y + POSITION_OFFSET;
// 오른쪽 경계 체크
if (x + tooltipSize.x > rootSize.x)
if (x + tooltipSize.x > panelSize.x)
{
x = mousePosition.x - tooltipSize.x - POSITION_OFFSET;
x = worldPosition.x - tooltipSize.x - POSITION_OFFSET;
}
// 아래쪽 경계 체크
if (y + tooltipSize.y > rootSize.y)
if (y + tooltipSize.y > panelSize.y)
{
y = mousePosition.y - tooltipSize.y - POSITION_OFFSET;
y = worldPosition.y - tooltipSize.y - POSITION_OFFSET;
}
// 왼쪽/위쪽 경계 체크
@@ -476,9 +481,7 @@ namespace UVC.UIToolkit
_tooltipContainer?.RemoveFromHierarchy();
_tooltipContainer = null;
_tooltipLabel = null;
_root = null;
_isInitialized = false;
_isVisible = false;
_instance = null;
}

View File

@@ -4,6 +4,7 @@ using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.Extention;
namespace UVC.UIToolkit
{
@@ -233,6 +234,7 @@ namespace UVC.UIToolkit
if (_label != null)
{
_label.selection.isSelectable = value;
_label.pickingMode = value ? PickingMode.Position : PickingMode.Ignore;
}
}
}
@@ -456,7 +458,7 @@ namespace UVC.UIToolkit
{
AddToClassList("utk-label");
_label = new Label { name = "label" };
_label = new Label { name = "label", pickingMode = PickingMode.Ignore };
_label.AddToClassList("utk-label__text");
Add(_label);
@@ -621,14 +623,14 @@ namespace UVC.UIToolkit
// 아이콘과 텍스트 사이의 간격을 적용
if (_iconLabel != null)
{
_iconLabel.style.marginRight = _iconPosition == IconPosition.Left ? _gap : 0;
_iconLabel.style.marginLeft = _iconPosition == IconPosition.Right ? _gap : 0;
_iconLabel.style.marginRight = _iconPosition == IconPosition.Left ? (_text.IsNullOrEmpty() ? 0 : _gap) : 0;
_iconLabel.style.marginLeft = _iconPosition == IconPosition.Right ? (_text.IsNullOrEmpty() ? 0 : _gap) : 0;
}
if (_imageIcon != null)
{
_imageIcon.style.marginRight = _iconPosition == IconPosition.Left ? _gap : 0;
_imageIcon.style.marginLeft = _iconPosition == IconPosition.Right ? _gap : 0;
_imageIcon.style.marginRight = _iconPosition == IconPosition.Left ? (_text.IsNullOrEmpty() ? 0 : _gap) : 0;
_imageIcon.style.marginLeft = _iconPosition == IconPosition.Right ? (_text.IsNullOrEmpty() ? 0 : _gap) : 0;
}
}
@@ -669,7 +671,7 @@ namespace UVC.UIToolkit
_iconLabel.style.display = DisplayStyle.Flex;
UTKMaterialIcons.ApplyIconStyle(_iconLabel, fontSize ?? GetEffectiveIconSize());
}
if(_text.IsNullOrEmpty()) TextAlignment = TextAlign.Center; // 텍스트가 없는 경우 아이콘 중앙 정렬
EnableInClassList("utk-label--has-icon", true);
UpdateIconPosition();
UpdateGap();
@@ -693,6 +695,7 @@ namespace UVC.UIToolkit
await UTKMaterialIcons.ApplyIconStyleAsync(_iconLabel, ct, fontSize ?? GetEffectiveIconSize());
}
if(_text.IsNullOrEmpty()) TextAlignment = TextAlign.Center; // 텍스트가 없는 경우 아이콘 중앙 정렬
EnableInClassList("utk-label--has-icon", true);
UpdateIconPosition();
UpdateGap();
@@ -816,6 +819,7 @@ namespace UVC.UIToolkit
_imageIcon.style.backgroundImage = new StyleBackground(texture);
_imageIcon.style.display = DisplayStyle.Flex;
if(_text.IsNullOrEmpty()) TextAlignment = TextAlign.Center; // 텍스트가 없는 경우 아이콘 중앙 정렬
EnableInClassList("utk-label--has-icon", true);
UpdateIconPosition();
UpdateGap();

View File

@@ -874,7 +874,8 @@ namespace UVC.UIToolkit
title = new UTKLabel();
title.AddToClassList("utk-property-group__title");
title.Size = UTKLabel.LabelSize.Label2;
title.IsBold = true;
// count = new UTKLabel();
// count.AddToClassList("utk-property-group__count");
@@ -887,8 +888,7 @@ namespace UVC.UIToolkit
// 데이터 바인딩
expandIcon.SetMaterialIcon(group.IsExpanded ? UTKMaterialIcons.ExpandMore : UTKMaterialIcons.ChevronRight, 16);
title.Text = group.GroupName;
title.Size = UTKLabel.LabelSize.Label1;
title.IsBold = true;
// count.Text = $"({group.ItemCount})";
// count.Variant = UTKLabel.LabelVariant.Secondary;

View File

@@ -90,11 +90,32 @@ namespace UVC.UIToolkit
_listView.selectionType = SelectionType.Single;
_listView.reorderable = true;
_listView.reorderMode = ListViewReorderMode.Animated;
_listView.selectionType = SelectionType.None; // 선택 비활성화 (체크박스 사용)
_listView.itemIndexChanged += OnItemIndexChanged;
// Unity 내장 reorderable-handle 숨김 (CSS 선택자가 매칭되지 않는 경우 대비)
_listView.RegisterCallback<AttachToPanelEvent>(_ =>
{
_listView.schedule.Execute(() => HideBuiltInHandles()).ExecuteLater(50);
});
Add(_listView);
}
/// <summary>
/// Unity ListView 내장 드래그 핸들을 숨깁니다.
/// 커스텀 드래그 핸들(DragIndicator 아이콘)을 사용하므로 내장 핸들은 불필요합니다.
/// </summary>
private void HideBuiltInHandles()
{
if (_listView == null) return;
_listView.Query(className: "unity-list-view__reorderable-handle").ForEach(el =>
{
el.style.display = DisplayStyle.None;
el.style.width = 0;
});
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
@@ -137,9 +158,9 @@ namespace UVC.UIToolkit
root.styleSheets.Add(_itemStyleSheet);
// 드래그 핸들 아이콘 설정
var handle = root.Q<Label>("drag-handle");
var handle = root.Q<UTKLabel>("drag-handle");
if (handle != null)
handle.text = UTKMaterialIcons.DragIndicator;
handle.SetMaterialIcon(UTKMaterialIcons.DragIndicator);
return root;
}
@@ -156,7 +177,7 @@ namespace UVC.UIToolkit
container.style.alignItems = Align.Center;
container.style.minHeight = ITEM_HEIGHT;
var handle = new Label(UTKMaterialIcons.DragIndicator);
var handle = new UTKLabel("", UTKMaterialIcons.DragIndicator);
handle.name = "drag-handle";
handle.AddToClassList("reordable-list-item__drag-handle");
container.Add(handle);
@@ -178,6 +199,9 @@ namespace UVC.UIToolkit
{
if (index < 0 || index >= _items.Count) return;
// ListView가 makeItem 반환 후 inline flex-grow:0을 강제하므로 bindItem에서 덮어씀
element.style.flexGrow = 1;
var data = _items[index];
// 요소 참조

View File

@@ -15,10 +15,10 @@ namespace UVC.UIToolkit
#region Fields
/// <summary>아이콘 요소 (Material Icon Label 또는 Image)</summary>
protected Label? _iconLabel;
protected UTKLabel? _iconLabel;
/// <summary>텍스트 라벨</summary>
protected Label? _textLabel;
protected UTKLabel? _textLabel;
/// <summary>루트 버튼 요소</summary>
protected VisualElement? _rootButton;
@@ -103,12 +103,13 @@ namespace UVC.UIToolkit
{
var root = asset.Instantiate();
_rootButton = root.Q<VisualElement>("button-root");
_iconLabel = root.Q<Label>("icon");
_textLabel = root.Q<Label>("label");
_iconLabel = root.Q<UTKLabel>("icon");
_textLabel = root.Q<UTKLabel>("label");
_textLabel.Size = UTKLabel.LabelSize.Caption; // UXML에서 기본 크기를 설정하므로 코드에서 다시 지정
// TemplateContainer가 아이콘 정렬을 방해하지 않도록 설정
root.style.flexGrow = 1;
root.style.alignItems = Align.Center;
root.style.alignItems = Align.Stretch;
root.style.justifyContent = Justify.Center;
Add(root);
@@ -129,12 +130,13 @@ namespace UVC.UIToolkit
_rootButton = new VisualElement();
_rootButton.AddToClassList("utk-toolbar-btn");
_iconLabel = new Label();
_iconLabel = new UTKLabel();
_iconLabel.AddToClassList("utk-toolbar-btn__icon");
_rootButton.Add(_iconLabel);
_textLabel = new Label();
_textLabel = new UTKLabel();
_textLabel.AddToClassList("utk-toolbar-btn__label");
_textLabel.Size = UTKLabel.LabelSize.Caption;
_rootButton.Add(_textLabel);
Add(_rootButton);
@@ -164,6 +166,7 @@ namespace UVC.UIToolkit
UpdateIcon(_data.IconPath, _data.UseMaterialIcon);
UpdateText(_data.Text);
UpdateEnabled(_data.IsEnabled);
UpdateTooltip(_data.Tooltip);
}
/// <summary>
@@ -216,29 +219,15 @@ namespace UVC.UIToolkit
if (useMaterialIcon)
{
// Material Icon (폰트 기반)
// Material Icon (폰트 기반) - UTKLabel의 SetMaterialIcon 사용
_iconLabel.RemoveFromClassList("utk-toolbar-btn__icon--image");
_iconLabel.text = iconPath;
_iconLabel.style.backgroundImage = StyleKeyword.None;
_iconLabel.SetMaterialIcon(iconPath);
}
else
{
// 이미지 아이콘
// 이미지 아이콘 - UTKLabel의 SetImageIcon 사용
_iconLabel.AddToClassList("utk-toolbar-btn__icon--image");
_iconLabel.text = "";
var sprite = Resources.Load<Sprite>(iconPath);
if (sprite != null)
{
_iconLabel.style.backgroundImage = new StyleBackground(sprite);
}
else
{
var texture = Resources.Load<Texture2D>(iconPath);
if (texture != null)
{
_iconLabel.style.backgroundImage = new StyleBackground(texture);
}
}
_iconLabel.SetImageIcon(iconPath);
}
}
@@ -250,7 +239,26 @@ namespace UVC.UIToolkit
{
if (_textLabel != null)
{
_textLabel.text = text;
_textLabel.Text = text;
_textLabel.style.display = string.IsNullOrEmpty(text) ? DisplayStyle.None : DisplayStyle.Flex;
}
}
/// <summary>
/// 툴팁을 업데이트합니다.
/// </summary>
/// <param name="tooltipText">툴팁 텍스트</param>
protected void UpdateTooltip(string? tooltipText)
{
if (_rootButton == null) return;
if (string.IsNullOrEmpty(tooltipText))
{
UTKTooltipManager.Instance.DetachTooltip(_rootButton);
}
else
{
UTKTooltipManager.Instance.UpdateTooltip(_rootButton, tooltipText);
}
}
@@ -282,6 +290,7 @@ namespace UVC.UIToolkit
UpdateIcon(_data.IconPath, _data.UseMaterialIcon);
UpdateText(_data.Text);
UpdateEnabled(_data.IsEnabled);
UpdateTooltip(_data.Tooltip);
}
#endregion
@@ -354,6 +363,12 @@ namespace UVC.UIToolkit
// 데이터 바인딩 해제
UnbindData();
// 툴팁 해제
if (_rootButton != null)
{
UTKTooltipManager.Instance.DetachTooltip(_rootButton);
}
// 테마 구독 해제
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);