Compare commits
3 Commits
b7e743c4f4
...
43ab917db0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43ab917db0 | ||
|
|
5712da17d7 | ||
|
|
cfba7fdf53 |
@@ -1,5 +1,5 @@
|
||||
<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:VisualElement name="loadingProgress" class="utk-sample-container" style="height: 1075px;">
|
||||
<ui:VisualElement name="loadingBar" class="utk-sample-container" style="height: 1075px;">
|
||||
<ui:VisualElement class="utk-sample-section" style="translate: 0 1000px; transform-origin: bottom;">
|
||||
<ui:Label text="로딩 중..." name="statusLabel" class="utk-sample-section__title"/>
|
||||
<ui:VisualElement class="utk-sample-row">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,9 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ui:UXML
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:ui="UnityEngine.UIElements"
|
||||
xmlns:uie="UnityEditor.UIElements"
|
||||
xsi:noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
|
||||
>
|
||||
|
||||
</ui:UXML>
|
||||
<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: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
|
||||
@@ -0,0 +1,128 @@
|
||||
/* ============================================================
|
||||
EWLKOverViewModalContent — OverView 모달 테이블 스타일
|
||||
|
||||
레이아웃 구조:
|
||||
.ewlk-overview (테이블 루트, flex-column)
|
||||
.ewlk-overview__header (헤더 행, flex-row)
|
||||
.ewlk-overview__header-cell (헤더 셀)
|
||||
.ewlk-overview__group × 3 (작업장 그룹, flex-row)
|
||||
.ewlk-overview__cell--name (작업장 이름, 2행 span)
|
||||
.ewlk-overview__rows (월간+일간, flex-column)
|
||||
.ewlk-overview__row × 2 (데이터 행, flex-row)
|
||||
.ewlk-overview__cell--period
|
||||
.ewlk-overview__cell--data × 3
|
||||
.ewlk-overview__cell--type
|
||||
============================================================ */
|
||||
|
||||
/* ── 테이블 루트 ─────────────────────────────────────────── */
|
||||
.ewlk-overview {
|
||||
flex-direction: column;
|
||||
background-color: rgb(255, 255, 255);
|
||||
border-width: 1px;
|
||||
border-color: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
/* ── 헤더 행 ─────────────────────────────────────────────── */
|
||||
.ewlk-overview__header {
|
||||
flex-direction: row;
|
||||
min-height: 36px;
|
||||
background-color: rgb(200, 206, 224);
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
/* ── 헤더 셀 공통 ────────────────────────────────────────── */
|
||||
.ewlk-overview__header-cell {
|
||||
-unity-text-align: middle-center;
|
||||
font-size: 12px;
|
||||
-unity-font-style: bold;
|
||||
color: rgb(20, 20, 20);
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
border-right-width: 1px;
|
||||
border-color: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
/* ── 작업장 그룹 ─────────────────────────────────────────── */
|
||||
.ewlk-overview__group {
|
||||
flex-direction: row;
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
/* ── 행 묶음 컨테이너 ────────────────────────────────────── */
|
||||
.ewlk-overview__rows {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ── 데이터 행 ───────────────────────────────────────────── */
|
||||
.ewlk-overview__row {
|
||||
flex-direction: row;
|
||||
min-height: 36px;
|
||||
border-bottom-width: 1px;
|
||||
border-color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.ewlk-overview__rows .ewlk-overview__row:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
/* ── 공통 셀 ─────────────────────────────────────────────── */
|
||||
.ewlk-overview__cell {
|
||||
-unity-text-align: middle-center;
|
||||
font-size: 12px;
|
||||
color: rgb(20, 20, 20);
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right-width: 1px;
|
||||
border-color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
/* ── 셀 종류별 너비 / 배경 ──────────────────────────────── */
|
||||
|
||||
/* 작업장 이름 (헤더의 '구분' + 그룹의 작업장명 — 동일 너비) */
|
||||
.ewlk-overview__cell--name {
|
||||
width: 140px;
|
||||
flex-shrink: 0;
|
||||
-unity-font-style: bold;
|
||||
background-color: rgb(232, 235, 248);
|
||||
border-right-color: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
/* 월간/일간 구분 열 */
|
||||
.ewlk-overview__cell--period {
|
||||
width: 50px;
|
||||
flex-shrink: 0;
|
||||
background-color: rgb(245, 246, 252);
|
||||
border-right-color: rgb(180, 180, 180);
|
||||
}
|
||||
|
||||
/* 목표수량·현시점계획·실적수량 (동등 너비 분할) */
|
||||
.ewlk-overview__cell--data {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
/* 구분 (제조/생산) — 마지막 열, 오른쪽 테두리 제거 */
|
||||
.ewlk-overview__cell--type {
|
||||
width: 60px;
|
||||
flex-shrink: 0;
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
/* 헤더의 name/period/type 셀도 동일 너비 클래스 공유 */
|
||||
.ewlk-overview__header .ewlk-overview__cell--name {
|
||||
background-color: rgb(200, 206, 224);
|
||||
border-right-color: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
.ewlk-overview__header .ewlk-overview__cell--period {
|
||||
background-color: rgb(200, 206, 224);
|
||||
}
|
||||
|
||||
.ewlk-overview__header .ewlk-overview__cell--type {
|
||||
border-right-width: 0;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96af6e40133939f47bca856a7bdc2eaa
|
||||
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}
|
||||
|
||||
63
Assets/Scripts/EnglewoodLAB/Config/EWLKAppConfig.cs
Normal file
63
Assets/Scripts/EnglewoodLAB/Config/EWLKAppConfig.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
#nullable enable
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.EnglewoodLAB.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// StreamingAssets/EWLKAppConfig.json에서 앱 설정을 로드합니다.
|
||||
/// </summary>
|
||||
public class EWLKAppConfig
|
||||
{
|
||||
/// <summary>로드된 설정 인스턴스 (로드 전에는 null)</summary>
|
||||
public static EWLKAppConfig? Config { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// StreamingAssets 폴더의 EWLKAppConfig.json에서 설정을 로드합니다.
|
||||
/// </summary>
|
||||
/// <returns>로드 성공 여부</returns>
|
||||
public static bool LoadConfig()
|
||||
{
|
||||
string path = Path.Combine(Application.streamingAssetsPath, "EWLKAppConfig.json");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Debug.LogError($"[EWLKAppConfig] 설정 파일을 찾을 수 없습니다: {path}");
|
||||
return false;
|
||||
}
|
||||
|
||||
string json = File.ReadAllText(path);
|
||||
Config = JsonConvert.DeserializeObject<EWLKAppConfig>(json);
|
||||
Debug.Log($"[EWLKAppConfig] 설정 로드 완료: {path}");
|
||||
return Config != null;
|
||||
}
|
||||
|
||||
/// <summary>목표 프레임 레이트</summary>
|
||||
[JsonProperty("targetFrameRate")]
|
||||
public int TargetFrameRate { get; set; } = 60;
|
||||
|
||||
/// <summary>MQTT 브로커 설정</summary>
|
||||
[JsonProperty("mqtt")]
|
||||
public EWLKMqttConfig? Mqtt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>MQTT 브로커 연결 설정</summary>
|
||||
public class EWLKMqttConfig
|
||||
{
|
||||
/// <summary>브로커 도메인</summary>
|
||||
[JsonProperty("host")]
|
||||
public string Host { get; set; } = "localhost";
|
||||
|
||||
/// <summary>브로커 포트</summary>
|
||||
[JsonProperty("port")]
|
||||
public int Port { get; set; } = 1883;
|
||||
|
||||
/// <summary>JSON 페이로드에서 데이터를 담고 있는 키</summary>
|
||||
[JsonProperty("dataKey")]
|
||||
public string DataKey { get; set; } = "data";
|
||||
|
||||
/// <summary>MessagePack 인코딩 사용 여부</summary>
|
||||
[JsonProperty("messagePack")]
|
||||
public bool MessagePack { get; set; } = false;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/EnglewoodLAB/Config/EWLKAppConfig.cs.meta
Normal file
2
Assets/Scripts/EnglewoodLAB/Config/EWLKAppConfig.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 453b4a2183338f94986518da60656b9b
|
||||
21
Assets/Scripts/EnglewoodLAB/Config/EWLKConstants.cs
Normal file
21
Assets/Scripts/EnglewoodLAB/Config/EWLKConstants.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace UVC.EnglewoodLAB.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// 앱 설정에서 로드된 런타임 상수.
|
||||
/// EWLKAppMain.Init()에서 EWLKAppConfig 로드 후 설정됩니다.
|
||||
/// </summary>
|
||||
public static class EWLKConstants
|
||||
{
|
||||
/// <summary>MQTT 브로커 도메인</summary>
|
||||
public static string MQTT_DOMAIN = "localhost";
|
||||
|
||||
/// <summary>MQTT 브로커 포트</summary>
|
||||
public static int MQTT_PORT = 1883;
|
||||
|
||||
/// <summary>MQTT JSON에서 데이터를 담고 있는 키</summary>
|
||||
public static string MQTT_DATA_KEY = "data";
|
||||
|
||||
/// <summary>MQTT MessagePack 인코딩 사용 여부</summary>
|
||||
public static bool MQTT_MESSAGEPACK_ENABLED = false;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/EnglewoodLAB/Config/EWLKConstants.cs.meta
Normal file
2
Assets/Scripts/EnglewoodLAB/Config/EWLKConstants.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f9b7dbf83096114da5ae5d71bbcc9ff
|
||||
8
Assets/Scripts/EnglewoodLAB/Data.meta
Normal file
8
Assets/Scripts/EnglewoodLAB/Data.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24792ae178280d9449c2151aa8e38e8c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
49
Assets/Scripts/EnglewoodLAB/Data/EWLKOverViewData.cs
Normal file
49
Assets/Scripts/EnglewoodLAB/Data/EWLKOverViewData.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
#nullable enable
|
||||
|
||||
namespace UVC.EnglewoodLAB.Data
|
||||
{
|
||||
/// <summary>월간/일간 단위 생산 실적 데이터</summary>
|
||||
public class EWLKOverViewPeriodData
|
||||
{
|
||||
/// <summary>목표수량</summary>
|
||||
public string Target { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>현시점 계획</summary>
|
||||
public string Plan { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>실적수량</summary>
|
||||
public string Actual { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>구분 (제조/생산 등)</summary>
|
||||
public string Type { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>작업장별 OverView 데이터 (월간 + 일간)</summary>
|
||||
public class EWLKOverViewWorkshopData
|
||||
{
|
||||
/// <summary>작업장 이름 (예: 제조작업장, 충포장작업장(3F))</summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>월간 실적</summary>
|
||||
public EWLKOverViewPeriodData Monthly { get; } = new();
|
||||
|
||||
/// <summary>일간 실적</summary>
|
||||
public EWLKOverViewPeriodData Daily { get; } = new();
|
||||
|
||||
/// <param name="name">작업장 이름</param>
|
||||
public EWLKOverViewWorkshopData(string name) => Name = name;
|
||||
}
|
||||
|
||||
/// <summary>OverView 화면 전체 데이터 (3개 작업장)</summary>
|
||||
public class EWLKOverViewData
|
||||
{
|
||||
/// <summary>제조작업장</summary>
|
||||
public EWLKOverViewWorkshopData Manufacture { get; } = new("제조작업장");
|
||||
|
||||
/// <summary>충포장작업장(3F)</summary>
|
||||
public EWLKOverViewWorkshopData Packing3F { get; } = new("충포장작업장(3F)");
|
||||
|
||||
/// <summary>충포장작업장(4F)</summary>
|
||||
public EWLKOverViewWorkshopData Packing4F { get; } = new("충포장작업장(4F)");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 677e0385b85098c499cbc1178b672f39
|
||||
133
Assets/Scripts/EnglewoodLAB/Data/EWLKOverViewMqttService.cs
Normal file
133
Assets/Scripts/EnglewoodLAB/Data/EWLKOverViewMqttService.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UVC.Data;
|
||||
using UVC.Data.Core;
|
||||
using UVC.Data.Mqtt;
|
||||
|
||||
namespace UVC.EnglewoodLAB.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// MES/ERP OverView 데이터를 MQTT로 수신하는 서비스.
|
||||
/// 3개 작업장(제조작업장, 충포장3F/4F)의 월간·일간 생산 실적을 구독합니다.
|
||||
/// DataRepository.Instance.MqttReceiver(공유 수신기)를 사용합니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// MQTT JSON 페이로드 스키마:
|
||||
/// <code>
|
||||
/// {
|
||||
/// "monthly_target": "25,000 kg(15 BT)",
|
||||
/// "monthly_plan": "20,000 kg(10 BT)",
|
||||
/// "monthly_actual": "20,000 kg(10 BT)",
|
||||
/// "daily_target": "3,000 kg(2BT)",
|
||||
/// "daily_plan": "1,000 kg(1 BT)",
|
||||
/// "daily_actual": "1,000 kg(1 BT)",
|
||||
/// "type": "제조"
|
||||
/// }
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public class EWLKOverViewMqttService : IDisposable
|
||||
{
|
||||
// ── MQTT 토픽 (MES/ERP 연동) ──────────────────────────────
|
||||
private const string TopicManufacture = "ewlk/overview/manufacture";
|
||||
private const string TopicPacking3F = "ewlk/overview/packing3f";
|
||||
private const string TopicPacking4F = "ewlk/overview/packing4f";
|
||||
|
||||
// ── DataMask (공통 스키마) ─────────────────────────────────
|
||||
private static readonly DataMask s_Mask = CreateMask();
|
||||
|
||||
private bool _subscribed;
|
||||
private bool _disposed;
|
||||
|
||||
// ── 공개 API ──────────────────────────────────────────────
|
||||
|
||||
/// <summary>가장 최근에 수신한 OverView 데이터 (초기값: 빈 문자열)</summary>
|
||||
public EWLKOverViewData CurrentData { get; } = new();
|
||||
|
||||
/// <summary>작업장 데이터가 갱신될 때 발생 — 메인 스레드에서 호출됩니다.</summary>
|
||||
public event Action<EWLKOverViewData>? OnDataUpdated;
|
||||
|
||||
// ── 초기화 ────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// DataRepository 공유 수신기에 3개 작업장 토픽을 등록합니다.
|
||||
/// MqttReceiver.Start()는 EWLKSceneMain에서 호출합니다.
|
||||
/// </summary>
|
||||
public void Subscribe()
|
||||
{
|
||||
if (_subscribed || _disposed) return;
|
||||
_subscribed = true;
|
||||
|
||||
var receiver = DataRepository.Instance.MqttReceiver;
|
||||
receiver.Add(BuildConfig(TopicManufacture,
|
||||
data => UpdateWorkshop(CurrentData.Manufacture, data)));
|
||||
receiver.Add(BuildConfig(TopicPacking3F,
|
||||
data => UpdateWorkshop(CurrentData.Packing3F, data)));
|
||||
receiver.Add(BuildConfig(TopicPacking4F,
|
||||
data => UpdateWorkshop(CurrentData.Packing4F, data)));
|
||||
}
|
||||
|
||||
/// <summary>DataRepository 공유 수신기에서 토픽 구독을 해제합니다.</summary>
|
||||
public void Unsubscribe()
|
||||
{
|
||||
if (!_subscribed) return;
|
||||
_subscribed = false;
|
||||
|
||||
var receiver = DataRepository.Instance.MqttReceiver;
|
||||
receiver.Remove(TopicManufacture);
|
||||
receiver.Remove(TopicPacking3F);
|
||||
receiver.Remove(TopicPacking4F);
|
||||
}
|
||||
|
||||
// ── 내부 구현 ─────────────────────────────────────────────
|
||||
|
||||
private static DataMask CreateMask()
|
||||
{
|
||||
var mask = new DataMask();
|
||||
mask["monthly_target"] = "";
|
||||
mask["monthly_plan"] = "";
|
||||
mask["monthly_actual"] = "";
|
||||
mask["daily_target"] = "";
|
||||
mask["daily_plan"] = "";
|
||||
mask["daily_actual"] = "";
|
||||
mask["type"] = "";
|
||||
return mask;
|
||||
}
|
||||
|
||||
private static MqttSubscriptionConfig BuildConfig(string topic, Action<IDataObject?> handler) =>
|
||||
new MqttSubscriptionConfig(topic)
|
||||
.SetDataMapper(new DataMapper(s_Mask))
|
||||
.SetHandler(handler);
|
||||
|
||||
/// <summary>
|
||||
/// 수신된 IDataObject를 DataObject로 캐스팅하여 작업장 데이터를 갱신합니다.
|
||||
/// 이 메서드는 메인 스레드에서 호출됩니다 (MqttDataReceiver가 UniTask.Post로 보장).
|
||||
/// </summary>
|
||||
private void UpdateWorkshop(EWLKOverViewWorkshopData workshop, IDataObject? data)
|
||||
{
|
||||
if (data is not DataObject obj) return;
|
||||
|
||||
workshop.Monthly.Target = obj.GetString("monthly_target") ?? string.Empty;
|
||||
workshop.Monthly.Plan = obj.GetString("monthly_plan") ?? string.Empty;
|
||||
workshop.Monthly.Actual = obj.GetString("monthly_actual") ?? string.Empty;
|
||||
workshop.Monthly.Type = obj.GetString("type") ?? string.Empty;
|
||||
|
||||
workshop.Daily.Target = obj.GetString("daily_target") ?? string.Empty;
|
||||
workshop.Daily.Plan = obj.GetString("daily_plan") ?? string.Empty;
|
||||
workshop.Daily.Actual = obj.GetString("daily_actual") ?? string.Empty;
|
||||
workshop.Daily.Type = obj.GetString("type") ?? string.Empty;
|
||||
|
||||
OnDataUpdated?.Invoke(CurrentData);
|
||||
}
|
||||
|
||||
// ── IDisposable ───────────────────────────────────────────
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
Unsubscribe();
|
||||
OnDataUpdated = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 695c3a78b9151da4cb71ca0a276e7c87
|
||||
@@ -1,6 +1,79 @@
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UVC.Core;
|
||||
using UVC.Data;
|
||||
using UVC.Data.Mqtt;
|
||||
using UVC.EnglewoodLAB.Config;
|
||||
|
||||
public class EWLKAppMain
|
||||
namespace UVC.EnglewoodLAB
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// EnglewoodLAB 앱의 진입점 (싱글톤).
|
||||
/// 설정 로드, MQTT 브로커 설정을 담당합니다.
|
||||
/// [DefaultExecutionOrder(100)]: SceneContext/SceneMain의 Awake보다 나중에 실행됩니다.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(100)]
|
||||
public class EWLKAppMain : SingletonApp<EWLKAppMain>
|
||||
{
|
||||
/// <summary>초기화 완료 후 발생하는 이벤트</summary>
|
||||
public System.Action? Initialized;
|
||||
|
||||
/// <summary>
|
||||
/// SingletonApp Awake 시 호출됩니다.
|
||||
/// 설정을 로드하고 네트워크(MQTT)를 구성합니다.
|
||||
/// </summary>
|
||||
protected override async void Init()
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
|
||||
await SetupConfigAsync();
|
||||
SetNetworkConfig();
|
||||
|
||||
Initialized?.Invoke();
|
||||
}
|
||||
|
||||
// ── 설정 로드 ─────────────────────────────────────────────
|
||||
|
||||
private UniTask SetupConfigAsync()
|
||||
{
|
||||
if (EWLKAppConfig.LoadConfig())
|
||||
{
|
||||
var cfg = EWLKAppConfig.Config!;
|
||||
Application.targetFrameRate = cfg.TargetFrameRate;
|
||||
|
||||
if (cfg.Mqtt != null)
|
||||
{
|
||||
EWLKConstants.MQTT_DOMAIN = cfg.Mqtt.Host;
|
||||
EWLKConstants.MQTT_PORT = cfg.Mqtt.Port;
|
||||
EWLKConstants.MQTT_DATA_KEY = cfg.Mqtt.DataKey;
|
||||
EWLKConstants.MQTT_MESSAGEPACK_ENABLED = cfg.Mqtt.MessagePack;
|
||||
}
|
||||
}
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
// ── 네트워크 설정 ─────────────────────────────────────────
|
||||
|
||||
private void SetNetworkConfig()
|
||||
{
|
||||
bool useWebSocket = false;
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
EWLKConstants.MQTT_PORT = 8083;
|
||||
useWebSocket = true;
|
||||
#endif
|
||||
|
||||
DataRepository.Instance.MqttReceiver
|
||||
.SetDataPicker(new MqttDataPicker(
|
||||
EWLKConstants.MQTT_DATA_KEY,
|
||||
EWLKConstants.MQTT_MESSAGEPACK_ENABLED));
|
||||
|
||||
DataRepository.Instance.MqttReceiver
|
||||
.SetDomainPort(EWLKConstants.MQTT_DOMAIN, EWLKConstants.MQTT_PORT);
|
||||
|
||||
DataRepository.Instance.MqttReceiver
|
||||
.SetUseWebSocket(useWebSocket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,98 @@
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.Core;
|
||||
using UVC.EnglewoodLAB.Data;
|
||||
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 세 개, NavSideBar, OverView MQTT 서비스를 등록합니다.
|
||||
/// </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);
|
||||
}
|
||||
|
||||
// OverView MQTT 서비스 등록 (토픽 구독 — 브로커 연결은 EWLKSceneMain.Initialize()에서)
|
||||
var overViewMqtt = new EWLKOverViewMqttService();
|
||||
overViewMqtt.Subscribe();
|
||||
Injector.RegisterInstance<EWLKOverViewMqttService>(overViewMqtt, 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,151 @@
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.Core;
|
||||
using UVC.Data;
|
||||
using UVC.EnglewoodLAB.Data;
|
||||
using UVC.EnglewoodLAB.UIToolkit;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
namespace UVC.EnglewoodLAB
|
||||
{
|
||||
public class EWLKSceneMain
|
||||
/// <summary>
|
||||
/// 메인 씬의 초기화 컨트롤러.
|
||||
/// NavSideBar 설정, 콘텐츠 뷰 전환, OverView 모달 표시를 담당합니다.
|
||||
/// </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;
|
||||
[Inject] private EWLKOverViewMqttService? _overViewMqtt;
|
||||
|
||||
// ── 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()
|
||||
{
|
||||
DataRepository.Instance.MqttReceiver.Start();
|
||||
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;
|
||||
}
|
||||
|
||||
// ── 이벤트 처리 ──────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// NavSideBar 항목 선택 시 호출됩니다.
|
||||
/// </summary>
|
||||
/// <param name="index">선택된 항목 인덱스</param>
|
||||
private void OnNavItemSelected(int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 1: // OverView
|
||||
ShowOverViewModalAsync()
|
||||
.Forget(ex => Debug.LogError(ex));
|
||||
break;
|
||||
default:
|
||||
// TODO: 인덱스별 콘텐츠 뷰(DynamicUI) 전환 구현 예정
|
||||
Debug.Log($"[EWLKSceneMain] 메뉴 {index} 선택됨");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ── OverView 모달 ────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// OverView 모달을 열고 MQTT 실시간 데이터를 바인딩합니다.
|
||||
/// 모달이 닫히면 이벤트 바인딩을 해제합니다.
|
||||
/// </summary>
|
||||
private async UniTask ShowOverViewModalAsync()
|
||||
{
|
||||
var content = new EWLKOverViewModalContent();
|
||||
|
||||
// 현재 캐시된 데이터로 즉시 표시 (MQTT 미연결 시 빈 테이블)
|
||||
if (_overViewMqtt != null)
|
||||
{
|
||||
content.UpdateData(_overViewMqtt.CurrentData);
|
||||
_overViewMqtt.OnDataUpdated += content.UpdateData;
|
||||
}
|
||||
|
||||
var modal = UTKModal.Create("OVERVIEW", UTKModal.ModalSize.Large);
|
||||
modal.Add(content);
|
||||
await modal.ShowAsync();
|
||||
|
||||
// 모달 닫힌 후 정리
|
||||
if (_overViewMqtt != null)
|
||||
_overViewMqtt.OnDataUpdated -= content.UpdateData;
|
||||
|
||||
content.Dispose();
|
||||
|
||||
// 메뉴 선택 상태 해제 (모달이 닫히면 활성화 항목 없음)
|
||||
_navSideBar?.SetActiveItem(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -0,0 +1,167 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.EnglewoodLAB.Data;
|
||||
|
||||
namespace UVC.EnglewoodLAB.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// OverView 모달 내부 테이블 컨텐츠.
|
||||
/// 3개 작업장의 월간/일간 생산 실적(목표수량·현시점 계획·실적수량·구분)을 표시합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var content = new EWLKOverViewModalContent();
|
||||
/// content.UpdateData(mqttService.CurrentData);
|
||||
/// mqttService.OnDataUpdated += content.UpdateData;
|
||||
///
|
||||
/// var modal = UTKModal.Create("OVERVIEW", UTKModal.ModalSize.Large);
|
||||
/// modal.Add(content);
|
||||
/// await modal.ShowAsync();
|
||||
///
|
||||
/// mqttService.OnDataUpdated -= content.UpdateData;
|
||||
/// content.Dispose();
|
||||
/// </code>
|
||||
/// </example>
|
||||
[UxmlElement]
|
||||
public partial class EWLKOverViewModalContent : VisualElement, IDisposable
|
||||
{
|
||||
private const string USS_PATH = "EWLK/UIToolkit/Main/EWLKOverViewModalContentUss";
|
||||
|
||||
// 데이터 셀 참조 캐시 [작업장, 기간, 컬럼]
|
||||
// 작업장: 0=제조작업장, 1=충포장(3F), 2=충포장(4F)
|
||||
// 기간: 0=월간, 1=일간
|
||||
// 컬럼: 0=목표수량, 1=현시점계획, 2=실적수량, 3=구분
|
||||
private readonly Label[,,] _cells = new Label[3, 2, 4];
|
||||
|
||||
public EWLKOverViewModalContent()
|
||||
{
|
||||
var uss = Resources.Load<StyleSheet>(USS_PATH);
|
||||
if (uss != null) styleSheets.Add(uss);
|
||||
|
||||
AddToClassList("ewlk-overview");
|
||||
BuildUI();
|
||||
}
|
||||
|
||||
// ── UI 구성 ───────────────────────────────────────────────
|
||||
|
||||
private void BuildUI()
|
||||
{
|
||||
Add(BuildHeader());
|
||||
|
||||
string[] names = { "제조작업장", "충포장작업장(3F)", "충포장작업장(4F)" };
|
||||
for (int wi = 0; wi < 3; wi++)
|
||||
Add(BuildWorkshopGroup(wi, names[wi]));
|
||||
}
|
||||
|
||||
/// <summary>헤더 행을 생성합니다.</summary>
|
||||
private static VisualElement BuildHeader()
|
||||
{
|
||||
var header = new VisualElement();
|
||||
header.AddToClassList("ewlk-overview__header");
|
||||
|
||||
header.Add(MakeHeaderCell("구분", "ewlk-overview__cell--name"));
|
||||
header.Add(MakeHeaderCell("", "ewlk-overview__cell--period"));
|
||||
header.Add(MakeHeaderCell("목표수량", "ewlk-overview__cell--data"));
|
||||
header.Add(MakeHeaderCell("현시점 계획", "ewlk-overview__cell--data"));
|
||||
header.Add(MakeHeaderCell("실적수량", "ewlk-overview__cell--data"));
|
||||
header.Add(MakeHeaderCell("구분", "ewlk-overview__cell--type"));
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 작업장 행 그룹을 생성합니다.
|
||||
/// 왼쪽에 작업장 이름이 2행(월간+일간)에 걸쳐 표시됩니다.
|
||||
/// </summary>
|
||||
private VisualElement BuildWorkshopGroup(int wi, string name)
|
||||
{
|
||||
var group = new VisualElement();
|
||||
group.AddToClassList("ewlk-overview__group");
|
||||
|
||||
// 왼쪽: 작업장 이름 셀 (2행 height 걸쳐 세로 중앙 정렬)
|
||||
var nameCell = new Label(name);
|
||||
nameCell.AddToClassList("ewlk-overview__cell");
|
||||
nameCell.AddToClassList("ewlk-overview__cell--name");
|
||||
group.Add(nameCell);
|
||||
|
||||
// 오른쪽: 월간/일간 행 컨테이너
|
||||
var rows = new VisualElement();
|
||||
rows.AddToClassList("ewlk-overview__rows");
|
||||
|
||||
string[] periodLabels = { "월간", "일간" };
|
||||
for (int pi = 0; pi < 2; pi++)
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.AddToClassList("ewlk-overview__row");
|
||||
|
||||
// 기간 레이블 (월간/일간)
|
||||
var periodLabel = new Label(periodLabels[pi]);
|
||||
periodLabel.AddToClassList("ewlk-overview__cell");
|
||||
periodLabel.AddToClassList("ewlk-overview__cell--period");
|
||||
row.Add(periodLabel);
|
||||
|
||||
// 데이터 셀 4개: 목표수량·현시점계획·실적수량·구분
|
||||
for (int ci = 0; ci < 4; ci++)
|
||||
{
|
||||
var cell = new Label();
|
||||
cell.AddToClassList("ewlk-overview__cell");
|
||||
cell.AddToClassList(ci < 3
|
||||
? "ewlk-overview__cell--data"
|
||||
: "ewlk-overview__cell--type");
|
||||
_cells[wi, pi, ci] = cell;
|
||||
row.Add(cell);
|
||||
}
|
||||
|
||||
rows.Add(row);
|
||||
}
|
||||
|
||||
group.Add(rows);
|
||||
return group;
|
||||
}
|
||||
|
||||
private static Label MakeHeaderCell(string text, string sizeClass)
|
||||
{
|
||||
var label = new Label(text);
|
||||
label.AddToClassList("ewlk-overview__header-cell");
|
||||
label.AddToClassList(sizeClass);
|
||||
return label;
|
||||
}
|
||||
|
||||
// ── 데이터 갱신 ───────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// OverView 데이터로 테이블을 갱신합니다.
|
||||
/// MQTT 데이터 수신 시 또는 모달 열릴 때 호출합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">갱신할 OverView 데이터</param>
|
||||
public void UpdateData(EWLKOverViewData data)
|
||||
{
|
||||
var workshops = new[]
|
||||
{
|
||||
data.Manufacture,
|
||||
data.Packing3F,
|
||||
data.Packing4F,
|
||||
};
|
||||
|
||||
for (int wi = 0; wi < 3; wi++)
|
||||
{
|
||||
ApplyPeriod(wi, 0, workshops[wi].Monthly);
|
||||
ApplyPeriod(wi, 1, workshops[wi].Daily);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyPeriod(int wi, int pi, EWLKOverViewPeriodData period)
|
||||
{
|
||||
_cells[wi, pi, 0].text = period.Target;
|
||||
_cells[wi, pi, 1].text = period.Plan;
|
||||
_cells[wi, pi, 2].text = period.Actual;
|
||||
_cells[wi, pi, 3].text = period.Type;
|
||||
}
|
||||
|
||||
// ── IDisposable ───────────────────────────────────────────
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d04fa3104d5991c4a92464244bfd962b
|
||||
9
Assets/StreamingAssets/EWLKAppConfig.json
Normal file
9
Assets/StreamingAssets/EWLKAppConfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"targetFrameRate": 60,
|
||||
"mqtt": {
|
||||
"host": "localhost",
|
||||
"port": 1883,
|
||||
"dataKey": "data",
|
||||
"messagePack": false
|
||||
}
|
||||
}
|
||||
7
Assets/StreamingAssets/EWLKAppConfig.json.meta
Normal file
7
Assets/StreamingAssets/EWLKAppConfig.json.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b272a9c533455349b8266b72f02c5ab
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user