2025-12-08 21:06:05 +09:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 11:16:34 +09:00
|
|
|
/// <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)
|
|
|
|
|
{
|
|
|
|
|
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 19:49:36 +09:00
|
|
|
/// <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)
|
|
|
|
|
{
|
|
|
|
|
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 21:06:05 +09:00
|
|
|
#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
|
|
|
|
|
}
|
|
|
|
|
}
|