leftSideBar 구현
- 버튼 클릭 시 열고 닫히는 애니메이션 작동 - 열었을 때 메뉴 상세이름 보이도록 - 메뉴 눌렀을 때 동작은 아직 없는 상태
This commit is contained in:
@@ -1,4 +1,2 @@
|
||||
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
||||
<ui:Template name="UTKProgressBarSample" src="project://database/Assets/Resources/UIToolkit/Sample/Slider/UTKProgressBarSample.uxml?fileID=9197481963319205126&guid=3c87ecf76a8c9fd4486ed612975878e8&type=3#UTKProgressBarSample"/>
|
||||
<ui:Instance template="UTKProgressBarSample"/>
|
||||
</ui:UXML>
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:ui="UnityEngine.UIElements"
|
||||
xmlns:uie="UnityEditor.UIElements"
|
||||
xmlns:ewlk="UVC.EnglewoodLAB.UIToolkit"
|
||||
xsi:noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
|
||||
>
|
||||
|
||||
<!-- 좌측 네비게이션 사이드바 -->
|
||||
<ewlk:EWLKNavSideBar
|
||||
name="navSideBar"
|
||||
style="position: absolute; left: 0; top: 0; bottom: 0;" />
|
||||
</ui:UXML>
|
||||
166
Assets/Resources/EWLK/UIToolkit/Main/EWLKNavSideBarUss.uss
Normal file
166
Assets/Resources/EWLK/UIToolkit/Main/EWLKNavSideBarUss.uss
Normal file
@@ -0,0 +1,166 @@
|
||||
/* ============================================================
|
||||
EWLKNavSideBar — 메인 씬 좌측 네비게이션 사이드바 스타일
|
||||
네이비 배경, 아이콘 + 텍스트 레이아웃
|
||||
|
||||
수정: color:inherit → 제거 (Unity USS는 inherit 키워드 미지원,
|
||||
부모 color가 자식에게 자동 전파됨)
|
||||
cursor:link → 제거 (Unity USS 미지원)
|
||||
============================================================ */
|
||||
|
||||
/* ── 사이드바 루트 ────────────────────────────────────────── */
|
||||
.ewlk-nav-sidebar {
|
||||
flex-direction: column;
|
||||
background-color: rgb(26, 34, 64);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── 헤더 ─────────────────────────────────────────────────── */
|
||||
.ewlk-nav__header {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
padding-left: 8px;
|
||||
padding-right: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ewlk-nav__logo-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-image: resource("EWLK/Images/logo-white");
|
||||
background-size: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ewlk-nav__logo-text {
|
||||
flex-grow: 1;
|
||||
color: rgb(255, 255, 255);
|
||||
font-size: 13px;
|
||||
-unity-font-style: bold;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin-left: 8px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.ewlk-nav__close-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
border-width: 0;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.ewlk-nav__close-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
/* ── 아이콘 레이블 공통 ───────────────────────────────────── */
|
||||
/* color는 부모(버튼)에서 자동 전파되므로 별도 지정 불필요 */
|
||||
.ewlk-nav__icon-label {
|
||||
-unity-text-align: middle-center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── 구분선 ───────────────────────────────────────────────── */
|
||||
.ewlk-nav__divider {
|
||||
height: 1px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── 메뉴 영역 ────────────────────────────────────────────── */
|
||||
.ewlk-nav__menu {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── 메뉴 항목 (Button) ────────────────────────────────────── */
|
||||
.ewlk-nav__item {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
min-height: 0;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
-unity-text-align: middle-left;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ewlk-nav__item:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.ewlk-nav__item--active {
|
||||
background-color: rgba(255, 255, 255, 0.14);
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.ewlk-nav__item--active:hover {
|
||||
background-color: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
/* ── 메뉴 항목 아이콘 / 레이블 ─────────────────────────────── */
|
||||
/* color는 부모(.ewlk-nav__item)에서 자동 전파 */
|
||||
.ewlk-nav__item-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
-unity-text-align: middle-center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ewlk-nav__item-label {
|
||||
flex-grow: 1;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin-left: 8px;
|
||||
-unity-text-align: middle-left;
|
||||
}
|
||||
|
||||
/* ── 푸터 ─────────────────────────────────────────────────── */
|
||||
.ewlk-nav__footer {
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
/* ── 토글 버튼 (열기/닫기) ────────────────────────────────── */
|
||||
.ewlk-nav__toggle-btn {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.ewlk-nav__toggle-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2773d1950271a4b478656767cf6763d2
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
unsupportedSelectorAction: 0
|
||||
@@ -279,6 +279,7 @@ GameObject:
|
||||
- component: {fileID: 672992130}
|
||||
- component: {fileID: 672992129}
|
||||
- component: {fileID: 672992128}
|
||||
- component: {fileID: 672992131}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
@@ -360,6 +361,50 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &672992131
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 672992127}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData
|
||||
m_RenderShadows: 1
|
||||
m_RequiresDepthTextureOption: 2
|
||||
m_RequiresOpaqueTextureOption: 2
|
||||
m_CameraType: 0
|
||||
m_Cameras: []
|
||||
m_RendererIndex: -1
|
||||
m_VolumeLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 1
|
||||
m_VolumeTrigger: {fileID: 0}
|
||||
m_VolumeFrameworkUpdateModeOption: 2
|
||||
m_RenderPostProcessing: 0
|
||||
m_Antialiasing: 0
|
||||
m_AntialiasingQuality: 2
|
||||
m_StopNaN: 0
|
||||
m_Dithering: 0
|
||||
m_ClearDepth: 1
|
||||
m_AllowXRRendering: 1
|
||||
m_AllowHDROutput: 1
|
||||
m_UseScreenCoordOverride: 0
|
||||
m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_RequiresDepthTexture: 0
|
||||
m_RequiresColorTexture: 0
|
||||
m_TaaSettings:
|
||||
m_Quality: 3
|
||||
m_FrameInfluence: 0.1
|
||||
m_JitterScale: 1
|
||||
m_MipBias: 0
|
||||
m_VarianceClampScale: 0.9
|
||||
m_ContrastAdaptiveSharpening: 0
|
||||
m_Version: 2
|
||||
--- !u!1 &774244994
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -129,6 +129,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 39254822}
|
||||
- component: {fileID: 39254821}
|
||||
- component: {fileID: 39254823}
|
||||
m_Layer: 0
|
||||
m_Name: Directional Light
|
||||
m_TagString: Untagged
|
||||
@@ -216,6 +217,134 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!114 &39254823
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 39254820}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData
|
||||
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 &557669013
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 557669015}
|
||||
- component: {fileID: 557669014}
|
||||
m_Layer: 0
|
||||
m_Name: StaticDocument
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &557669014
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 557669013}
|
||||
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: 3e76559fee54d2040be8ca19a74c70e3, 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!4 &557669015
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 557669013}
|
||||
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 &883201336
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 883201337}
|
||||
- component: {fileID: 883201338}
|
||||
m_Layer: 0
|
||||
m_Name: SceneMain
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &883201337
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 883201336}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 897.09546, y: 577.4398, z: 32.3774}
|
||||
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 &883201338
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 883201336}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 85492bb0e96aa6946b45bd6bd762a556, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::UVC.EnglewoodLAB.EWLKSceneMain
|
||||
--- !u!1 &1079771970
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -308,9 +437,174 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1306527161
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1306527163}
|
||||
- component: {fileID: 1306527162}
|
||||
m_Layer: 0
|
||||
m_Name: DynamicDocument
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1306527162
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1306527161}
|
||||
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: 0cf46852e8559c8439acec80a2f14fc3, type: 3}
|
||||
m_SortingOrder: 1000
|
||||
m_Position: 0
|
||||
m_WorldSpaceSizeMode: 1
|
||||
m_WorldSpaceWidth: 1920
|
||||
m_WorldSpaceHeight: 1080
|
||||
m_PivotReferenceSize: 0
|
||||
m_Pivot: 0
|
||||
m_WorldSpaceCollider: {fileID: 0}
|
||||
--- !u!4 &1306527163
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1306527161}
|
||||
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 &1664424032
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1664424033}
|
||||
- component: {fileID: 1664424034}
|
||||
m_Layer: 0
|
||||
m_Name: SceneContext
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1664424033
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1664424032}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 897.09546, y: 577.4398, z: 32.3774}
|
||||
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 &1664424034
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1664424032}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7bcd3c6511d97a94d8da2de5051ba292, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::UVC.EnglewoodLAB.EWLKSceneContext
|
||||
scenePrefabs: []
|
||||
autoInjectSceneObjects: 1
|
||||
targetObjects: []
|
||||
staticUI: {fileID: 557669014}
|
||||
dynamicUI: {fileID: 1306527162}
|
||||
modalUI: {fileID: 1840260740}
|
||||
--- !u!1 &1840260738
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1840260739}
|
||||
- component: {fileID: 1840260740}
|
||||
m_Layer: 0
|
||||
m_Name: ModalDocument
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1840260739
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1840260738}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 897.09546, y: 577.4398, z: 32.3774}
|
||||
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 &1840260740
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1840260738}
|
||||
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: 4f0c5b331c9f51f4387dadbbafc03e0b, type: 3}
|
||||
m_SortingOrder: 2000
|
||||
m_Position: 0
|
||||
m_WorldSpaceSizeMode: 1
|
||||
m_WorldSpaceWidth: 1920
|
||||
m_WorldSpaceHeight: 1080
|
||||
m_PivotReferenceSize: 0
|
||||
m_Pivot: 0
|
||||
m_WorldSpaceCollider: {fileID: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 1079771973}
|
||||
- {fileID: 39254822}
|
||||
- {fileID: 557669015}
|
||||
- {fileID: 1306527163}
|
||||
- {fileID: 1840260739}
|
||||
- {fileID: 883201337}
|
||||
- {fileID: 1664424033}
|
||||
|
||||
@@ -1,6 +1,92 @@
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.Core;
|
||||
using UVC.EnglewoodLAB.UIToolkit;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
public class EWLKSceneContext
|
||||
namespace UVC.EnglewoodLAB
|
||||
{
|
||||
|
||||
/// <summary>UIDocument 타입 충돌 방지용 마커 래퍼 — Modal UI Document</summary>
|
||||
public sealed class ModalUIDocument { public readonly UIDocument Value; public ModalUIDocument(UIDocument v) { Value = v; } }
|
||||
|
||||
/// <summary>
|
||||
/// 메인 씬의 DI 컨텍스트.
|
||||
/// UIDocument 세 개와 씬 전용 서비스를 등록합니다.
|
||||
/// </summary>
|
||||
public class EWLKSceneContext : InjectorSceneContext
|
||||
{
|
||||
/// <summary>EWLKSceneContext 인스턴스에 접근하기 위한 편의 프로퍼티</summary>
|
||||
public static new EWLKSceneContext? Instance => InjectorSceneContext.Instance as EWLKSceneContext;
|
||||
|
||||
[SerializeField] private UIDocument? staticUI;
|
||||
[SerializeField] private UIDocument? dynamicUI;
|
||||
[SerializeField] private UIDocument? modalUI;
|
||||
|
||||
/// <summary>
|
||||
/// 메인 씬 서비스를 등록합니다.
|
||||
/// 씬 로드 시 자동 호출되며, 씬 전환 시 자동으로 정리됩니다.
|
||||
/// </summary>
|
||||
protected override void RegisterSceneServices()
|
||||
{
|
||||
base.RegisterSceneServices();
|
||||
|
||||
Injector.RegisterSingleton<EWLKSceneMain>();
|
||||
|
||||
if (staticUI != null)
|
||||
{
|
||||
staticUI.name = "StaticUI";
|
||||
Injector.RegisterInstance(new StaticUIDocument(staticUI), ServiceLifetime.Scene);
|
||||
}
|
||||
|
||||
if (dynamicUI != null)
|
||||
{
|
||||
dynamicUI.name = "DynamicUI";
|
||||
Injector.RegisterInstance(new DynamicUIDocument(dynamicUI), ServiceLifetime.Scene);
|
||||
}
|
||||
|
||||
if (modalUI != null)
|
||||
{
|
||||
modalUI.name = "ModalUI";
|
||||
Injector.RegisterInstance(new ModalUIDocument(modalUI), ServiceLifetime.Scene);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 비동기 서비스 등록.
|
||||
/// UIDocument의 rootVisualElement가 준비된 후 NavSideBar를 추출하고,
|
||||
/// Modal UI 루트를 UTK 컴포넌트에 설정합니다.
|
||||
/// RegisterSceneServices()는 Awake 동 프레임에 실행되므로 rootVisualElement가
|
||||
/// null일 수 있습니다. 여기서는 한 프레임 대기 후 안전하게 접근합니다.
|
||||
/// </summary>
|
||||
protected override async UniTask RegisterSceneServicesAsync()
|
||||
{
|
||||
await base.RegisterSceneServicesAsync();
|
||||
|
||||
// UIDocument 패널 초기화 대기 (Awake → Start 이후 준비됨)
|
||||
await UniTask.WaitUntil(
|
||||
() => staticUI == null || staticUI.rootVisualElement != null
|
||||
);
|
||||
|
||||
// NavSideBar를 UXML에서 추출하여 DI에 등록
|
||||
if (staticUI != null)
|
||||
{
|
||||
var navSideBar = staticUI.rootVisualElement?.Q<EWLKNavSideBar>("navSideBar");
|
||||
if (navSideBar != null)
|
||||
Injector.RegisterInstance<EWLKNavSideBar>(navSideBar, ServiceLifetime.Scene);
|
||||
else
|
||||
Debug.LogWarning("[EWLKSceneContext] StaticUI에서 EWLKNavSideBar(navSideBar)를 찾지 못했습니다.");
|
||||
}
|
||||
|
||||
// 모달 UI를 사용하는 UTK 컴포넌트에 루트 설정
|
||||
if (modalUI?.rootVisualElement != null)
|
||||
{
|
||||
UTKAlert.SetRoot(modalUI.rootVisualElement);
|
||||
UTKModal.SetRoot(modalUI.rootVisualElement);
|
||||
UTKNotification.SetRoot(modalUI.rootVisualElement);
|
||||
UTKToast.SetRoot(modalUI.rootVisualElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,109 @@
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.Core;
|
||||
using UVC.EnglewoodLAB.UIToolkit;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
namespace UVC.EnglewoodLAB
|
||||
{
|
||||
public class EWLKSceneMain
|
||||
/// <summary>
|
||||
/// 메인 씬의 초기화 컨트롤러.
|
||||
/// NavSideBar 설정 및 콘텐츠 뷰 전환을 담당합니다.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(90)]
|
||||
public class EWLKSceneMain : SingletonScene<EWLKSceneMain>
|
||||
{
|
||||
|
||||
// ── DI 주입 ─────────────────────────────────────
|
||||
[Inject] private StaticUIDocument? _staticUIDoc;
|
||||
[Inject] private DynamicUIDocument? _dynamicUIDoc;
|
||||
[Inject] private ModalUIDocument? _modalUIDoc;
|
||||
[Inject] private EWLKNavSideBar? _navSideBar;
|
||||
|
||||
// ── UIDocument 편의 접근자 ────────────────────────
|
||||
private UIDocument? StaticUI => _staticUIDoc?.Value;
|
||||
private UIDocument? DynamicUI => _dynamicUIDoc?.Value;
|
||||
private UIDocument? ModalUI => _modalUIDoc?.Value;
|
||||
|
||||
/// <summary>씬 초기화 완료 이벤트</summary>
|
||||
public Action? Initialized;
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// SingletonScene Awake 시 호출됩니다.
|
||||
/// InjectorAppContext 초기화 완료 후 씬을 세팅합니다.
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
InitializeAsync()
|
||||
.Forget(ex => Debug.LogError(ex));
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
if (_navSideBar != null)
|
||||
_navSideBar.OnNavItemSelected -= OnNavItemSelected;
|
||||
}
|
||||
|
||||
// ── 초기화 ──────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// AppContext DI 완료를 기다린 후 씬을 초기화합니다.
|
||||
/// InjectorSceneContext.PerformSceneInjection()은 Start()에서 실행되므로
|
||||
/// WaitForInitializationAsync()로 [Inject] 필드 주입 완료를 보장합니다.
|
||||
/// </summary>
|
||||
private async UniTask InitializeAsync()
|
||||
{
|
||||
await InjectorAppContext.Instance.WaitForInitializationAsync();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
SetupNavSideBar();
|
||||
Initialized?.Invoke();
|
||||
}
|
||||
|
||||
// ── NavSideBar 설정 ──────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// NavSideBar에 메뉴 항목을 설정하고 이벤트를 구독합니다.
|
||||
/// </summary>
|
||||
private void SetupNavSideBar()
|
||||
{
|
||||
if (_navSideBar == null)
|
||||
{
|
||||
Debug.LogWarning("[EWLKSceneMain] EWLKNavSideBar가 주입되지 않았습니다. " +
|
||||
"EWLKSceneContext에서 StaticUI UXML을 확인하세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
_navSideBar.SetMenuItems(new[]
|
||||
{
|
||||
new EWLKNavSideBar.NavItemData("설비목록", UTKMaterialIcons.List),
|
||||
new EWLKNavSideBar.NavItemData("OverView", UTKMaterialIcons.BarChart),
|
||||
new EWLKNavSideBar.NavItemData("제조지시현황", UTKMaterialIcons.Factory),
|
||||
new EWLKNavSideBar.NavItemData("충포장지시현황", UTKMaterialIcons.Inventory2),
|
||||
});
|
||||
|
||||
_navSideBar.OnNavItemSelected += OnNavItemSelected;
|
||||
_navSideBar.SetActiveItem(0); // 첫 번째 항목 기본 활성화
|
||||
}
|
||||
|
||||
// ── 이벤트 처리 ──────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// NavSideBar 항목 선택 시 호출됩니다.
|
||||
/// 인덱스에 따라 DynamicUI의 콘텐츠 뷰를 전환합니다.
|
||||
/// </summary>
|
||||
/// <param name="index">선택된 항목 인덱스</param>
|
||||
private void OnNavItemSelected(int index)
|
||||
{
|
||||
// TODO: 인덱스별 콘텐츠 뷰(DynamicUI) 전환 구현 예정
|
||||
Debug.Log($"[EWLKSceneMain] 메뉴 {index} 선택됨");
|
||||
}
|
||||
}
|
||||
}
|
||||
446
Assets/Scripts/EnglewoodLAB/UIToolkit/EWLKNavSideBar.cs
Normal file
446
Assets/Scripts/EnglewoodLAB/UIToolkit/EWLKNavSideBar.cs
Normal file
@@ -0,0 +1,446 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
namespace UVC.EnglewoodLAB.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// EnglewoodLAB 메인 씬의 좌측 네비게이션 사이드바.
|
||||
/// 열기/닫기 애니메이션과 항목 선택 이벤트를 지원합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var sidebar = root.Q<EWLKNavSideBar>("navSideBar");
|
||||
/// sidebar.SetMenuItems(new[]
|
||||
/// {
|
||||
/// new EWLKNavSideBar.NavItemData("설비목록", UTKMaterialIcons.List),
|
||||
/// new EWLKNavSideBar.NavItemData("OverView", UTKMaterialIcons.BarChart),
|
||||
/// });
|
||||
/// sidebar.OnNavItemSelected += index => Debug.Log($"메뉴 {index} 선택");
|
||||
/// </code>
|
||||
/// </example>
|
||||
[UxmlElement]
|
||||
public partial class EWLKNavSideBar : VisualElement, IDisposable
|
||||
{
|
||||
#region 상수 (Constants)
|
||||
|
||||
private const string USS_PATH = "EWLK/UIToolkit/Main/EWLKNavSideBarUss";
|
||||
private const float ExpandedWidth = 160f;
|
||||
private const float CollapsedWidth = 40f;
|
||||
private const float AnimDuration = 0.25f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 내부 타입 (Inner Types)
|
||||
|
||||
/// <summary>내비게이션 메뉴 항목 데이터</summary>
|
||||
public sealed class NavItemData
|
||||
{
|
||||
/// <summary>표시 이름</summary>
|
||||
public string Label { get; }
|
||||
|
||||
/// <summary>Material Icons 유니코드 문자 (예: UTKMaterialIcons.List)</summary>
|
||||
public string IconChar { get; }
|
||||
|
||||
/// <param name="label">표시 이름</param>
|
||||
/// <param name="iconChar">Material Icons 유니코드 문자</param>
|
||||
public NavItemData(string label, string iconChar)
|
||||
{
|
||||
Label = label;
|
||||
IconChar = iconChar;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 클릭 콜백을 대칭적으로 등록/해제하기 위한 내비게이션 항목 래퍼.
|
||||
/// RegisterCallback ↔ UnregisterCallback 대칭 유지.
|
||||
/// </summary>
|
||||
private sealed class NavItemButton : Button, IDisposable
|
||||
{
|
||||
private readonly EventCallback<ClickEvent> _onClick;
|
||||
private bool _disposed;
|
||||
|
||||
public NavItemButton(EventCallback<ClickEvent> onClick)
|
||||
{
|
||||
_onClick = onClick;
|
||||
RegisterCallback<ClickEvent>(_onClick);
|
||||
}
|
||||
|
||||
public new void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
UnregisterCallback<ClickEvent>(_onClick);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 필드 (Fields)
|
||||
|
||||
private bool _disposed;
|
||||
private bool _isExpanded = true;
|
||||
private Tweener? _animTween;
|
||||
|
||||
// ── 헤더 요소 ─────────────────────────────────
|
||||
private Label? _logoText;
|
||||
private Button? _closeBtn;
|
||||
private EventCallback<ClickEvent>? _onCloseBtnClick;
|
||||
|
||||
// ── 메뉴 컨테이너 ─────────────────────────────
|
||||
private VisualElement? _menuContainer;
|
||||
|
||||
// ── 푸터 요소 ─────────────────────────────────
|
||||
private Label? _toggleIcon;
|
||||
private Button? _toggleBtn;
|
||||
private EventCallback<ClickEvent>? _onToggleBtnClick;
|
||||
|
||||
// ── 아이템 목록 ────────────────────────────────
|
||||
private readonly List<NavItemButton> _navItemBtns = new();
|
||||
private readonly List<Label> _itemLabels = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 이벤트 (Events)
|
||||
|
||||
/// <summary>내비게이션 항목 선택 이벤트 (인덱스 전달)</summary>
|
||||
public event Action<int>? OnNavItemSelected;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 속성 (Properties)
|
||||
|
||||
/// <summary>현재 메뉴 확장 여부</summary>
|
||||
public bool IsExpanded => _isExpanded;
|
||||
|
||||
/// <summary>현재 활성화된 항목 인덱스. 없으면 -1.</summary>
|
||||
public int ActiveIndex { get; private set; } = -1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 생성자 (Constructor)
|
||||
|
||||
public EWLKNavSideBar()
|
||||
{
|
||||
// 1. USS 로드
|
||||
var uss = Resources.Load<StyleSheet>(USS_PATH);
|
||||
if (uss != null)
|
||||
styleSheets.Add(uss);
|
||||
|
||||
// 2. 기본 클래스 설정
|
||||
AddToClassList("ewlk-nav-sidebar");
|
||||
style.width = ExpandedWidth;
|
||||
|
||||
// 3. UI 구성
|
||||
BuildUI();
|
||||
|
||||
// 4. 패널 생명주기 등록
|
||||
RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanel);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI 구성 (Build UI)
|
||||
|
||||
private void BuildUI()
|
||||
{
|
||||
// ── 헤더 ─────────────────────────────────────
|
||||
var header = new VisualElement { name = "nav-header" };
|
||||
header.AddToClassList("ewlk-nav__header");
|
||||
|
||||
// 로고 아이콘 (항상 표시)
|
||||
var logoIcon = new VisualElement { name = "logo-icon" };
|
||||
logoIcon.AddToClassList("ewlk-nav__logo-icon");
|
||||
|
||||
// 로고 텍스트 (확장 시만 표시)
|
||||
_logoText = new Label("Englewood LAB") { name = "logo-text" };
|
||||
_logoText.AddToClassList("ewlk-nav__logo-text");
|
||||
|
||||
// 닫기 버튼 (확장 시만 표시)
|
||||
_closeBtn = new Button { name = "close-btn" };
|
||||
_closeBtn.AddToClassList("ewlk-nav__close-btn");
|
||||
var closeBtnIcon = new Label(UTKMaterialIcons.Close);
|
||||
closeBtnIcon.AddToClassList("ewlk-nav__icon-label");
|
||||
UTKMaterialIcons.ApplyIconStyle(closeBtnIcon, 16);
|
||||
_closeBtn.Add(closeBtnIcon);
|
||||
|
||||
header.Add(logoIcon);
|
||||
header.Add(_logoText);
|
||||
header.Add(_closeBtn);
|
||||
Add(header);
|
||||
|
||||
// ── 구분선 ───────────────────────────────────
|
||||
var divider = new VisualElement { name = "nav-divider" };
|
||||
divider.AddToClassList("ewlk-nav__divider");
|
||||
Add(divider);
|
||||
|
||||
// ── 메뉴 영역 ────────────────────────────────
|
||||
_menuContainer = new VisualElement { name = "nav-menu" };
|
||||
_menuContainer.AddToClassList("ewlk-nav__menu");
|
||||
Add(_menuContainer);
|
||||
|
||||
// ── 푸터 ─────────────────────────────────────
|
||||
var footer = new VisualElement { name = "nav-footer" };
|
||||
footer.AddToClassList("ewlk-nav__footer");
|
||||
|
||||
// 구분선
|
||||
var footerDivider = new VisualElement();
|
||||
footerDivider.AddToClassList("ewlk-nav__divider");
|
||||
footer.Add(footerDivider);
|
||||
|
||||
// 설정 버튼 (고정 Tail 항목)
|
||||
var settingsBtn = CreateNavItemButton(
|
||||
-1,
|
||||
"설정",
|
||||
UTKMaterialIcons.Settings,
|
||||
isTailItem: true
|
||||
);
|
||||
footer.Add(settingsBtn);
|
||||
|
||||
// 토글 버튼 (열기/닫기)
|
||||
_toggleBtn = new Button { name = "toggle-btn" };
|
||||
_toggleBtn.AddToClassList("ewlk-nav__toggle-btn");
|
||||
_toggleIcon = new Label(UTKMaterialIcons.ChevronLeft);
|
||||
_toggleIcon.AddToClassList("ewlk-nav__icon-label");
|
||||
UTKMaterialIcons.ApplyIconStyle(_toggleIcon, 20);
|
||||
_toggleBtn.Add(_toggleIcon);
|
||||
footer.Add(_toggleBtn);
|
||||
|
||||
Add(footer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 내비게이션 항목 Button을 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="index">아이템 인덱스 (-1이면 Tail 항목)</param>
|
||||
/// <param name="labelText">표시 이름</param>
|
||||
/// <param name="iconChar">Material Icons 유니코드 문자</param>
|
||||
/// <param name="isTailItem">Tail 항목 여부</param>
|
||||
private NavItemButton CreateNavItemButton(
|
||||
int index,
|
||||
string labelText,
|
||||
string iconChar,
|
||||
bool isTailItem = false
|
||||
)
|
||||
{
|
||||
int capturedIndex = index;
|
||||
var btn = new NavItemButton(_ => OnNavItemClicked(capturedIndex));
|
||||
btn.AddToClassList("ewlk-nav__item");
|
||||
if (isTailItem)
|
||||
btn.AddToClassList("ewlk-nav__item--tail");
|
||||
|
||||
// 아이콘 레이블
|
||||
var iconLabel = new Label(iconChar);
|
||||
iconLabel.AddToClassList("ewlk-nav__item-icon");
|
||||
UTKMaterialIcons.ApplyIconStyle(iconLabel, 18);
|
||||
|
||||
// 텍스트 레이블
|
||||
var textLabel = new Label(labelText);
|
||||
textLabel.AddToClassList("ewlk-nav__item-label");
|
||||
|
||||
btn.Add(iconLabel);
|
||||
btn.Add(textLabel);
|
||||
|
||||
// Tail 항목이 아닌 경우에만 리스트 추적
|
||||
if (!isTailItem)
|
||||
{
|
||||
_navItemBtns.Add(btn);
|
||||
_itemLabels.Add(textLabel);
|
||||
}
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 공개 메서드 (Public Methods)
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 항목 목록을 설정합니다. 기존 항목을 모두 제거하고 새로 구성합니다.
|
||||
/// </summary>
|
||||
/// <param name="items">표시할 항목 데이터 목록</param>
|
||||
public void SetMenuItems(IReadOnlyList<NavItemData> items)
|
||||
{
|
||||
// 기존 항목 정리
|
||||
foreach (var btn in _navItemBtns)
|
||||
{
|
||||
_menuContainer?.Remove(btn);
|
||||
btn.Dispose();
|
||||
}
|
||||
_navItemBtns.Clear();
|
||||
_itemLabels.Clear();
|
||||
ActiveIndex = -1;
|
||||
|
||||
// 새 항목 추가
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
var btn = CreateNavItemButton(i, items[i].Label, items[i].IconChar);
|
||||
_menuContainer?.Add(btn);
|
||||
}
|
||||
|
||||
// 현재 확장 상태에 맞게 레이블 가시성 동기화
|
||||
UpdateLabelsVisibility(_isExpanded);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정 인덱스 항목을 활성 상태로 표시합니다.
|
||||
/// </summary>
|
||||
/// <param name="index">활성화할 아이템 인덱스</param>
|
||||
public void SetActiveItem(int index)
|
||||
{
|
||||
// 이전 활성 항목 해제
|
||||
if (ActiveIndex >= 0 && ActiveIndex < _navItemBtns.Count)
|
||||
_navItemBtns[ActiveIndex].RemoveFromClassList("ewlk-nav__item--active");
|
||||
|
||||
ActiveIndex = index;
|
||||
|
||||
if (index >= 0 && index < _navItemBtns.Count)
|
||||
_navItemBtns[index].AddToClassList("ewlk-nav__item--active");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 열기/닫기 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="expanded">true이면 열기, false이면 닫기</param>
|
||||
public void SetExpanded(bool expanded)
|
||||
{
|
||||
if (_isExpanded == expanded) return;
|
||||
_isExpanded = expanded;
|
||||
|
||||
AnimateWidth(expanded ? ExpandedWidth : CollapsedWidth);
|
||||
UpdateLabelsVisibility(expanded);
|
||||
UpdateToggleIcon();
|
||||
}
|
||||
|
||||
/// <summary>현재 열기/닫기 상태를 토글합니다.</summary>
|
||||
public void Toggle() => SetExpanded(!_isExpanded);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 이벤트 처리 (Event Handling)
|
||||
|
||||
private void OnAttachToPanel(AttachToPanelEvent evt)
|
||||
{
|
||||
// 닫기 버튼 이벤트 등록
|
||||
_onCloseBtnClick = _ => SetExpanded(false);
|
||||
_closeBtn?.RegisterCallback(_onCloseBtnClick);
|
||||
|
||||
// 토글 버튼 이벤트 등록
|
||||
_onToggleBtnClick = _ => Toggle();
|
||||
_toggleBtn?.RegisterCallback(_onToggleBtnClick);
|
||||
}
|
||||
|
||||
private void OnDetachFromPanel(DetachFromPanelEvent evt)
|
||||
{
|
||||
// 닫기 버튼 이벤트 해제
|
||||
if (_onCloseBtnClick != null)
|
||||
{
|
||||
_closeBtn?.UnregisterCallback(_onCloseBtnClick);
|
||||
_onCloseBtnClick = null;
|
||||
}
|
||||
|
||||
// 토글 버튼 이벤트 해제
|
||||
if (_onToggleBtnClick != null)
|
||||
{
|
||||
_toggleBtn?.UnregisterCallback(_onToggleBtnClick);
|
||||
_onToggleBtnClick = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNavItemClicked(int index)
|
||||
{
|
||||
if (index < 0) return; // 설정 버튼(index == -1) 처리는 외부에서
|
||||
SetActiveItem(index);
|
||||
OnNavItemSelected?.Invoke(index);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 애니메이션 (Animation)
|
||||
|
||||
private void AnimateWidth(float targetWidth)
|
||||
{
|
||||
_animTween?.Kill();
|
||||
_animTween = DOTween
|
||||
.To(
|
||||
() => resolvedStyle.width,
|
||||
x => style.width = x,
|
||||
targetWidth,
|
||||
AnimDuration
|
||||
)
|
||||
.SetEase(Ease.InOutQuad);
|
||||
}
|
||||
|
||||
/// <summary>확장 상태에 따라 로고 텍스트와 항목 레이블 가시성을 설정합니다.</summary>
|
||||
private void UpdateLabelsVisibility(bool visible)
|
||||
{
|
||||
var display = visible ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
|
||||
if (_logoText != null)
|
||||
_logoText.style.display = display;
|
||||
|
||||
if (_closeBtn != null)
|
||||
_closeBtn.style.display = display;
|
||||
|
||||
foreach (var label in _itemLabels)
|
||||
label.style.display = display;
|
||||
}
|
||||
|
||||
/// <summary>확장 상태에 맞게 토글 버튼 아이콘을 갱신합니다.</summary>
|
||||
private void UpdateToggleIcon()
|
||||
{
|
||||
if (_toggleIcon == null) return;
|
||||
// 확장 → ChevronLeft(닫기), 축소 → Menu(열기)
|
||||
_toggleIcon.text = _isExpanded
|
||||
? UTKMaterialIcons.ChevronLeft
|
||||
: UTKMaterialIcons.Menu;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>등록된 이벤트 및 DOTween 리소스를 해제합니다.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
// DOTween 정리
|
||||
_animTween?.Kill();
|
||||
_animTween = null;
|
||||
|
||||
// 생명주기 콜백 해제
|
||||
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanel);
|
||||
|
||||
// 버튼 이벤트 해제
|
||||
if (_onCloseBtnClick != null)
|
||||
{
|
||||
_closeBtn?.UnregisterCallback(_onCloseBtnClick);
|
||||
_onCloseBtnClick = null;
|
||||
}
|
||||
if (_onToggleBtnClick != null)
|
||||
{
|
||||
_toggleBtn?.UnregisterCallback(_onToggleBtnClick);
|
||||
_onToggleBtnClick = null;
|
||||
}
|
||||
|
||||
// 메뉴 항목 정리
|
||||
foreach (var btn in _navItemBtns)
|
||||
btn.Dispose();
|
||||
_navItemBtns.Clear();
|
||||
_itemLabels.Clear();
|
||||
|
||||
// 외부 이벤트 정리
|
||||
OnNavItemSelected = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23f33d4a307cd6546becdf8801da0748
|
||||
Reference in New Issue
Block a user