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

225 lines
9.3 KiB
C#

using System;
using System.Globalization;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
using UVC.Extention;
using UVC.UI.Tooltip;
namespace UVC.UI.Window.PropertyWindow.UI
{
/// <summary>
/// Int, Float RangeProperty를 위한 UI를 제어하는 스크립트입니다.
/// 범위의 시작(Min)과 끝(Max) 값을 별도의 InputField로 관리합니다.
/// </summary>
[RequireComponent(typeof(LayoutElement))]
public class NumberRangePropertyUI : MonoBehaviour, IPropertyUI
{
[Header("UI Components")]
[SerializeField]
private TextMeshProUGUI _nameLabel; // 속성 전체 이름
[SerializeField]
private TextMeshProUGUI _descriptionLabel;
[SerializeField]
private TMP_InputField _minInputField; // 최소값 입력 필드
[SerializeField]
private TMP_InputField _maxInputField; // 최대값 입력 필드
private IPropertyItem _propertyItem;
private PropertyWindow _controller;
/// <summary>
/// PropertyView에 의해 호출되어 UI를 초기화하고 데이터를 설정합니다.
/// </summary>
public void Setup(IPropertyItem item, PropertyWindow controller)
{
if (!(item is IntRangeProperty intRangeItem) && !(item is FloatRangeProperty floatRange))
{
Debug.LogError($"IntRangePropertyUI에 잘못된 타입의 PropertyItem이 전달되었습니다. {item.GetType()}");
return;
}
_propertyItem = item;
_controller = controller;
// --- 데이터 바인딩 ---
_nameLabel.text = _propertyItem.Name;
// 툴팁 설정 (TooltipHandler 컴포넌트가 있다면 연동)
TooltipHandler tooltipHandler = _nameLabel.GetComponent<TooltipHandler>();
if (tooltipHandler != null && !_propertyItem.Tooltip.IsNullOrEmpty())
{
tooltipHandler.Tooltip = _propertyItem.Tooltip;
}
if (_propertyItem.Description.IsNullOrEmpty())
{
_descriptionLabel.gameObject.SetActive(false);
}
else
{
_descriptionLabel.gameObject.SetActive(true);
_descriptionLabel.text = _propertyItem.Description;
}
if (item.PropertyType == PropertyType.IntRange)
{
_minInputField.contentType = TMP_InputField.ContentType.IntegerNumber;
_maxInputField.contentType = TMP_InputField.ContentType.IntegerNumber;
// 초기 값 설정
_minInputField.text = (_propertyItem as IntRangeProperty).Value.Item1.ToString(CultureInfo.InvariantCulture);
_maxInputField.text = (_propertyItem as IntRangeProperty).Value.Item2.ToString(CultureInfo.InvariantCulture);
}
else if (item.PropertyType == PropertyType.FloatRange)
{
_minInputField.contentType = TMP_InputField.ContentType.DecimalNumber;
_maxInputField.contentType = TMP_InputField.ContentType.DecimalNumber;
// 초기 값 설정
_minInputField.text = (_propertyItem as FloatRangeProperty).Value.Item1.ToString(CultureInfo.InvariantCulture);
_maxInputField.text = (_propertyItem as FloatRangeProperty).Value.Item2.ToString(CultureInfo.InvariantCulture);
}
// 읽기 전용 상태 설정
bool isReadOnly = _propertyItem.IsReadOnly;
_minInputField.interactable = !isReadOnly;
_maxInputField.interactable = !isReadOnly;
// --- 이벤트 리스너 등록 ---
_minInputField.onEndEdit.RemoveAllListeners();
_maxInputField.onEndEdit.RemoveAllListeners();
_minInputField.onEndEdit.AddListener(OnValueSubmitted);
_maxInputField.onEndEdit.AddListener(OnValueSubmitted);
}
/// <summary>
/// 사용자가 Min 또는 Max 입력 필드의 수정을 완료했을 때 호출됩니다.
/// </summary>
private void OnValueSubmitted(string input)
{
// 입력 필드의 값을 파싱합니다.
if (_propertyItem.PropertyType == PropertyType.IntRange)
{
OnValueSubmittedInt(input);
}
else if (_propertyItem.PropertyType == PropertyType.FloatRange)
{
OnValueSubmittedFloat(input);
}
}
private void OnValueSubmittedInt(string input)
{
IntRangeProperty propertyItem = _propertyItem as IntRangeProperty;
if (propertyItem == null) return;
int.TryParse(_minInputField.text, out int newMin);
int.TryParse(_maxInputField.text, out int newMax);
// 시작 값이 끝 값보다 크지 않도록 보정합니다.
if (newMin > newMax)
{
newMin = newMax;
}
// 이전 값을 저장합니다.
var oldValue = new Vector2Int(propertyItem.Value.Item1, propertyItem.Value.Item2);
var newValue = new Vector2Int(newMin, newMax);
// 값이 변경되었는지 확인합니다.
if (oldValue == newValue)
{
// 값이 변경되지 않았으면 UI만 원래 값으로 복원하고 종료합니다.
_minInputField.text = propertyItem.Value.Item1.ToString(CultureInfo.InvariantCulture);
_maxInputField.text = propertyItem.Value.Item2.ToString(CultureInfo.InvariantCulture);
return;
}
// 변경 이벤트를 발생시켜 다른 부분에 알립니다.
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, new Tuple<int, int>(newMin, newMax));
// 보정된 값으로 UI를 다시 업데이트합니다.
_minInputField.text = newMin.ToString(CultureInfo.InvariantCulture);
}
private void OnValueSubmittedFloat(string input)
{
FloatRangeProperty propertyItem = _propertyItem as FloatRangeProperty;
if (propertyItem == null) return;
float.TryParse(_minInputField.text, out float newMin);
float.TryParse(_maxInputField.text, out float newMax);
// 시작 값이 끝 값보다 크지 않도록 보정합니다.
if (newMin > newMax)
{
newMin = newMax;
}
// 이전 값을 저장합니다.
var oldValue = new Vector2(propertyItem.Value.Item1, propertyItem.Value.Item2);
var newValue = new Vector2(newMin, newMax);
// 값이 변경되었는지 확인합니다.
if (oldValue == newValue)
{
// 값이 변경되지 않았으면 UI만 원래 값으로 복원하고 종료합니다.
_minInputField.text = propertyItem.Value.Item1.ToString(CultureInfo.InvariantCulture);
_maxInputField.text = propertyItem.Value.Item2.ToString(CultureInfo.InvariantCulture);
return;
}
// 변경 이벤트를 발생시켜 다른 부분에 알립니다.
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, new Tuple<float, float>(newMin, newMax));
// 보정된 값으로 UI를 다시 업데이트합니다.
_minInputField.text = newMin.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// UI의 읽기 전용 상태를 설정합니다.
/// </summary>
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
public void SetReadOnly(bool isReadOnly)
{
if (_propertyItem != null)
{
_propertyItem.IsReadOnly = isReadOnly;
}
if (_minInputField != null) _minInputField.interactable = !isReadOnly;
if (_maxInputField != null) _maxInputField.interactable = !isReadOnly;
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value is Tuple<int, int> intRange)
{
if (_minInputField != null) _minInputField.SetTextWithoutNotify(intRange.Item1.ToString(CultureInfo.InvariantCulture));
if (_maxInputField != null) _maxInputField.SetTextWithoutNotify(intRange.Item2.ToString(CultureInfo.InvariantCulture));
}
else if (value is Tuple<float, float> floatRange)
{
if (_minInputField != null) _minInputField.SetTextWithoutNotify(floatRange.Item1.ToString(CultureInfo.InvariantCulture));
if (_maxInputField != null) _maxInputField.SetTextWithoutNotify(floatRange.Item2.ToString(CultureInfo.InvariantCulture));
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>
private void OnDestroy()
{
if (_minInputField != null) _minInputField.onEndEdit.RemoveAllListeners();
if (_maxInputField != null) _maxInputField.onEndEdit.RemoveAllListeners();
}
}
}