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