Files
XRLib/Assets/Scripts/UVC/UI/Window/PropertyWindow/PropertyView.cs
UVCLimHun 6b78b68229 merge
merge and property
2025-12-24 17:36:01 +09:00

378 lines
14 KiB
C#

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UVC.UI.Window.PropertyWindow
{
/// <summary>
/// 속성창의 UI를 담당하는 View 클래스입니다.
/// Controller로부터 데이터를 받아와 동적으로 UI 요소들을 생성하고 관리합니다.
/// 그룹과 개별 아이템을 혼용하여 렌더링할 수 있습니다.
/// </summary>
public class PropertyView : MonoBehaviour
{
/// <summary>
/// UI 요소들이 생성될 부모 컨테이너입니다.
/// </summary>
[Tooltip("속성 UI들이 생성될 부모 Transform (예: Vertical Layout Group이 있는 Panel)")]
[SerializeField] private Transform _container;
[Header("Group UI Prefab")]
[SerializeField] private GameObject _groupPrefab;
[Header("Property UI Prefabs")]
[SerializeField] private GameObject _stringPropertyPrefab;
[SerializeField] private GameObject _numberPropertyPrefab;
[SerializeField] private GameObject _boolPropertyPrefab;
[SerializeField] private GameObject _vector2PropertyPrefab;
[SerializeField] private GameObject _vector3PropertyPrefab;
[SerializeField] private GameObject _colorPropertyPrefab;
[SerializeField] private GameObject _datePropertyPrefab;
[SerializeField] private GameObject _dateTimePropertyPrefab;
[SerializeField] private GameObject _enumPropertyPrefab;
[SerializeField] private GameObject _listPropertyPrefab;
[SerializeField] private GameObject _radioGroupPropertyPrefab;
[SerializeField] private GameObject _numberRangePropertyPrefab;
[SerializeField] private GameObject _dateRangePropertyPrefab;
[SerializeField] private GameObject _dateTimeRangePropertyPrefab;
/// <summary>
/// View가 상호작용할 Controller 인스턴스입니다.
/// </summary>
private PropertyWindow _controller;
/// <summary>
/// 현재 표시 중인 그룹 뷰들의 캐시
/// </summary>
private readonly Dictionary<string, PropertyGroupView> _groupViews = new Dictionary<string, PropertyGroupView>();
/// <summary>
/// 현재 표시 중인 아이템 UI들의 캐시 (아이템 ID -> GameObject)
/// </summary>
private readonly Dictionary<string, GameObject> _itemViews = new Dictionary<string, GameObject>();
/// <summary>
/// Controller를 View에 설정하고 UI를 초기화합니다.
/// </summary>
/// <param name="controller">사용할 PropertyWindow</param>
public void Initialize(PropertyWindow controller)
{
// 기존 컨트롤러 이벤트 해제
if (_controller != null)
{
_controller.PropertyValueChanged -= OnPropertyValueChanged;
_controller.GroupExpandedChanged -= OnGroupExpandedChanged;
}
_controller = controller;
// Controller가 null이 아니면, 이벤트 핸들러를 등록하고 UI를 그립니다.
if (_controller != null)
{
_controller.PropertyValueChanged += OnPropertyValueChanged;
_controller.GroupExpandedChanged += OnGroupExpandedChanged;
DrawEntries();
}
}
/// <summary>
/// Controller에 있는 엔트리 목록을 기반으로 UI를 생성합니다.
/// 그룹과 개별 아이템을 혼용하여 렌더링합니다.
/// </summary>
private void DrawEntries()
{
// 기존 UI 정리
ClearAllViews();
if (_controller == null) return;
// Order 순으로 정렬된 엔트리들을 렌더링
var sortedEntries = _controller.Entries;
foreach (var entry in sortedEntries)
{
if (entry is IPropertyGroup group)
{
DrawGroup(group);
}
else if (entry is IPropertyItem item && item.GroupId == null)
{
// 그룹에 속하지 않은 개별 아이템만 직접 렌더링
DrawPropertyItem(item, _container);
}
}
}
/// <summary>
/// 그룹 UI를 생성합니다.
/// </summary>
private void DrawGroup(IPropertyGroup group)
{
if (_groupPrefab == null)
{
Debug.LogWarning("[PropertyView] 그룹 프리팹이 설정되지 않았습니다. 그룹 내 아이템만 표시합니다.");
// 그룹 프리팹이 없으면 아이템들만 직접 렌더링
foreach (var item in group.Items)
{
DrawPropertyItem(item, _container);
}
return;
}
// 그룹 UI 생성
GameObject groupInstance = Instantiate(_groupPrefab, _container);
var groupView = groupInstance.GetComponent<PropertyGroupView>();
if (groupView != null)
{
groupView.Setup(group, _controller);
_groupViews[group.GroupId] = groupView;
// 그룹이 펼쳐진 상태면 자식 아이템들 렌더링
if (group.IsExpanded)
{
foreach (var item in group.Items)
{
DrawPropertyItem(item, groupView.ItemContainer);
}
}
}
else
{
Debug.LogError($"[PropertyView] 그룹 프리팹 '{_groupPrefab.name}'에 PropertyGroupView 컴포넌트가 없습니다.");
Destroy(groupInstance);
}
}
/// <summary>
/// 개별 속성 아이템 UI를 생성합니다.
/// </summary>
private void DrawPropertyItem(IPropertyItem item, Transform container)
{
GameObject prefab = GetPrefabForProperty(item.PropertyType);
if (prefab != null)
{
GameObject uiInstance = Instantiate(prefab, container);
var propertyUI = uiInstance.GetComponent<IPropertyUI>();
if (propertyUI != null)
{
propertyUI.Setup(item, _controller);
_itemViews[item.Id] = uiInstance;
}
else
{
Debug.LogError($"[PropertyView] 프리팹 '{prefab.name}'에 IPropertyUI를 구현한 스크립트가 없습니다.");
Destroy(uiInstance);
}
}
else
{
Debug.LogWarning($"[PropertyView] '{item.PropertyType}' 타입에 대한 UI 프리팹이 지정되지 않았습니다.");
}
}
/// <summary>
/// 모든 View를 정리합니다.
/// </summary>
private void ClearAllViews()
{
// 그룹 뷰 정리
foreach (var groupView in _groupViews.Values)
{
if (groupView != null)
{
groupView.Reset();
Destroy(groupView.gameObject);
}
}
_groupViews.Clear();
// 아이템 뷰 정리
foreach (var itemView in _itemViews.Values)
{
if (itemView != null)
{
Destroy(itemView);
}
}
_itemViews.Clear();
// 컨테이너의 모든 자식 삭제 (혹시 누락된 것이 있을 경우)
foreach (Transform child in _container)
{
Destroy(child.gameObject);
}
}
/// <summary>
/// 그룹 펼침/접힘 상태가 변경되었을 때 호출됩니다.
/// </summary>
private void OnGroupExpandedChanged(object sender, PropertyGroupExpandedEventArgs e)
{
if (_groupViews.TryGetValue(e.Group.GroupId, out var groupView))
{
groupView.UpdateExpandedState();
// 펼쳐진 경우 자식 아이템들 렌더링
if (e.IsExpanded)
{
foreach (var item in e.Group.Items)
{
// 이미 존재하는 아이템은 건너뜀
if (!_itemViews.ContainsKey(item.Id))
{
DrawPropertyItem(item, groupView.ItemContainer);
}
}
}
else
{
// 접힌 경우 자식 아이템들 제거
groupView.ClearItems();
foreach (var item in e.Group.Items)
{
if (_itemViews.TryGetValue(item.Id, out var itemView))
{
Destroy(itemView);
_itemViews.Remove(item.Id);
}
}
}
}
}
/// <summary>
/// [하위 호환성] 기존 방식으로 속성 목록을 그립니다.
/// </summary>
[System.Obsolete("Use DrawEntries() instead. This method is kept for backward compatibility.")]
private void DrawProperties()
{
DrawEntries();
}
/// <summary>
/// 속성 타입에 맞는 UI 프리팹을 반환합니다.
/// 실제 구현에서는 더 많은 case가 필요합니다.
/// </summary>
private GameObject GetPrefabForProperty(PropertyType type)
{
switch (type)
{
case PropertyType.String:
return _stringPropertyPrefab;
case PropertyType.Int:
return _numberPropertyPrefab;
case PropertyType.Float:
return _numberPropertyPrefab;
case PropertyType.Bool:
return _boolPropertyPrefab;
case PropertyType.Vector2:
return _vector2PropertyPrefab;
case PropertyType.Vector3:
return _vector3PropertyPrefab;
case PropertyType.Color:
return _colorPropertyPrefab;
case PropertyType.Date:
return _datePropertyPrefab;
case PropertyType.DateTime:
return _dateTimePropertyPrefab;
case PropertyType.Enum:
return _enumPropertyPrefab;
case PropertyType.DropdownList:
return _listPropertyPrefab;
case PropertyType.RadioGroup:
return _radioGroupPropertyPrefab;
case PropertyType.IntRange:
return _numberRangePropertyPrefab;
case PropertyType.FloatRange:
return _numberRangePropertyPrefab;
case PropertyType.DateRange:
return _dateRangePropertyPrefab;
case PropertyType.DateTimeRange:
return _dateTimeRangePropertyPrefab;
default:
Debug.LogWarning($"'{type}' 타입에 대한 프리팹이 정의되지 않았습니다.");
return null;
}
}
/// <summary>
/// Controller에서 PropertyValueChanged 이벤트가 발생했을 때 호출되는 핸들러입니다.
/// </summary>
private void OnPropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
{
//Debug.Log($"[PropertyView] 속성 변경 감지: ID='{e.PropertyId}', 이전 값='{e.OldValue}', 새 값='{e.NewValue}'");
// 여기서 특정 속성 값의 변경에 따라 다른 UI를 업데이트하는 로직을 추가할 수 있습니다.
// 예: 특정 bool 속성이 false가 되면 다른 속성 UI를 비활성화 처리
// DrawProperties(); // 전체를 다시 그리는 가장 간단하지만 비효율적일 수 있는 방법
}
private void OnDestroy()
{
// 오브젝트가 파괴될 때 이벤트 핸들러를 안전하게 해제합니다. (메모리 누수 방지)
if (_controller != null)
{
_controller.PropertyValueChanged -= OnPropertyValueChanged;
_controller.GroupExpandedChanged -= OnGroupExpandedChanged;
}
// 캐시 정리
_groupViews.Clear();
_itemViews.Clear();
}
#region Property/Group Visibility
/// <summary>
/// 특정 속성 아이템의 가시성을 설정합니다.
/// </summary>
/// <param name="propertyId">속성 아이템의 ID</param>
/// <param name="visible">가시성 여부</param>
public void SetPropertyVisibility(string propertyId, bool visible)
{
if (_itemViews.TryGetValue(propertyId, out var itemView) && itemView != null)
{
itemView.SetActive(visible);
}
}
/// <summary>
/// 특정 그룹의 가시성을 설정합니다.
/// </summary>
/// <param name="groupId">그룹 ID</param>
/// <param name="visible">가시성 여부</param>
public void SetGroupVisibility(string groupId, bool visible)
{
if (_groupViews.TryGetValue(groupId, out var groupView) && groupView != null)
{
groupView.gameObject.SetActive(visible);
}
}
#endregion
#region Property/Group ReadOnly
/// <summary>
/// 특정 속성 아이템의 읽기 전용 상태를 설정합니다.
/// </summary>
/// <param name="propertyId">속성 아이템의 ID</param>
/// <param name="isReadOnly">읽기 전용 여부</param>
public void SetPropertyReadOnly(string propertyId, bool isReadOnly)
{
if (_itemViews.TryGetValue(propertyId, out var itemView) && itemView != null)
{
var propertyUI = itemView.GetComponent<IPropertyUI>();
if (propertyUI != null)
{
propertyUI.SetReadOnly(isReadOnly);
}
}
}
#endregion
}
}