781 lines
28 KiB
C#
781 lines
28 KiB
C#
#nullable enable
|
|
using Cysharp.Threading.Tasks;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace SHI.Modal.NW
|
|
{
|
|
/// <summary>
|
|
/// NW(네트워크 다이어그램) 모달 창을 관리하는 메인 컨트롤러입니다.
|
|
///
|
|
/// <para><b>개요:</b></para>
|
|
/// <para>
|
|
/// TreeList, NWModelView, NWChart 세 가지 뷰를 통합 관리하며,
|
|
/// 뷰 간의 선택 동기화, 드래그를 통한 뷰 크기 조절, 확장/축소 기능을 제공합니다.
|
|
/// ISOPModal과 유사하나, 수직 레이아웃(ModelView 위, Chart 아래)으로 구성됩니다.
|
|
/// </para>
|
|
///
|
|
/// <para><b>주요 기능:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item>glTF 모델과 네트워크 차트 데이터 동시 로드</item>
|
|
/// <item>TreeList ↔ ModelView ↔ Chart 선택 동기화</item>
|
|
/// <item>드래그 버튼으로 ModelView/Chart 크기 비율 조절 (수직)</item>
|
|
/// <item>개별 뷰 전체화면 확장/복원</item>
|
|
/// <item>TreeList 가시성 토글 시 3D 모델 가시성 동기화</item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>UI 구조:</b></para>
|
|
/// <code>
|
|
/// NWModal (UXML)
|
|
/// ├── TreeList (왼쪽 패널) - 모델 계층 구조
|
|
/// └── right-panel (오른쪽)
|
|
/// ├── NWModelView (상단) - 3D 모델 뷰어
|
|
/// ├── drag-btn (구분선) - 수직 드래그로 크기 조절
|
|
/// └── NWChart (하단) - 네트워크 다이어그램
|
|
/// </code>
|
|
///
|
|
/// <para><b>ISOPModal과의 차이점:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item>레이아웃: 수평(ISOP) vs 수직(NW)</item>
|
|
/// <item>차트 타입: 간트 차트(ISOP) vs 네트워크 다이어그램(NW)</item>
|
|
/// <item>드래그 방향: X축(ISOP) vs Y축(NW)</item>
|
|
/// </list>
|
|
/// </summary>
|
|
public class NWModal : MonoBehaviour
|
|
{
|
|
[SerializeField]
|
|
public UIDocument uiDocument;
|
|
|
|
private TreeList? listView;
|
|
private NWModelView? modelView;
|
|
private NWChart? chartView;
|
|
private PlayBar? _playBar;
|
|
|
|
private Button? closeBtn;
|
|
private Button? showTreeBtn;
|
|
private Button? dragBtn;
|
|
|
|
private VisualElement? rightPanel;
|
|
|
|
private CancellationTokenSource? _cts;
|
|
private bool _suppressSelection = false;
|
|
|
|
// key<->id 매핑(차트-리스트/모델 동기화)
|
|
private readonly Dictionary<string, int> _keyToId = new Dictionary<string, int>();
|
|
private readonly Dictionary<int, string> _idToKey = new Dictionary<int, string>();
|
|
|
|
private int selectedItemId = -1;
|
|
|
|
private enum ExpandedSide { None, Model, Chart }
|
|
private ExpandedSide _expanded = ExpandedSide.None;
|
|
|
|
// 드래그 상태 저장
|
|
private bool _isDragging = false;
|
|
private int _activePointerId = -1;
|
|
private float _dragOffset = 0f; // 포인터와 drag 버튼 중심 간 오프셋
|
|
|
|
// 확장 전 비율 저장
|
|
private float _lastModelFlexGrow = 1f;
|
|
private float _lastChartFlexGrow = 1f;
|
|
|
|
// GeometryChangedEvent 콜백 (해제용)
|
|
private EventCallback<GeometryChangedEvent>? _rightPanelGeometryChangedCallback;
|
|
|
|
// 로딩 UI
|
|
private VisualElement? _loadingOverlay;
|
|
private VisualElement? _loadingSpinner;
|
|
private IVisualElementScheduledItem? _spinnerAnimation;
|
|
|
|
private void OnEnable()
|
|
{
|
|
OnDestroy(); // 중복 초기화 방지
|
|
|
|
var root = uiDocument.rootVisualElement;
|
|
|
|
// UXML에서 <TreeList> 태그로 추가했다면 Query로 찾음
|
|
listView = root.Q<TreeList>();
|
|
|
|
if (listView != null)
|
|
{
|
|
listView.OnSelectionChanged += OnListItemSelectionChanged;
|
|
listView.OnClosed += OnListClosed;
|
|
listView.OnVisibilityChanged += OnListVisibilityChanged;
|
|
|
|
// listView가 다른 요소 위에 표시되도록 설정
|
|
listView.style.unityOverflowClipBox = OverflowClipBox.ContentBox;
|
|
|
|
// 더미 데이터 생성
|
|
// var data = GenerateDummyData();
|
|
// listView.SetData(data);
|
|
}
|
|
|
|
modelView = root.Q<NWModelView>();
|
|
if (modelView != null)
|
|
{
|
|
// 선택 동기화: 모델 -> 리스트/차트
|
|
modelView.OnItemSelected += OnModelItemSelected;
|
|
modelView.OnExpand += ToggleExpandModel;
|
|
|
|
// modelView의 내용이 범위를 벗어나지 않도록 overflow 설정
|
|
modelView.style.overflow = Overflow.Hidden;
|
|
}
|
|
|
|
|
|
chartView = root.Q<NWChart>();
|
|
if (chartView != null)
|
|
{
|
|
chartView.OnExpand += ToggleExpandChart;
|
|
}
|
|
|
|
rightPanel = root.Q<VisualElement>("right-panel");
|
|
|
|
showTreeBtn = root.Q<Button>("show-tree-btn");
|
|
|
|
if (showTreeBtn != null)
|
|
{
|
|
|
|
showTreeBtn.clicked += OnClickShowTree;
|
|
|
|
showTreeBtn.style.display = DisplayStyle.None;
|
|
}
|
|
|
|
closeBtn = root.Q<Button>("closeButton");
|
|
if (closeBtn != null)
|
|
{
|
|
closeBtn.clicked += OnClickClose;
|
|
}
|
|
|
|
_playBar = root.Q<PlayBar>("play-bar");
|
|
if (_playBar != null)
|
|
{
|
|
_playBar.SetTimeRange(DateTime.Now, DateTime.Now.AddHours(1));
|
|
_playBar.OnPlayProgress += OnPlayProgressHandler;
|
|
_playBar.OnPositionChanged += OnPlayPositionChangedHandler;
|
|
}
|
|
|
|
initDrag(root);
|
|
|
|
_expanded = ExpandedSide.None;
|
|
|
|
// 로딩 UI 참조
|
|
_loadingOverlay = root.Q<VisualElement>("loading-overlay");
|
|
_loadingSpinner = root.Q<VisualElement>("loading-spinner");
|
|
}
|
|
|
|
private void OnPlayProgressHandler(DateTime time)
|
|
{
|
|
if(chartView != null && listView != null)
|
|
{
|
|
List<string> models = chartView.GetModelNamesByDate(time.ToString("yyyyMMdd"));
|
|
Debug.Log($"Models at {time:yyyyMMdd}: {string.Join(", ", models)}");
|
|
if(models.Count > 0) listView.ShowItems(models);
|
|
}
|
|
}
|
|
|
|
private void OnPlayPositionChangedHandler(DateTime time)
|
|
{
|
|
if(chartView != null && listView != null)
|
|
{
|
|
List<string> models = chartView.GetModelNamesByDate(time.ToString("yyyyMMdd"));
|
|
Debug.Log($"Models at {time:yyyyMMdd}: {string.Join(", ", models)}");
|
|
if(models.Count > 0) listView.ShowItems(models);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void initDrag(VisualElement root)
|
|
{
|
|
dragBtn = root.Q<Button>("drag-btn");
|
|
if (dragBtn != null && rightPanel != null)
|
|
{
|
|
if (modelView != null)
|
|
{
|
|
// dragBtn이 절대 위치로 움직일 수 있도록 설정
|
|
dragBtn.style.position = Position.Absolute;
|
|
dragBtn.pickingMode = PickingMode.Position;
|
|
|
|
// 드래그 이벤트 등록
|
|
dragBtn.RegisterCallback<PointerDownEvent>(OnDragPointerDown, TrickleDown.TrickleDown);
|
|
dragBtn.RegisterCallback<PointerMoveEvent>(OnDragPointerMove, TrickleDown.TrickleDown);
|
|
dragBtn.RegisterCallback<PointerUpEvent>(OnDragPointerUp, TrickleDown.TrickleDown);
|
|
dragBtn.RegisterCallback<PointerCancelEvent>(OnDragPointerCancel, TrickleDown.TrickleDown);
|
|
|
|
// 초기화 및 레이아웃 변경 시 재계산
|
|
_rightPanelGeometryChangedCallback = OnRightPanelGeometryChanged;
|
|
rightPanel.RegisterCallback(_rightPanelGeometryChangedCallback);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnDragPointerDown(PointerDownEvent evt)
|
|
{
|
|
// 좌클릭만 처리 (0)
|
|
if (evt.button != 0) return;
|
|
|
|
Debug.Log("Drag Started (PointerDown) - captured");
|
|
|
|
// 포인터 캡처
|
|
_isDragging = true;
|
|
_activePointerId = evt.pointerId;
|
|
dragBtn?.CapturePointer(_activePointerId);
|
|
|
|
// 포인터가 drag 버튼의 어느 위치를 눌렀는지 계산 (center 기준, Y축)
|
|
if (dragBtn != null)
|
|
{
|
|
var dragCenterY = dragBtn.layout.y + dragBtn.layout.height * 0.5f;
|
|
_dragOffset = evt.position.y - dragCenterY;
|
|
}
|
|
|
|
evt.StopImmediatePropagation();
|
|
}
|
|
|
|
private void OnDragPointerMove(PointerMoveEvent evt)
|
|
{
|
|
if (!_isDragging) return;
|
|
if (evt.pointerId != _activePointerId) return;
|
|
|
|
// evt.position은 rightPanel 기준 좌표
|
|
float pointerY = evt.position.y;
|
|
float centerY = pointerY - _dragOffset;
|
|
if (rightPanel != null && dragBtn != null)
|
|
{
|
|
ApplyDragPosition(rightPanel, dragBtn, centerY);
|
|
}
|
|
evt.StopImmediatePropagation();
|
|
}
|
|
|
|
private void OnDragPointerUp(PointerUpEvent evt)
|
|
{
|
|
if (!_isDragging) return;
|
|
if (evt.pointerId != _activePointerId) return;
|
|
|
|
Debug.Log("Drag Ended");
|
|
|
|
_isDragging = false;
|
|
if (_activePointerId != -1)
|
|
{
|
|
try { dragBtn?.ReleasePointer(_activePointerId); } catch { }
|
|
}
|
|
_activePointerId = -1;
|
|
evt.StopImmediatePropagation();
|
|
}
|
|
|
|
private void OnDragPointerCancel(PointerCancelEvent evt)
|
|
{
|
|
if (!_isDragging) return;
|
|
if (evt.pointerId != _activePointerId) return;
|
|
|
|
_isDragging = false;
|
|
if (_activePointerId != -1)
|
|
{
|
|
try { dragBtn?.ReleasePointer(_activePointerId); } catch { }
|
|
}
|
|
_activePointerId = -1;
|
|
evt.StopImmediatePropagation();
|
|
}
|
|
|
|
private bool _geometryInitialized = false;
|
|
private void OnRightPanelGeometryChanged(GeometryChangedEvent evt)
|
|
{
|
|
// 드래그 중에는 GeometryChanged 이벤트 무시
|
|
if (_isDragging) return;
|
|
|
|
// 초기화: rightPanel의 레이아웃이 계산될 때까지 대기
|
|
if (!_geometryInitialized)
|
|
{
|
|
if (rightPanel == null || rightPanel.layout.height <= 0)
|
|
{
|
|
return; // 아직 레이아웃이 계산되지 않음
|
|
}
|
|
_geometryInitialized = true;
|
|
}
|
|
if (rightPanel != null && dragBtn != null)
|
|
{
|
|
UpdateDragAndPanels(rightPanel, dragBtn);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// glTF 모델과 간트 데이터를 하위 뷰에 로드하고, 선택 동기화를 위한 매핑을 구축합니다.
|
|
/// </summary>
|
|
/// <param name="gltfPaths">glTF/glb 파일 경로 목록.</param>
|
|
/// <param name="ganttPath">간트 데이터셋 경로.</param>
|
|
/// <param name="externalCt">외부 취소 토큰.</param>
|
|
public async UniTask LoadData(List<string> gltfPaths, string ganttPath, CancellationToken externalCt = default)
|
|
{
|
|
if(modelView == null)
|
|
{
|
|
//대기
|
|
while(modelView == null)
|
|
{
|
|
await UniTask.Yield();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
Debug.Log($"NWModal.LoadData: gltfPath:{string.Join(", ", gltfPaths)}, ganttPath={ganttPath}");
|
|
|
|
// 로딩 표시
|
|
ShowLoading(true);
|
|
|
|
try
|
|
{
|
|
// 이전 작업 취소
|
|
if (_cts != null)
|
|
{
|
|
try { _cts.Cancel(); } catch { }
|
|
_cts.Dispose();
|
|
}
|
|
_cts = CancellationTokenSource.CreateLinkedTokenSource(externalCt);
|
|
var ct = _cts.Token;
|
|
|
|
// 모델/리스트 로드
|
|
IEnumerable<TreeListItemData> items = Array.Empty<TreeListItemData>();
|
|
if (modelView != null)
|
|
{
|
|
try
|
|
{
|
|
items = await modelView.LoadModelAsync(gltfPaths, ct);
|
|
}
|
|
catch (OperationCanceledException) { }
|
|
}
|
|
|
|
BuildKeyMaps(items);
|
|
|
|
if (listView != null) listView.SetData(items.ToList());
|
|
if (chartView != null) chartView.Load(ganttPath);
|
|
if (_playBar != null && chartView != null)
|
|
{
|
|
_playBar.SetTimeRange(chartView.ProjectStartDate, chartView.ProjectEndDate);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// 로딩 숨기기
|
|
ShowLoading(false);
|
|
}
|
|
}
|
|
|
|
#region 로딩 UI (Loading UI)
|
|
/// <summary>
|
|
/// 로딩 오버레이와 스피너 애니메이션을 표시하거나 숨깁니다.
|
|
/// </summary>
|
|
private void ShowLoading(bool show)
|
|
{
|
|
if (_loadingOverlay == null) return;
|
|
|
|
if (show)
|
|
{
|
|
_loadingOverlay.AddToClassList("visible");
|
|
StartSpinnerAnimation();
|
|
}
|
|
else
|
|
{
|
|
_loadingOverlay.RemoveFromClassList("visible");
|
|
StopSpinnerAnimation();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스피너 회전 애니메이션 시작
|
|
/// </summary>
|
|
private void StartSpinnerAnimation()
|
|
{
|
|
if (_loadingSpinner == null) return;
|
|
|
|
StopSpinnerAnimation();
|
|
|
|
float angle = 0f;
|
|
_spinnerAnimation = _loadingSpinner.schedule.Execute(() =>
|
|
{
|
|
angle = (angle + 10f) % 360f;
|
|
_loadingSpinner.style.rotate = new Rotate(Angle.Degrees(angle));
|
|
}).Every(16); // ~60fps
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스피너 회전 애니메이션 중지
|
|
/// </summary>
|
|
private void StopSpinnerAnimation()
|
|
{
|
|
if (_spinnerAnimation != null)
|
|
{
|
|
_spinnerAnimation.Pause();
|
|
_spinnerAnimation = null;
|
|
}
|
|
|
|
if (_loadingSpinner != null)
|
|
{
|
|
_loadingSpinner.style.rotate = new Rotate(Angle.Degrees(0));
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
private void BuildKeyMaps(IEnumerable<TreeListItemData> items)
|
|
{
|
|
_keyToId.Clear();
|
|
_idToKey.Clear();
|
|
if (items == null) return;
|
|
|
|
var stack = new Stack<TreeListItemData>();
|
|
foreach (var it in items)
|
|
{
|
|
if (it == null) continue;
|
|
stack.Push(it);
|
|
while (stack.Count > 0)
|
|
{
|
|
var cur = stack.Pop();
|
|
if (!string.IsNullOrEmpty(cur.ExternalKey))
|
|
{
|
|
_keyToId[cur.ExternalKey] = cur.id;
|
|
_idToKey[cur.id] = cur.ExternalKey;
|
|
}
|
|
if (cur.children != null)
|
|
{
|
|
for (int i = 0; i < cur.children.Count; i++)
|
|
{
|
|
var child = cur.children[i] as TreeListItemData;
|
|
if (child != null) stack.Push(child);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnClickClose()
|
|
{
|
|
//this.gameObject.SetActive(false);
|
|
Destroy(this.gameObject);
|
|
}
|
|
|
|
private void OnClickShowTree()
|
|
{
|
|
if (listView != null)
|
|
{
|
|
listView.Show();
|
|
showTreeBtn.style.display = DisplayStyle.None;
|
|
//1frame 후에 위치 갱신
|
|
if (rightPanel != null)
|
|
{
|
|
rightPanel.schedule.Execute(() =>
|
|
{
|
|
UpdateDragAndPanels(rightPanel, dragBtn);
|
|
}).ExecuteLater(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnListItemSelectionChanged(TreeListItemData data)
|
|
{
|
|
if (data == null) return;
|
|
Debug.Log($"NWModal.OnListItemSelectionChanged: id={data.id}, isSelected={data.isSelected}");
|
|
if (data.isSelected)
|
|
{
|
|
HandleSelection(data.id, "ListView");
|
|
}
|
|
else
|
|
{
|
|
HandleDeselection(data.id, "ListView");
|
|
}
|
|
}
|
|
|
|
private void OnListClosed()
|
|
{
|
|
showTreeBtn.style.display = DisplayStyle.Flex;
|
|
if (rightPanel != null) UpdateDragAndPanels(rightPanel, dragBtn);
|
|
}
|
|
|
|
private void OnListVisibilityChanged(TreeListItemData itemData)
|
|
{
|
|
if (modelView != null) modelView.SetVisibility(itemData.id, itemData.IsVisible);
|
|
}
|
|
|
|
private void OnModelItemSelected(TreeListItemData data)
|
|
{
|
|
if (data == null) return;
|
|
HandleSelection(data.id, "ModelView");
|
|
}
|
|
|
|
private void HandleSelection(int itemId, string source)
|
|
{
|
|
if (_suppressSelection) return;
|
|
_suppressSelection = true;
|
|
try
|
|
{
|
|
selectedItemId = itemId;
|
|
if (source != "ListView" && listView != null) listView.SelectByItemId(itemId);
|
|
if (source != "ModelView" && modelView != null) modelView.FocusItemById(itemId);
|
|
if (source != "ChartView" && chartView != null)
|
|
{
|
|
//if (_idToKey.TryGetValue(itemId, out var key)) chartView.SelectByItemKey(key);
|
|
//else chartView.SelectByItemId(itemId);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_suppressSelection = false;
|
|
}
|
|
}
|
|
|
|
private void HandleDeselection(int itemId, string source)
|
|
{
|
|
if (_suppressSelection) return;
|
|
if (source != "ModelView" && modelView != null) modelView.UnfocusItem();
|
|
}
|
|
|
|
private void ToggleExpandModel()
|
|
{
|
|
if (_expanded == ExpandedSide.Model)
|
|
{
|
|
// 확장 해제: 저장된 비율로 복원
|
|
_expanded = ExpandedSide.None;
|
|
modelView.style.display = DisplayStyle.Flex;
|
|
chartView.style.display = DisplayStyle.Flex;
|
|
dragBtn.style.display = DisplayStyle.Flex;
|
|
// 저장된 비율로 복원
|
|
modelView.style.flexGrow = _lastModelFlexGrow;
|
|
chartView.style.flexGrow = _lastChartFlexGrow;
|
|
// 1프레임 후 dragBtn 위치 업데이트
|
|
rightPanel?.schedule.Execute(() =>
|
|
{
|
|
if (rightPanel != null) UpdateDragAndPanels(rightPanel, dragBtn);
|
|
}).ExecuteLater(1);
|
|
return;
|
|
}
|
|
|
|
// 확장 전 비율 저장
|
|
_lastModelFlexGrow = modelView.resolvedStyle.flexGrow;
|
|
_lastChartFlexGrow = chartView.resolvedStyle.flexGrow;
|
|
|
|
_expanded = ExpandedSide.Model;
|
|
//modelView 확장
|
|
modelView.style.display = DisplayStyle.Flex;
|
|
modelView.style.flexGrow = 1;
|
|
chartView.style.display = DisplayStyle.None;
|
|
dragBtn.style.display = DisplayStyle.None;
|
|
}
|
|
|
|
private void ToggleExpandChart()
|
|
{
|
|
if (_expanded == ExpandedSide.Chart)
|
|
{
|
|
// 확장 해제: 저장된 비율로 복원
|
|
_expanded = ExpandedSide.None;
|
|
modelView.style.display = DisplayStyle.Flex;
|
|
chartView.style.display = DisplayStyle.Flex;
|
|
dragBtn.style.display = DisplayStyle.Flex;
|
|
// 저장된 비율로 복원
|
|
modelView.style.flexGrow = _lastModelFlexGrow;
|
|
chartView.style.flexGrow = _lastChartFlexGrow;
|
|
// 1프레임 후 dragBtn 위치 업데이트
|
|
rightPanel?.schedule.Execute(() =>
|
|
{
|
|
if (rightPanel != null) UpdateDragAndPanels(rightPanel, dragBtn);
|
|
}).ExecuteLater(1);
|
|
return;
|
|
}
|
|
|
|
// 확장 전 비율 저장
|
|
_lastModelFlexGrow = modelView.resolvedStyle.flexGrow;
|
|
_lastChartFlexGrow = chartView.resolvedStyle.flexGrow;
|
|
|
|
_expanded = ExpandedSide.Chart;
|
|
// chartView 확장
|
|
modelView.style.display = DisplayStyle.None;
|
|
chartView.style.display = DisplayStyle.Flex;
|
|
chartView.style.flexGrow = 1;
|
|
dragBtn.style.display = DisplayStyle.None;
|
|
}
|
|
|
|
|
|
// dragBtn 중심 Y를 주면 flex-grow를 조절하여 modelView와 chartView 비율 조정 (수직)
|
|
private void ApplyDragPosition(VisualElement root, VisualElement drag, float centerY)
|
|
{
|
|
if (modelView == null || chartView == null) return;
|
|
|
|
// 확장 모드일 때는 비율 조정하지 않음
|
|
if (_expanded != ExpandedSide.None) return;
|
|
|
|
float dragHalf = Mathf.Max(1f, drag.layout.height * 0.5f);
|
|
|
|
// 드래그 가능 범위 계산 (수직)
|
|
float topBound = 0f;
|
|
float bottomBound = root.layout.height;
|
|
|
|
// centerY를 범위 내로 제한
|
|
centerY = Mathf.Clamp(centerY, topBound, bottomBound);
|
|
|
|
// 사용 가능한 높이
|
|
float availableHeight = bottomBound - topBound;
|
|
|
|
// centerY를 topBound 기준 상대 좌표로 변환
|
|
float relativeY = centerY - topBound;
|
|
|
|
// flex 비율 계산 (relativeY : (availableHeight - relativeY))
|
|
float modelFlexGrow = relativeY;
|
|
float chartFlexGrow = availableHeight - relativeY;
|
|
|
|
// 양 끝 판정을 위한 threshold
|
|
float threshold = 2f;
|
|
bool isAtTopEdge = relativeY < threshold;
|
|
bool isAtBottomEdge = relativeY > (availableHeight - threshold);
|
|
|
|
// 가시성 및 flex 처리
|
|
if (isAtTopEdge)
|
|
{
|
|
// 위쪽 끝: modelView 숨김, chartView만 표시
|
|
modelView.style.display = DisplayStyle.None;
|
|
chartView.style.display = DisplayStyle.Flex;
|
|
chartView.style.flexGrow = 1;
|
|
}
|
|
else if (isAtBottomEdge)
|
|
{
|
|
// 아래쪽 끝: chartView 숨김, modelView만 표시
|
|
modelView.style.display = DisplayStyle.Flex;
|
|
chartView.style.display = DisplayStyle.None;
|
|
modelView.style.flexGrow = 1;
|
|
}
|
|
else
|
|
{
|
|
// 중간: 둘 다 표시, flex 비율로 조정
|
|
modelView.style.display = DisplayStyle.Flex;
|
|
chartView.style.display = DisplayStyle.Flex;
|
|
modelView.style.flexGrow = modelFlexGrow;
|
|
chartView.style.flexGrow = chartFlexGrow;
|
|
|
|
// 비율 저장 (중간 위치에서만)
|
|
_lastModelFlexGrow = modelFlexGrow;
|
|
_lastChartFlexGrow = chartFlexGrow;
|
|
}
|
|
|
|
// dragBtn 위치 조정 (수직)
|
|
float newDragTop = centerY - dragHalf;
|
|
drag.style.top = new Length(newDragTop, LengthUnit.Pixel);
|
|
}
|
|
|
|
private void UpdateDragAndPanels(VisualElement root, VisualElement drag)
|
|
{
|
|
if (modelView == null || chartView == null) return;
|
|
|
|
// modelView의 현재 layout을 사용해서 dragBtn을 배치 (수직)
|
|
var mv = modelView.layout;
|
|
|
|
// modelView의 아래쪽 끝이 dragBtn의 중심
|
|
float centerY = mv.height;
|
|
|
|
// 적용
|
|
ApplyDragPosition(root, drag, centerY);
|
|
}
|
|
|
|
// 테스트용 데이터 생성
|
|
List<TreeListItemData> GenerateDummyData()
|
|
{
|
|
var root1 = new TreeListItemData { id = 1, name = "모델", isExpanded = true };
|
|
|
|
root1.Add(new TreeListItemData { id = 2, name = "모델1", parent = root1 });
|
|
root1.Add(new TreeListItemData { id = 3, name = "모델2", parent = root1, IsVisible = false });
|
|
|
|
var child3 = new TreeListItemData { id = 4, name = "모델3", parent = root1 };
|
|
child3.Add(new TreeListItemData { id = 5, name = "메쉬 A", parent = child3 });
|
|
root1.Add(child3);
|
|
|
|
return new List<TreeListItemData> { root1 };
|
|
}
|
|
|
|
|
|
private void OnDestroy()
|
|
{
|
|
// CancellationTokenSource 정리
|
|
if (_cts != null)
|
|
{
|
|
try { _cts.Cancel(); } catch { }
|
|
_cts.Dispose();
|
|
_cts = null;
|
|
}
|
|
|
|
// listView 이벤트 해제 및 Dispose
|
|
if (listView != null)
|
|
{
|
|
listView.OnSelectionChanged -= OnListItemSelectionChanged;
|
|
listView.OnClosed -= OnListClosed;
|
|
listView.OnVisibilityChanged -= OnListVisibilityChanged;
|
|
listView.Dispose();
|
|
}
|
|
|
|
// modelView 이벤트 해제 및 Dispose
|
|
if (modelView != null)
|
|
{
|
|
modelView.OnItemSelected -= OnModelItemSelected;
|
|
modelView.OnExpand -= ToggleExpandModel;
|
|
modelView.Dispose();
|
|
}
|
|
|
|
// chartView 이벤트 해제 및 Dispose
|
|
if (chartView != null)
|
|
{
|
|
chartView.OnExpand -= ToggleExpandChart;
|
|
chartView.Dispose();
|
|
}
|
|
|
|
// 버튼 이벤트 해제
|
|
if (showTreeBtn != null) showTreeBtn.clicked -= OnClickShowTree;
|
|
if (closeBtn != null) closeBtn.clicked -= OnClickClose;
|
|
|
|
// dragBtn 포인터 이벤트 해제
|
|
if (dragBtn != null)
|
|
{
|
|
dragBtn.UnregisterCallback<PointerDownEvent>(OnDragPointerDown, TrickleDown.TrickleDown);
|
|
dragBtn.UnregisterCallback<PointerMoveEvent>(OnDragPointerMove, TrickleDown.TrickleDown);
|
|
dragBtn.UnregisterCallback<PointerUpEvent>(OnDragPointerUp, TrickleDown.TrickleDown);
|
|
dragBtn.UnregisterCallback<PointerCancelEvent>(OnDragPointerCancel, TrickleDown.TrickleDown);
|
|
}
|
|
|
|
// rightPanel GeometryChangedEvent 해제
|
|
if (rightPanel != null && _rightPanelGeometryChangedCallback != null)
|
|
{
|
|
rightPanel.UnregisterCallback(_rightPanelGeometryChangedCallback);
|
|
_rightPanelGeometryChangedCallback = null;
|
|
}
|
|
|
|
if( _playBar != null)
|
|
{
|
|
_playBar.OnPlayProgress -= OnPlayProgressHandler;
|
|
_playBar.OnPositionChanged -= OnPlayPositionChangedHandler;
|
|
_playBar.Dispose();
|
|
}
|
|
|
|
// 딕셔너리 정리
|
|
_keyToId.Clear();
|
|
_idToKey.Clear();
|
|
|
|
// 드래그 상태 초기화
|
|
_isDragging = false;
|
|
_activePointerId = -1;
|
|
_dragOffset = 0f;
|
|
_geometryInitialized = false;
|
|
|
|
// 로딩 UI 정리
|
|
StopSpinnerAnimation();
|
|
_loadingOverlay = null;
|
|
_loadingSpinner = null;
|
|
|
|
// UI 참조 정리
|
|
rightPanel = null;
|
|
listView = null;
|
|
modelView = null;
|
|
chartView = null;
|
|
closeBtn = null;
|
|
showTreeBtn = null;
|
|
dragBtn = null;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
} |