merge
merge and property
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// 속성 그룹의 UI를 담당하는 View 클래스입니다.
|
||||
/// 접이식 헤더와 자식 아이템들의 컨테이너를 관리합니다.
|
||||
/// </summary>
|
||||
public class PropertyGroupView : MonoBehaviour
|
||||
{
|
||||
[Header("Header")]
|
||||
[SerializeField] private Button _headerButton;
|
||||
[SerializeField] private TextMeshProUGUI _groupNameLabel;
|
||||
[SerializeField] private Image _expandIcon;
|
||||
|
||||
[Header("Icons")]
|
||||
[SerializeField] private Sprite _expandedIcon;
|
||||
[SerializeField] private Sprite _collapsedIcon;
|
||||
|
||||
[Header("Content")]
|
||||
[SerializeField] private Transform _itemContainer;
|
||||
[SerializeField] private GameObject _contentPanel;
|
||||
|
||||
private IPropertyGroup _group;
|
||||
private PropertyWindow _controller;
|
||||
|
||||
/// <summary>
|
||||
/// 자식 PropertyItem UI들이 생성될 컨테이너입니다.
|
||||
/// </summary>
|
||||
public Transform ItemContainer => _itemContainer;
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 데이터
|
||||
/// </summary>
|
||||
public IPropertyGroup Group => _group;
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 ID
|
||||
/// </summary>
|
||||
public string GroupId => _group?.GroupId;
|
||||
|
||||
/// <summary>
|
||||
/// PropertyGroupView를 초기화합니다.
|
||||
/// </summary>
|
||||
/// <param name="group">표시할 그룹 데이터</param>
|
||||
/// <param name="controller">상호작용할 컨트롤러</param>
|
||||
public void Setup(IPropertyGroup group, PropertyWindow controller)
|
||||
{
|
||||
_group = group;
|
||||
_controller = controller;
|
||||
|
||||
// 그룹명 설정
|
||||
if (_groupNameLabel != null)
|
||||
{
|
||||
_groupNameLabel.text = group.GroupName;
|
||||
}
|
||||
|
||||
// 헤더 버튼 이벤트 등록
|
||||
if (_headerButton != null)
|
||||
{
|
||||
_headerButton.onClick.RemoveAllListeners();
|
||||
_headerButton.onClick.AddListener(OnHeaderClicked);
|
||||
}
|
||||
|
||||
// 펼침/접힘 상태 반영
|
||||
UpdateExpandedState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 헤더 클릭 시 호출됩니다.
|
||||
/// </summary>
|
||||
private void OnHeaderClicked()
|
||||
{
|
||||
if (_controller != null && _group != null)
|
||||
{
|
||||
_controller.ToggleGroupExpanded(_group.GroupId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 펼침/접힘 상태를 UI에 반영합니다.
|
||||
/// </summary>
|
||||
public void UpdateExpandedState()
|
||||
{
|
||||
if (_group == null) return;
|
||||
|
||||
bool isExpanded = _group.IsExpanded;
|
||||
|
||||
// 컨텐츠 패널 표시/숨김
|
||||
if (_contentPanel != null)
|
||||
{
|
||||
_contentPanel.SetActive(isExpanded);
|
||||
}
|
||||
|
||||
// 아이콘 변경
|
||||
if (_expandIcon != null)
|
||||
{
|
||||
if (isExpanded && _expandedIcon != null)
|
||||
{
|
||||
_expandIcon.sprite = _expandedIcon;
|
||||
}
|
||||
else if (!isExpanded && _collapsedIcon != null)
|
||||
{
|
||||
_expandIcon.sprite = _collapsedIcon;
|
||||
}
|
||||
|
||||
// 아이콘 회전으로 표현할 경우
|
||||
_expandIcon.transform.rotation = Quaternion.Euler(0, 0, isExpanded ? 0 : -90);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 내 모든 아이템 UI를 제거합니다.
|
||||
/// </summary>
|
||||
public void ClearItems()
|
||||
{
|
||||
if (_itemContainer == null) return;
|
||||
|
||||
foreach (Transform child in _itemContainer)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀에 반환하기 전에 정리합니다.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_group = null;
|
||||
_controller = null;
|
||||
|
||||
if (_headerButton != null)
|
||||
{
|
||||
_headerButton.onClick.RemoveAllListeners();
|
||||
}
|
||||
|
||||
ClearItems();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_headerButton != null)
|
||||
{
|
||||
_headerButton.onClick.RemoveAllListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1292fe635565fff43a6bd494f3f597b8
|
||||
@@ -0,0 +1,255 @@
|
||||
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)
|
||||
{
|
||||
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97fb1aaa6e72f9742b99c8d8ddf43ebc
|
||||
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/View/UI.meta
Normal file
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/View/UI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba2f83247bcb33a45b3e8e656b4d3ed0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,87 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// StringProperty를 위한 PropertyUIBase 기반 UI 스크립트입니다.
|
||||
/// 기존 StringPropertyUI의 리팩토링 버전입니다.
|
||||
/// </summary>
|
||||
public class StringPropertyUIBase : PropertyUIBase<StringProperty>
|
||||
{
|
||||
[Header("String Property UI")]
|
||||
[SerializeField] private TMP_InputField _valueInput;
|
||||
|
||||
#region PropertyUIBase Implementation
|
||||
|
||||
/// <summary>
|
||||
/// 값 관련 UI를 설정합니다.
|
||||
/// </summary>
|
||||
protected override void SetupValueUI()
|
||||
{
|
||||
if (_valueInput == null || _propertyItem == null) return;
|
||||
|
||||
// 값 설정
|
||||
_valueInput.text = _propertyItem.Value;
|
||||
|
||||
// 읽기 전용 상태 설정
|
||||
ApplyReadOnlyState(_valueInput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이벤트를 구독합니다.
|
||||
/// </summary>
|
||||
protected override void SubscribeEvents()
|
||||
{
|
||||
base.SubscribeEvents();
|
||||
|
||||
if (_valueInput != null)
|
||||
{
|
||||
_valueInput.onEndEdit.AddListener(OnValueSubmitted);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이벤트 구독을 해제합니다.
|
||||
/// </summary>
|
||||
protected override void UnsubscribeEvents()
|
||||
{
|
||||
base.UnsubscribeEvents();
|
||||
|
||||
if (_valueInput != null)
|
||||
{
|
||||
_valueInput.onEndEdit.RemoveListener(OnValueSubmitted);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// 사용자가 InputField 수정 완료 후 Enter를 누르거나 포커스를 잃었을 때 호출됩니다.
|
||||
/// </summary>
|
||||
/// <param name="newValue">InputField에 입력된 새로운 문자열</param>
|
||||
private void OnValueSubmitted(string newValue)
|
||||
{
|
||||
NotifyValueChanged(newValue);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPoolable Implementation
|
||||
|
||||
public override void OnReturnToPool()
|
||||
{
|
||||
base.OnReturnToPool();
|
||||
|
||||
// 입력 필드 초기화
|
||||
if (_valueInput != null)
|
||||
{
|
||||
_valueInput.text = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cab478cef001a8c43afe2fe8389124ae
|
||||
Reference in New Issue
Block a user