Files
XRLib/Assets/Scripts/UVC/UI/Window/PropertyWindow/View/PropertyUIBase.cs
2025-12-22 19:49:36 +09:00

276 lines
8.2 KiB
C#

using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UVC.Extention;
using UVC.UI.Tooltip;
namespace UVC.UI.Window.PropertyWindow
{
/// <summary>
/// 모든 PropertyUI의 공통 베이스 클래스입니다.
/// 공통 기능을 제공하고 IPropertyUI, IPoolable, IDisposable을 구현합니다.
/// </summary>
[RequireComponent(typeof(LayoutElement))]
public abstract class PropertyUIBase<T> : MonoBehaviour, IPropertyUI, IPoolable, IDisposable
where T : class, IPropertyItem
{
[Header("Common UI Elements")]
[SerializeField] protected TextMeshProUGUI _nameLabel;
[SerializeField] protected TextMeshProUGUI _descriptionLabel;
/// <summary>
/// 현재 표시 중인 속성 아이템
/// </summary>
protected T _propertyItem;
/// <summary>
/// 상호작용할 컨트롤러
/// </summary>
protected PropertyWindow _controller;
/// <summary>
/// 이미 해제되었는지 여부
/// </summary>
protected bool _isDisposed = false;
#region IPropertyUI Implementation
/// <summary>
/// PropertyView에 의해 호출되어 UI를 초기화합니다.
/// </summary>
public virtual void Setup(IPropertyItem item, PropertyWindow controller)
{
// 기존 상태 정리
Cleanup();
// 타입 체크
if (!(item is T typedItem))
{
Debug.LogError($"[{GetType().Name}] 잘못된 타입의 PropertyItem이 전달되었습니다. 예상: {typeof(T).Name}, 실제: {item.GetType().Name}");
return;
}
_propertyItem = typedItem;
_controller = controller;
_isDisposed = false;
// 공통 UI 설정
SetupCommonUI();
// 파생 클래스의 추가 설정
SetupValueUI();
// 이벤트 구독
SubscribeEvents();
}
/// <summary>
/// UI의 읽기 전용 상태를 설정합니다.
/// 파생 클래스에서 재정의하여 구체적인 UI 요소에 적용해야 합니다.
/// </summary>
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
public virtual void SetReadOnly(bool isReadOnly)
{
if (_propertyItem != null)
{
_propertyItem.IsReadOnly = isReadOnly;
}
// 파생 클래스에서 구체적인 UI 요소 비활성화 로직을 구현
ApplyReadOnlyStateToUI(isReadOnly);
}
/// <summary>
/// 파생 클래스에서 구체적인 UI 요소에 읽기 전용 상태를 적용합니다.
/// </summary>
/// <param name="isReadOnly">읽기 전용 여부</param>
protected virtual void ApplyReadOnlyStateToUI(bool isReadOnly)
{
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// Undo/Redo 시 PropertyValueChanged 이벤트 없이 값을 반영할 때 사용됩니다.
/// </summary>
/// <param name="value">새로운 값</param>
public virtual void UpdateValue(object value)
{
// 파생 클래스에서 구체적인 구현을 제공해야 합니다.
ApplyValueToUI(value);
}
/// <summary>
/// 파생 클래스에서 UI에 값을 적용합니다.
/// </summary>
/// <param name="value">적용할 값</param>
protected virtual void ApplyValueToUI(object value)
{
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
}
#endregion
#region Common UI Setup
/// <summary>
/// 공통 UI 요소들을 설정합니다.
/// </summary>
protected virtual void SetupCommonUI()
{
if (_propertyItem == null) return;
// 이름 설정
if (_nameLabel != null)
{
_nameLabel.text = _propertyItem.Name;
// 툴팁 설정
var tooltipHandler = _nameLabel.GetComponent<TooltipHandler>();
if (tooltipHandler != null && !_propertyItem.Tooltip.IsNullOrEmpty())
{
tooltipHandler.Tooltip = _propertyItem.Tooltip;
}
}
// 설명 설정
if (_descriptionLabel != null)
{
if (_propertyItem.Description.IsNullOrEmpty())
{
_descriptionLabel.gameObject.SetActive(false);
}
else
{
_descriptionLabel.gameObject.SetActive(true);
_descriptionLabel.text = _propertyItem.Description;
}
}
}
/// <summary>
/// 파생 클래스에서 값 관련 UI를 설정합니다.
/// </summary>
protected abstract void SetupValueUI();
#endregion
#region Event Management
/// <summary>
/// 이벤트를 구독합니다. 파생 클래스에서 재정의할 수 있습니다.
/// </summary>
protected virtual void SubscribeEvents()
{
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
}
/// <summary>
/// 이벤트 구독을 해제합니다. 파생 클래스에서 재정의할 수 있습니다.
/// </summary>
protected virtual void UnsubscribeEvents()
{
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
}
#endregion
#region Value Update
/// <summary>
/// 컨트롤러를 통해 값 변경을 통지합니다.
/// </summary>
/// <param name="newValue">새로운 값</param>
protected void NotifyValueChanged(object newValue)
{
if (_controller == null || _propertyItem == null) return;
// 값이 변경되지 않았으면 무시
var oldValue = _propertyItem.GetValue();
if (Equals(oldValue, newValue)) return;
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, newValue);
}
/// <summary>
/// 읽기 전용 상태를 설정합니다.
/// </summary>
/// <param name="interactable">UI 요소</param>
protected void ApplyReadOnlyState(Selectable interactable)
{
if (interactable != null && _propertyItem != null)
{
interactable.interactable = !_propertyItem.IsReadOnly;
}
}
/// <summary>
/// 읽기 전용 상태를 설정합니다 (TMP_InputField용).
/// </summary>
/// <param name="inputField">입력 필드</param>
protected void ApplyReadOnlyState(TMP_InputField inputField)
{
if (inputField != null && _propertyItem != null)
{
inputField.interactable = !_propertyItem.IsReadOnly;
}
}
#endregion
#region Cleanup and Disposal
/// <summary>
/// 상태를 정리합니다.
/// </summary>
protected virtual void Cleanup()
{
UnsubscribeEvents();
_propertyItem = null;
_controller = null;
}
/// <summary>
/// IDisposable 구현
/// </summary>
public void Dispose()
{
if (_isDisposed) return;
Cleanup();
_isDisposed = true;
}
#endregion
#region IPoolable Implementation
/// <summary>
/// 풀에서 가져올 때 호출됩니다.
/// </summary>
public virtual void OnGetFromPool()
{
_isDisposed = false;
}
/// <summary>
/// 풀에 반환될 때 호출됩니다.
/// </summary>
public virtual void OnReturnToPool()
{
Cleanup();
}
#endregion
#region Unity Lifecycle
protected virtual void OnDestroy()
{
Dispose();
}
#endregion
}
}