381 lines
14 KiB
C#
381 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;
|
|
[SerializeField] private GameObject _colorStatePropertyPrefab;
|
|
|
|
/// <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;
|
|
case PropertyType.ColorState:
|
|
return _colorStatePropertyPrefab;
|
|
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
|
|
}
|
|
}
|