Files
XRLib/Assets/Scripts/UVC/UIToolkit/Property/Views/UTKIntPropertyItemView.cs
2026-02-04 20:31:52 +09:00

432 lines
12 KiB
C#

#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// Int 속성 View 클래스입니다.
/// UTKIntegerField 또는 UTKSliderInt를 사용하여 int 값을 표시/편집합니다.
///
/// <para><b>사용법 (단독 사용):</b></para>
/// <code>
/// // C# 코드에서 생성
/// var view = new UTKIntPropertyItemView();
/// view.Label = "수량";
/// view.Value = 10;
/// view.UseSlider = true;
/// view.MinValue = 0;
/// view.MaxValue = 100;
/// parent.Add(view);
///
/// // UXML에서 사용
/// &lt;utk:UTKIntPropertyItemView label="수량" value="10" use-slider="true" min-value="0" max-value="100" /&gt;
/// </code>
/// </summary>
[UxmlElement]
public partial class UTKIntPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<int>
{
#region Fields
private UTKIntegerField? _intField;
private UTKSliderInt? _slider;
private int _value;
private int _minValue;
private int _maxValue = 100;
private bool _useSlider;
private IUTKPropertyItem<int>? _boundData;
#endregion
#region Properties
protected override string ViewTypeName => "UTKIntPropertyItemView";
/// <summary>현재 값</summary>
[UxmlAttribute("value")]
public int Value
{
get => _value;
set
{
if (_value != value)
{
_value = value;
UpdateValueUI();
OnValueChanged?.Invoke(value);
if (_boundData != null && _boundData.Value != value)
{
_boundData.Value = value;
}
}
}
}
/// <summary>최소값 (슬라이더 모드)</summary>
[UxmlAttribute("min-value")]
public int MinValue
{
get => _minValue;
set
{
_minValue = value;
if (_slider != null)
{
_slider.lowValue = value;
}
}
}
/// <summary>최대값 (슬라이더 모드)</summary>
[UxmlAttribute("max-value")]
public int MaxValue
{
get => _maxValue;
set
{
_maxValue = value;
if (_slider != null)
{
_slider.highValue = value;
}
}
}
/// <summary>슬라이더 사용 여부</summary>
[UxmlAttribute("use-slider")]
public bool UseSlider
{
get => _useSlider;
set
{
if (_useSlider != value)
{
_useSlider = value;
UpdateSliderClass();
}
}
}
#endregion
#region Events
public event Action<int>? OnValueChanged;
#endregion
#region Constructor
public UTKIntPropertyItemView() : base()
{
InitializeUI();
}
public UTKIntPropertyItemView(string label, int value = 0, bool useSlider = false) : base()
{
_value = value;
_useSlider = useSlider;
Label = label;
InitializeUI();
}
public UTKIntPropertyItemView(UTKIntPropertyItem item) : base()
{
_value = item.Value;
_minValue = item.MinValue;
_maxValue = item.MaxValue;
_useSlider = item.UseSlider;
_isReadOnly = item.IsReadOnly;
InitializeUI();
Bind(item);
}
public UTKIntPropertyItemView(string label, int value, int minValue, int maxValue, bool useSlider = true) : base()
{
_value = value;
_minValue = minValue;
_maxValue = maxValue;
_useSlider = useSlider;
Label = label;
InitializeUI();
}
#endregion
#region Initialization
private void InitializeUI()
{
AddToClassList("utk-property-item-view");
AddToClassList("utk-property-item-view--int");
if (!CreateUIFromUxml())
{
CreateUIFallback();
}
// UXML에서 요소 가져오기
QueryUIElements();
// 이벤트 등록
RegisterEvents();
// 슬라이더 클래스 업데이트
UpdateSliderClass();
UpdateValueUI();
UpdateReadOnlyState();
}
private void QueryUIElements()
{
_slider = this.Q<UTKSliderInt>("slider-field");
_intField = this.Q<UTKIntegerField>("value-field");
// Fallback: UXML에서 못 찾으면 생성
if (_valueContainer != null)
{
if (_slider == null)
{
_slider = new UTKSliderInt("", _minValue, _maxValue, _value)
{
name = "slider-field"
};
_slider.AddToClassList("utk-property-item-view__slider");
_valueContainer.Insert(0, _slider);
}
if (_intField == null)
{
_intField = new UTKIntegerField { name = "value-field" };
_intField.AddToClassList("utk-property-item-view__field");
_valueContainer.Add(_intField);
}
}
// 초기 값 설정
if (_slider != null)
{
_slider.lowValue = _minValue;
_slider.highValue = _maxValue;
_slider.SetValueWithoutNotify(_value);
}
if (_intField != null)
{
_intField.SetValueWithoutNotify(_value);
_intField.isReadOnly = IsReadOnly;
}
}
private void UpdateSliderClass()
{
if (_useSlider)
{
AddToClassList("utk-property-item-view--slider");
}
else
{
RemoveFromClassList("utk-property-item-view--slider");
}
}
#endregion
#region Override Methods
protected override void CreateValueUI(VisualElement container)
{
// UXML/QueryUIElements 기반으로 생성하므로 여기서는 생성하지 않음
}
public override void RefreshUI()
{
UpdateValueUI();
}
protected override void OnReadOnlyStateChanged(bool isReadOnly)
{
if (_intField != null)
{
_intField.isReadOnly = isReadOnly;
}
if (_slider != null)
{
_slider.IsEnabled = !isReadOnly;
}
}
#endregion
#region Event Handling
private void RegisterEvents()
{
if (_intField != null)
{
_intField.OnValueChanged += OnIntFieldChanged;
}
if (_slider != null)
{
_slider.OnValueChanged += OnSliderChanged;
}
}
private void UnregisterEvents()
{
if (_intField != null)
{
_intField.OnValueChanged -= OnIntFieldChanged;
}
if (_slider != null)
{
_slider.OnValueChanged -= OnSliderChanged;
}
}
private void OnIntFieldChanged(int newValue)
{
int clampedValue = _useSlider ? Mathf.Clamp(newValue, _minValue, _maxValue) : newValue;
if (_slider != null && _slider.Value != clampedValue)
{
_slider.SetValueWithoutNotify(clampedValue);
}
if (_intField != null && _intField.Value != clampedValue)
{
_intField.SetValueWithoutNotify(clampedValue);
}
if (_value != clampedValue)
{
_value = clampedValue;
OnValueChanged?.Invoke(clampedValue);
if (_boundData != null && _boundData.Value != clampedValue)
{
_boundData.Value = clampedValue;
}
}
}
private void OnSliderChanged(int newValue)
{
if (_intField != null && _intField.Value != newValue)
{
_intField.SetValueWithoutNotify(newValue);
}
if (_value != newValue)
{
_value = newValue;
OnValueChanged?.Invoke(newValue);
if (_boundData != null && _boundData.Value != newValue)
{
_boundData.Value = newValue;
}
}
}
#endregion
#region Value Update
private void UpdateValueUI()
{
if (_intField != null && _intField.Value != _value)
{
_intField.SetValueWithoutNotify(_value);
}
if (_slider != null && _slider.Value != _value)
{
_slider.SetValueWithoutNotify(_value);
}
}
#endregion
#region Data Binding
public void Bind(IUTKPropertyItem data)
{
if (data is IUTKPropertyItem<int> intData)
{
Bind(intData);
}
else
{
Debug.LogWarning($"[UTKIntPropertyItemView] Cannot bind to non-int data: {data.GetType().Name}");
}
}
public void Bind(IUTKPropertyItem<int> data)
{
Unbind();
_boundData = data;
Label = data.Name;
_value = data.Value;
IsReadOnly = data.IsReadOnly;
IsVisible = data.IsVisible;
TooltipText = data.Tooltip;
if (data is UTKIntPropertyItem intItem)
{
_minValue = intItem.MinValue;
_maxValue = intItem.MaxValue;
if (_useSlider != intItem.UseSlider)
{
_useSlider = intItem.UseSlider;
UpdateSliderClass();
}
// 슬라이더 범위 업데이트
if (_slider != null)
{
_slider.lowValue = _minValue;
_slider.highValue = _maxValue;
}
}
data.OnTypedValueChanged += OnDataValueChanged;
UpdateValueUI();
UpdateReadOnlyState();
}
public void Unbind()
{
if (_boundData != null)
{
_boundData.OnTypedValueChanged -= OnDataValueChanged;
_boundData = null;
}
}
private void OnDataValueChanged(IUTKPropertyItem<int> item, int oldValue, int newValue)
{
if (_value != newValue)
{
_value = newValue;
UpdateValueUI();
}
}
#endregion
#region Dispose
protected override void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
UnregisterEvents();
Unbind();
OnValueChanged = null;
_intField = null;
_slider = null;
}
base.Dispose(disposing);
}
#endregion
}
}