From b64c3e10bc672bb66d4df456edbb02e9a0be4f51 Mon Sep 17 00:00:00 2001 From: logonkhi Date: Fri, 20 Feb 2026 19:17:36 +0900 Subject: [PATCH] =?UTF-8?q?UTKToolBar=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIToolkit/Button/UTKCheckBox.uss | 1 - .../List/UTKPropertyGroupHeader.uxml | 2 +- .../UIToolkit/List/UTKReordableListItem.uxml | 2 +- .../List/UTKReordableListItemUss.uss | 1 + .../UIToolkit/List/UTKReordableListUss.uss | 16 +- .../UIToolkit/Style/UTKDefaultStyle.uss | 1 + .../UIToolkit/ToolBar/UTKToolBarButton.uxml | 6 +- .../UIToolkit/ToolBar/UTKToolBarButtonUss.uss | 1 - .../ToolBar/UTKToolBarExpandableButton.uxml | 6 +- .../ToolBar/UTKToolBarSubMenuUss.uss | 15 +- .../ToolBar/UTKToolBarToggleButton.uxml | 6 +- .../Sample/UIToolkit/UTKReordableList.unity | 497 ++++++++++++++++++ .../UIToolkit/UTKReordableList.unity.meta | 7 + .../UIToolkit/UTKReordableListSample.cs | 152 ++++++ .../UIToolkit/UTKReordableListSample.cs.meta | 2 + .../UIToolkit/UTKReordableListUXML.uxml | 6 + .../UIToolkit/UTKReordableListUXML.uxml.meta | 10 + .../UIToolkit/UTKStyleGuideSample.Modal.cs | 7 +- .../Sample/UIToolkit/UTKStyleGuideSample.cs | 1 - Assets/Sample/UIToolkit/UTKToolBarSample.cs | 26 +- .../UIToolkit/Common/UTKTooltipExtensions.cs | 16 +- .../UVC/UIToolkit/Common/UTKTooltipManager.cs | 135 ++--- .../Scripts/UVC/UIToolkit/Label/UTKLabel.cs | 16 +- .../UVC/UIToolkit/List/UTKPropertyList.cs | 6 +- .../UVC/UIToolkit/List/UTKReordableList.cs | 30 +- .../ToolBar/Items/UTKToolBarButtonBase.cs | 67 ++- 26 files changed, 882 insertions(+), 153 deletions(-) create mode 100644 Assets/Sample/UIToolkit/UTKReordableList.unity create mode 100644 Assets/Sample/UIToolkit/UTKReordableList.unity.meta create mode 100644 Assets/Sample/UIToolkit/UTKReordableListSample.cs create mode 100644 Assets/Sample/UIToolkit/UTKReordableListSample.cs.meta create mode 100644 Assets/Sample/UIToolkit/UTKReordableListUXML.uxml create mode 100644 Assets/Sample/UIToolkit/UTKReordableListUXML.uxml.meta diff --git a/Assets/Resources/UIToolkit/Button/UTKCheckBox.uss b/Assets/Resources/UIToolkit/Button/UTKCheckBox.uss index f62f89a4..3bd0ca14 100644 --- a/Assets/Resources/UIToolkit/Button/UTKCheckBox.uss +++ b/Assets/Resources/UIToolkit/Button/UTKCheckBox.uss @@ -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; } diff --git a/Assets/Resources/UIToolkit/List/UTKPropertyGroupHeader.uxml b/Assets/Resources/UIToolkit/List/UTKPropertyGroupHeader.uxml index 194b3525..0e70506c 100644 --- a/Assets/Resources/UIToolkit/List/UTKPropertyGroupHeader.uxml +++ b/Assets/Resources/UIToolkit/List/UTKPropertyGroupHeader.uxml @@ -20,7 +20,7 @@ --> - + diff --git a/Assets/Resources/UIToolkit/List/UTKReordableListItem.uxml b/Assets/Resources/UIToolkit/List/UTKReordableListItem.uxml index 2dce22d9..705deea6 100644 --- a/Assets/Resources/UIToolkit/List/UTKReordableListItem.uxml +++ b/Assets/Resources/UIToolkit/List/UTKReordableListItem.uxml @@ -6,7 +6,7 @@ - + diff --git a/Assets/Resources/UIToolkit/List/UTKReordableListItemUss.uss b/Assets/Resources/UIToolkit/List/UTKReordableListItemUss.uss index a3052a3f..1abc14f2 100644 --- a/Assets/Resources/UIToolkit/List/UTKReordableListItemUss.uss +++ b/Assets/Resources/UIToolkit/List/UTKReordableListItemUss.uss @@ -14,6 +14,7 @@ align-items: center; padding: var(--space-s) var(--space-m); min-height: 36px; + flex-grow: 1; } /* =================================== diff --git a/Assets/Resources/UIToolkit/List/UTKReordableListUss.uss b/Assets/Resources/UIToolkit/List/UTKReordableListUss.uss index 95913bfd..baf67373 100644 --- a/Assets/Resources/UIToolkit/List/UTKReordableListUss.uss +++ b/Assets/Resources/UIToolkit/List/UTKReordableListUss.uss @@ -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; } diff --git a/Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss b/Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss index c0207033..db5fe94c 100644 --- a/Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss +++ b/Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss @@ -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, diff --git a/Assets/Resources/UIToolkit/ToolBar/UTKToolBarButton.uxml b/Assets/Resources/UIToolkit/ToolBar/UTKToolBarButton.uxml index 144628be..2b8ee89d 100644 --- a/Assets/Resources/UIToolkit/ToolBar/UTKToolBarButton.uxml +++ b/Assets/Resources/UIToolkit/ToolBar/UTKToolBarButton.uxml @@ -1,6 +1,6 @@ - + - - + + diff --git a/Assets/Resources/UIToolkit/ToolBar/UTKToolBarButtonUss.uss b/Assets/Resources/UIToolkit/ToolBar/UTKToolBarButtonUss.uss index bcf381b0..51dc8b0a 100644 --- a/Assets/Resources/UIToolkit/ToolBar/UTKToolBarButtonUss.uss +++ b/Assets/Resources/UIToolkit/ToolBar/UTKToolBarButtonUss.uss @@ -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; } diff --git a/Assets/Resources/UIToolkit/ToolBar/UTKToolBarExpandableButton.uxml b/Assets/Resources/UIToolkit/ToolBar/UTKToolBarExpandableButton.uxml index d51f58a9..47b14e44 100644 --- a/Assets/Resources/UIToolkit/ToolBar/UTKToolBarExpandableButton.uxml +++ b/Assets/Resources/UIToolkit/ToolBar/UTKToolBarExpandableButton.uxml @@ -1,7 +1,7 @@ - + - - + + diff --git a/Assets/Resources/UIToolkit/ToolBar/UTKToolBarSubMenuUss.uss b/Assets/Resources/UIToolkit/ToolBar/UTKToolBarSubMenuUss.uss index 1207fce5..d63a4320 100644 --- a/Assets/Resources/UIToolkit/ToolBar/UTKToolBarSubMenuUss.uss +++ b/Assets/Resources/UIToolkit/ToolBar/UTKToolBarSubMenuUss.uss @@ -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; } diff --git a/Assets/Resources/UIToolkit/ToolBar/UTKToolBarToggleButton.uxml b/Assets/Resources/UIToolkit/ToolBar/UTKToolBarToggleButton.uxml index f287471b..2b24b9f8 100644 --- a/Assets/Resources/UIToolkit/ToolBar/UTKToolBarToggleButton.uxml +++ b/Assets/Resources/UIToolkit/ToolBar/UTKToolBarToggleButton.uxml @@ -1,6 +1,6 @@ - + - - + + diff --git a/Assets/Sample/UIToolkit/UTKReordableList.unity b/Assets/Sample/UIToolkit/UTKReordableList.unity new file mode 100644 index 00000000..b55ec545 --- /dev/null +++ b/Assets/Sample/UIToolkit/UTKReordableList.unity @@ -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} diff --git a/Assets/Sample/UIToolkit/UTKReordableList.unity.meta b/Assets/Sample/UIToolkit/UTKReordableList.unity.meta new file mode 100644 index 00000000..23eced46 --- /dev/null +++ b/Assets/Sample/UIToolkit/UTKReordableList.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8b1b2ce854a8a3d47ac1fc46dfaf615f +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Sample/UIToolkit/UTKReordableListSample.cs b/Assets/Sample/UIToolkit/UTKReordableListSample.cs new file mode 100644 index 00000000..58dc1b05 --- /dev/null +++ b/Assets/Sample/UIToolkit/UTKReordableListSample.cs @@ -0,0 +1,152 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UIElements; +using UVC.UIToolkit; + +/// +/// UTKReordableList의 기능을 테스트하기 위한 샘플 MonoBehaviour입니다. +/// Dictionary 기반 SetData/ToDictionary, 이벤트 핸들러, 데이터 CRUD를 확인합니다. +/// +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(); + if (doc == null) + { + Debug.LogError("UIDocument가 할당되지 않았습니다."); + return; + } + uiDocument = doc; + + var root = uiDocument.rootVisualElement; + + // 테마 토글 + _themeToggle = root.Q("toggle"); + if (_themeToggle == null) + { + Debug.LogError("UXML에서 UTKToggle을 찾을 수 없습니다."); + return; + } + + // ReordableList + _reordableList = root.Q("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); + } + + /// + /// Dictionary 기반으로 샘플 데이터를 설정합니다. + /// + private void SetSampleData() + { + var listDict = new List> + { + 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); + } + + /// + /// 테스트 버튼들을 생성합니다. + /// + 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); + } + + /// + /// ToDictionary를 호출하여 현재 데이터를 콘솔에 출력합니다. + /// + 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"]}"); + } + } + + /// + /// 새 아이템을 추가합니다. + /// + private void OnAddItemClicked() + { + var currentData = _reordableList.ToDictionary(); + var newIndex = currentData.Count; + currentData.Add(new Dictionary + { + ["order"] = newIndex.ToString(), + ["active"] = "True", + ["text"] = $"항목 {newIndex}" + }); + _reordableList.SetData(currentData); + Debug.Log($"[Sample] 아이템 추가됨 (총 {currentData.Count}건)"); + } + + private void OnDestroy() + { + _reordableList?.Dispose(); + } +} diff --git a/Assets/Sample/UIToolkit/UTKReordableListSample.cs.meta b/Assets/Sample/UIToolkit/UTKReordableListSample.cs.meta new file mode 100644 index 00000000..ba917a88 --- /dev/null +++ b/Assets/Sample/UIToolkit/UTKReordableListSample.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4274ed098fc4bf048bb92836e8982c8f diff --git a/Assets/Sample/UIToolkit/UTKReordableListUXML.uxml b/Assets/Sample/UIToolkit/UTKReordableListUXML.uxml new file mode 100644 index 00000000..2cf1d04f --- /dev/null +++ b/Assets/Sample/UIToolkit/UTKReordableListUXML.uxml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Assets/Sample/UIToolkit/UTKReordableListUXML.uxml.meta b/Assets/Sample/UIToolkit/UTKReordableListUXML.uxml.meta new file mode 100644 index 00000000..56ed2e51 --- /dev/null +++ b/Assets/Sample/UIToolkit/UTKReordableListUXML.uxml.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 4bb0d1734d5c1b647ae0ffdb7879a92a +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0} diff --git a/Assets/Sample/UIToolkit/UTKStyleGuideSample.Modal.cs b/Assets/Sample/UIToolkit/UTKStyleGuideSample.Modal.cs index 5bce887e..88568678 100644 --- a/Assets/Sample/UIToolkit/UTKStyleGuideSample.Modal.cs +++ b/Assets/Sample/UIToolkit/UTKStyleGuideSample.Modal.cs @@ -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. 아이콘 버튼에 툴팁 diff --git a/Assets/Sample/UIToolkit/UTKStyleGuideSample.cs b/Assets/Sample/UIToolkit/UTKStyleGuideSample.cs index bdbe13d8..a85b3768 100644 --- a/Assets/Sample/UIToolkit/UTKStyleGuideSample.cs +++ b/Assets/Sample/UIToolkit/UTKStyleGuideSample.cs @@ -205,7 +205,6 @@ public partial class UTKStyleGuideSample : MonoBehaviour } UTKThemeManager.Instance.SetTheme(initialTheme); - UTKTooltipManager.Instance.Initialize(_root); CreateUI(); } diff --git a/Assets/Sample/UIToolkit/UTKToolBarSample.cs b/Assets/Sample/UIToolkit/UTKToolBarSample.cs index 14455fda..7fa3ddc6 100644 --- a/Assets/Sample/UIToolkit/UTKToolBarSample.cs +++ b/Assets/Sample/UIToolkit/UTKToolBarSample.cs @@ -84,7 +84,7 @@ namespace UVC.Sample.UIToolkit /// 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; diff --git a/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipExtensions.cs b/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipExtensions.cs index bb1ba57e..5ca9d658 100644 --- a/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipExtensions.cs +++ b/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipExtensions.cs @@ -10,17 +10,13 @@ namespace UVC.UIToolkit { /// /// VisualElement에 툴팁을 설정합니다. - /// UTKTooltipManager가 초기화되어 있어야 합니다. /// /// 대상 요소 /// 툴팁 텍스트 또는 다국어 키 /// 체이닝을 위한 원본 요소 public static T SetTooltip(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 /// 체이닝을 위한 원본 요소 public static T ClearTooltip(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 /// 체이닝을 위한 원본 요소 public static T UpdateTooltip(this T element, string tooltip) where T : VisualElement { - if (UTKTooltipManager.Instance.IsInitialized) - { - UTKTooltipManager.Instance.UpdateTooltip(element, tooltip); - } + UTKTooltipManager.Instance.UpdateTooltip(element, tooltip); return element; } } diff --git a/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipManager.cs b/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipManager.cs index 2e897d86..b6b4a602 100644 --- a/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipManager.cs +++ b/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipManager.cs @@ -12,6 +12,7 @@ namespace UVC.UIToolkit /// /// UIToolkit 기반 툴팁 매니저. /// VisualElement에 마우스 오버 시 툴팁을 표시하는 싱글톤 관리자입니다. + /// panel.visualTree를 사용하여 모든 UI 위에 툴팁을 표시합니다. /// /// /// Tooltip(툴팁)이란? @@ -25,7 +26,7 @@ namespace UVC.UIToolkit /// /// UTKTooltipManager는 싱글톤으로 구현되어 있습니다. /// UTKTooltipManager.Instance로 접근하며, 앱 전체에서 하나의 툴팁 UI를 공유합니다. - /// 사용 전에 반드시 Initialize(root)를 호출해야 합니다. + /// panel.visualTree를 사용하므로 별도 Initialize 호출이 필요 없습니다. /// /// /// 주요 기능: @@ -38,7 +39,6 @@ namespace UVC.UIToolkit /// /// 주요 메서드: /// - /// Initialize(root) - 초기화 (루트 요소 지정) /// AttachTooltip(element, text) - 요소에 툴팁 연결 /// DetachTooltip(element) - 툴팁 제거 /// Show(text, position) - 즉시 표시 @@ -56,20 +56,17 @@ namespace UVC.UIToolkit /// /// C# 코드에서 사용: /// - /// // 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); /// /// @@ -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 _tooltipRegistry = new(); @@ -105,24 +101,19 @@ namespace UVC.UIToolkit #endregion #region Properties - public bool IsInitialized => _isInitialized; public bool IsVisible => _isVisible; #endregion #region Initialization /// - /// 툴팁 매니저를 초기화합니다. + /// 툴팁 UI를 생성합니다 (아직 visual tree에 추가하지 않음). /// - /// VisualElement 트리의 루트 - 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(USS_PATH); // UXML 로드 시도 var visualTree = Resources.Load(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; } /// @@ -174,16 +163,6 @@ namespace UVC.UIToolkit pickingMode = PickingMode.Ignore }; - // 테마 적용 - UTKThemeManager.Instance.ApplyThemeToElement(_tooltipContainer); - - // USS 스타일시트 로드 - var uss = Resources.Load(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); } + + /// + /// 툴팁 컨테이너를 대상 요소의 panel.visualTree에 추가합니다. + /// + /// 대상 요소 (panel 접근용) + 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 /// 툴팁을 즉시 표시합니다. /// /// 표시할 텍스트 - /// 화면 좌표 + /// 월드 좌표 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 /// 지연 후 툴팁을 표시합니다. /// /// 표시할 텍스트 - /// 화면 좌표 + /// 월드 좌표 /// 지연 시간 (밀리초) 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 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 } /// - /// 화면 경계 내에서 위치 조정 + /// 화면 경계 내에서 위치 조정 (월드 좌표 기준) /// - 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; } diff --git a/Assets/Scripts/UVC/UIToolkit/Label/UTKLabel.cs b/Assets/Scripts/UVC/UIToolkit/Label/UTKLabel.cs index 218b6526..4084098c 100644 --- a/Assets/Scripts/UVC/UIToolkit/Label/UTKLabel.cs +++ b/Assets/Scripts/UVC/UIToolkit/Label/UTKLabel.cs @@ -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(); diff --git a/Assets/Scripts/UVC/UIToolkit/List/UTKPropertyList.cs b/Assets/Scripts/UVC/UIToolkit/List/UTKPropertyList.cs index 980d7b90..383ed74c 100644 --- a/Assets/Scripts/UVC/UIToolkit/List/UTKPropertyList.cs +++ b/Assets/Scripts/UVC/UIToolkit/List/UTKPropertyList.cs @@ -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; diff --git a/Assets/Scripts/UVC/UIToolkit/List/UTKReordableList.cs b/Assets/Scripts/UVC/UIToolkit/List/UTKReordableList.cs index 40ce5593..e05473d6 100644 --- a/Assets/Scripts/UVC/UIToolkit/List/UTKReordableList.cs +++ b/Assets/Scripts/UVC/UIToolkit/List/UTKReordableList.cs @@ -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(_ => + { + _listView.schedule.Execute(() => HideBuiltInHandles()).ExecuteLater(50); + }); + Add(_listView); } + /// + /// Unity ListView 내장 드래그 핸들을 숨깁니다. + /// 커스텀 드래그 핸들(DragIndicator 아이콘)을 사용하므로 내장 핸들은 불필요합니다. + /// + 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