Files
EnglewoodLAB/Assets/Scripts/UVC/UIToolkit/Property/Views/UTKFloatRangePropertyItemView.cs

537 lines
17 KiB
C#

#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// FloatRange 속성 View 클래스입니다.
/// Min, Max 두 개의 UTKFloatField 또는 UTKFloatStepper를 사용하여 실수 범위를 표시/편집합니다.
/// </summary>
[UxmlElement]
public partial class UTKFloatRangePropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<UTKFloatRange>
{
#region Fields
private UTKFloatField? _minField;
private UTKFloatField? _maxField;
private UTKFloatStepper? _minStepper;
private UTKFloatStepper? _maxStepper;
private UTKFloatRange _value;
private bool _useStepper;
private float _stepperMinValue = float.MinValue;
private float _stepperMaxValue = float.MaxValue;
private float _stepperStep = 1.0f;
private IUTKPropertyItem<UTKFloatRange>? _boundData;
#endregion
#region Properties
protected override string ViewTypeName => "UTKFloatRangePropertyItemView";
/// <summary>스테퍼(증감 버튼) 사용 여부</summary>
public bool UseStepper
{
get => _useStepper;
set
{
if (_useStepper != value)
{
_useStepper = value;
UpdateModeClass();
}
}
}
/// <summary>스테퍼 최소값</summary>
public float StepperMinValue
{
get => _stepperMinValue;
set
{
_stepperMinValue = value;
if (_minStepper != null) _minStepper.MinValue = value;
if (_maxStepper != null) _maxStepper.MinValue = value;
}
}
/// <summary>스테퍼 최대값</summary>
public float StepperMaxValue
{
get => _stepperMaxValue;
set
{
_stepperMaxValue = value;
if (_minStepper != null) _minStepper.MaxValue = value;
if (_maxStepper != null) _maxStepper.MaxValue = value;
}
}
/// <summary>스테퍼 증감 단위</summary>
public float StepperStep
{
get => _stepperStep;
set
{
_stepperStep = value > 0 ? value : 1.0f;
if (_minStepper != null) _minStepper.Step = _stepperStep;
if (_maxStepper != null) _maxStepper.Step = _stepperStep;
}
}
/// <summary>현재 값</summary>
public UTKFloatRange Value
{
get => _value;
set
{
if (!_value.Equals(value))
{
_value = value;
UpdateValueUI();
OnValueChanged?.Invoke(value);
if (_boundData != null && !_boundData.Value.Equals(value))
{
_boundData.Value = value;
}
}
}
}
#endregion
#region Events
public event Action<UTKFloatRange>? OnValueChanged;
#endregion
#region Constructor
public UTKFloatRangePropertyItemView() : base()
{
InitializeUI();
}
public UTKFloatRangePropertyItemView(string label, UTKFloatRange value = default) : base()
{
_value = value;
Label = label;
InitializeUI();
}
public UTKFloatRangePropertyItemView(string label, float min, float max) : base()
{
_value = new UTKFloatRange(min, max);
Label = label;
InitializeUI();
}
#endregion
#region Initialization
private void InitializeUI()
{
AddToClassList("utk-property-item-view");
AddToClassList("utk-property-item-view--float-range");
if (!CreateUIFromUxml())
{
CreateUIFallback();
}
// UXML에서 요소 가져오기
QueryUIElements();
// 이벤트 등록
RegisterEvents();
UpdateValueUI();
UpdateReadOnlyState();
}
private void QueryUIElements()
{
_minField = this.Q<UTKFloatField>("min-field");
_maxField = this.Q<UTKFloatField>("max-field");
_minStepper = this.Q<UTKFloatStepper>("min-stepper");
_maxStepper = this.Q<UTKFloatStepper>("max-stepper");
// Fallback: UXML에서 못 찾으면 생성
if (_valueContainer != null)
{
_valueContainer.style.flexDirection = FlexDirection.Row;
// FloatField 생성
if (_minField == null)
{
_minField = new UTKFloatField { name = "min-field" };
_minField.AddToClassList("utk-property-item-view__field");
_valueContainer.Add(_minField);
}
if (_maxField == null)
{
var separator = new UTKLabel("~", UTKLabel.LabelSize.Body2);
separator.AddToClassList("utk-property-item-view__range-separator");
_valueContainer.Add(separator);
_maxField = new UTKFloatField { name = "max-field" };
_maxField.AddToClassList("utk-property-item-view__field");
_valueContainer.Add(_maxField);
}
// FloatStepper 생성
if (_minStepper == null)
{
_minStepper = new UTKFloatStepper(_stepperMinValue, _stepperMaxValue, _value.Min, _stepperStep, IsReadOnly)
{
name = "min-stepper"
};
_minStepper.AddToClassList("utk-property-item-view__stepper");
_valueContainer.Add(_minStepper);
}
if (_maxStepper == null)
{
_maxStepper = new UTKFloatStepper(_stepperMinValue, _stepperMaxValue, _value.Max, _stepperStep, IsReadOnly)
{
name = "max-stepper"
};
_maxStepper.AddToClassList("utk-property-item-view__stepper");
_valueContainer.Add(_maxStepper);
}
}
// 초기 값 설정
if (_minField != null)
{
_minField.SetValueWithoutNotify(_value.Min);
_minField.isReadOnly = IsReadOnly;
}
if (_maxField != null)
{
_maxField.SetValueWithoutNotify(_value.Max);
_maxField.isReadOnly = IsReadOnly;
}
if (_minStepper != null)
{
_minStepper.MinValue = _stepperMinValue;
_minStepper.MaxValue = _stepperMaxValue;
_minStepper.Step = _stepperStep;
_minStepper.SetValue(_value.Min, false);
_minStepper.IsReadOnly = IsReadOnly;
}
if (_maxStepper != null)
{
_maxStepper.MinValue = _stepperMinValue;
_maxStepper.MaxValue = _stepperMaxValue;
_maxStepper.Step = _stepperStep;
_maxStepper.SetValue(_value.Max, false);
_maxStepper.IsReadOnly = IsReadOnly;
}
// 컨트롤 가시성 업데이트
UpdateControlVisibility();
}
private void RegisterEvents()
{
if (_minField != null)
{
_minField.OnValueChanged += OnMinChanged;
}
if (_maxField != null)
{
_maxField.OnValueChanged += OnMaxChanged;
}
if (_minStepper != null)
{
_minStepper.OnValueChanged += OnMinStepperChanged;
}
if (_maxStepper != null)
{
_maxStepper.OnValueChanged += OnMaxStepperChanged;
}
}
private void UnregisterEvents()
{
if (_minField != null)
{
_minField.OnValueChanged -= OnMinChanged;
}
if (_maxField != null)
{
_maxField.OnValueChanged -= OnMaxChanged;
}
if (_minStepper != null)
{
_minStepper.OnValueChanged -= OnMinStepperChanged;
}
if (_maxStepper != null)
{
_maxStepper.OnValueChanged -= OnMaxStepperChanged;
}
}
private void UpdateModeClass()
{
// ReadOnly 상태에서는 무조건 FloatField만 표시
if (IsReadOnly)
{
RemoveFromClassList("utk-property-item-view--stepper");
UpdateControlVisibility();
return;
}
// 스테퍼 클래스
if (_useStepper)
{
AddToClassList("utk-property-item-view--stepper");
}
else
{
RemoveFromClassList("utk-property-item-view--stepper");
}
UpdateControlVisibility();
}
/// <summary>컨트롤 가시성을 업데이트합니다.</summary>
private void UpdateControlVisibility()
{
bool isReadOnlyMode = IsReadOnly;
bool showStepper = !isReadOnlyMode && _useStepper;
bool showFloatField = isReadOnlyMode || !_useStepper;
if (_minField != null)
{
_minField.style.display = showFloatField ? DisplayStyle.Flex : DisplayStyle.None;
}
if (_maxField != null)
{
_maxField.style.display = showFloatField ? DisplayStyle.Flex : DisplayStyle.None;
}
if (_minStepper != null)
{
_minStepper.style.display = showStepper ? DisplayStyle.Flex : DisplayStyle.None;
}
if (_maxStepper != null)
{
_maxStepper.style.display = showStepper ? DisplayStyle.Flex : DisplayStyle.None;
}
}
#endregion
#region Override Methods
protected override void CreateValueUI(VisualElement container)
{
// UXML/QueryUIElements 기반으로 생성하므로 여기서는 생성하지 않음
}
public override void RefreshUI()
{
UpdateValueUI();
}
protected override void OnReadOnlyStateChanged(bool isReadOnly)
{
if (_minField != null) _minField.isReadOnly = isReadOnly;
if (_maxField != null) _maxField.isReadOnly = isReadOnly;
if (_minStepper != null) _minStepper.IsReadOnly = isReadOnly;
if (_maxStepper != null) _maxStepper.IsReadOnly = isReadOnly;
// ReadOnly 상태 변경 시 모드 클래스 업데이트
UpdateModeClass();
}
#endregion
#region Event Handling
private void OnMinChanged(float newMin)
{
var newValue = new UTKFloatRange(newMin, _value.Max);
if (!_value.Equals(newValue))
{
_value = newValue;
OnValueChanged?.Invoke(newValue);
if (_boundData != null && !_boundData.Value.Equals(newValue))
{
_boundData.Value = newValue;
}
}
}
private void OnMaxChanged(float newMax)
{
var newValue = new UTKFloatRange(_value.Min, newMax);
if (!_value.Equals(newValue))
{
_value = newValue;
OnValueChanged?.Invoke(newValue);
if (_boundData != null && !_boundData.Value.Equals(newValue))
{
_boundData.Value = newValue;
}
}
}
private void OnMinStepperChanged(float newMin)
{
var newValue = new UTKFloatRange(newMin, _value.Max);
if (!_value.Equals(newValue))
{
_value = newValue;
OnValueChanged?.Invoke(newValue);
if (_boundData != null && !_boundData.Value.Equals(newValue))
{
_boundData.Value = newValue;
}
}
}
private void OnMaxStepperChanged(float newMax)
{
var newValue = new UTKFloatRange(_value.Min, newMax);
if (!_value.Equals(newValue))
{
_value = newValue;
OnValueChanged?.Invoke(newValue);
if (_boundData != null && !_boundData.Value.Equals(newValue))
{
_boundData.Value = newValue;
}
}
}
#endregion
#region Value Update
private void UpdateValueUI()
{
if (_minField != null && !Mathf.Approximately(_minField.Value, _value.Min))
{
_minField.SetValueWithoutNotify(_value.Min);
}
if (_maxField != null && !Mathf.Approximately(_maxField.Value, _value.Max))
{
_maxField.SetValueWithoutNotify(_value.Max);
}
if (_minStepper != null && !Mathf.Approximately(_minStepper.Value, _value.Min))
{
_minStepper.SetValue(_value.Min, false);
}
if (_maxStepper != null && !Mathf.Approximately(_maxStepper.Value, _value.Max))
{
_maxStepper.SetValue(_value.Max, false);
}
}
#endregion
#region Data Binding
public void Bind(IUTKPropertyItem data)
{
if (data is IUTKPropertyItem<UTKFloatRange> rangeData)
{
Bind(rangeData);
}
else
{
Debug.LogWarning($"[UTKFloatRangePropertyItemView] Cannot bind to non-FloatRange data: {data.GetType().Name}");
}
}
public void Bind(IUTKPropertyItem<UTKFloatRange> data)
{
Unbind();
_boundData = data;
BindBase(data);
Label = data.Name;
_value = data.Value;
IsVisible = data.IsVisible;
TooltipText = data.Tooltip;
ShowLabel = data.ShowLabel;
// UTKFloatRangePropertyItem의 스테퍼 설정 적용
if (data is UTKFloatRangePropertyItem rangeItem)
{
_useStepper = rangeItem.UseStepper;
_stepperMinValue = rangeItem.StepperMinValue;
_stepperMaxValue = rangeItem.StepperMaxValue;
_stepperStep = rangeItem.StepperStep;
// 스테퍼에 값 적용
if (_minStepper != null)
{
_minStepper.MinValue = _stepperMinValue;
_minStepper.MaxValue = _stepperMaxValue;
_minStepper.Step = _stepperStep;
}
if (_maxStepper != null)
{
_maxStepper.MinValue = _stepperMinValue;
_maxStepper.MaxValue = _stepperMaxValue;
_maxStepper.Step = _stepperStep;
}
UpdateModeClass();
}
data.OnTypedValueChanged += OnDataValueChanged;
UpdateValueUI();
IsReadOnly = data.IsReadOnly;
}
public void Unbind()
{
if (_boundData != null)
{
_boundData.OnTypedValueChanged -= OnDataValueChanged;
UnbindBase();
_boundData = null;
}
}
private void OnDataValueChanged(IUTKPropertyItem<UTKFloatRange> item, UTKFloatRange oldValue, UTKFloatRange newValue)
{
if (!_value.Equals(newValue))
{
_value = newValue;
UpdateValueUI();
}
}
#endregion
#region Dispose
protected override void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
UnregisterEvents();
Unbind();
OnValueChanged = null;
_minField = null;
_maxField = null;
_minStepper?.Dispose();
_minStepper = null;
_maxStepper?.Dispose();
_maxStepper = null;
}
base.Dispose(disposing);
}
#endregion
}
}