#nullable enable using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// 실수 속성 아이템 /// FloatField 또는 Slider 사용 /// public class UTKFloatPropertyItem : UTKPropertyItemBase { #region Fields private FloatField? _floatField; private Slider? _slider; private bool _useSlider; private float _minValue; private float _maxValue = 1f; #endregion #region Properties public override UTKPropertyType PropertyType => UTKPropertyType.Float; /// 슬라이더 사용 여부 public bool UseSlider { get => _useSlider; set => _useSlider = value; } /// 최소값 (슬라이더 모드) public float MinValue { get => _minValue; set { _minValue = value; if (_slider != null) { _slider.lowValue = value; } } } /// 최대값 (슬라이더 모드) public float MaxValue { get => _maxValue; set { _maxValue = value; if (_slider != null) { _slider.highValue = value; } } } #endregion #region Constructor public UTKFloatPropertyItem(string id, string name, float initialValue = 0f) : base(id, name, initialValue) { } public UTKFloatPropertyItem(string id, string name, float initialValue, float minValue, float maxValue, bool useSlider = true) : base(id, name, initialValue) { _minValue = minValue; _maxValue = maxValue; _useSlider = useSlider; } #endregion #region Override Methods public override VisualElement CreateUI() { var container = CreateContainer(); if (_useSlider) { container.AddToClassList("utk-property-item--slider"); } var label = CreateNameLabel(); container.Add(label); var valueContainer = new VisualElement(); valueContainer.AddToClassList("utk-property-item__value"); if (_useSlider) { _slider = new Slider(_minValue, _maxValue); _slider.name = "slider-field"; _slider.value = Value; _slider.AddToClassList("utk-property-item__slider"); valueContainer.Add(_slider); _floatField = new FloatField(); _floatField.name = "value-field"; _floatField.value = Value; _floatField.AddToClassList("utk-property-item__number-field"); valueContainer.Add(_floatField); } else { _floatField = new FloatField(); _floatField.name = "value-field"; _floatField.value = Value; valueContainer.Add(_floatField); } container.Add(valueContainer); return container; } public override void BindUI(VisualElement element) { base.BindUI(element); _floatField = element.Q("value-field"); _slider = element.Q("slider-field"); if (_floatField != null) { _floatField.value = Value; _floatField.SetEnabled(!IsReadOnly); _floatField.RegisterValueChangedCallback(OnFloatFieldChanged); } if (_slider != null) { _slider.lowValue = _minValue; _slider.highValue = _maxValue; _slider.value = Value; _slider.SetEnabled(!IsReadOnly); _slider.RegisterValueChangedCallback(OnSliderChanged); } } public override void UnbindUI(VisualElement element) { if (_floatField != null) { _floatField.UnregisterValueChangedCallback(OnFloatFieldChanged); _floatField = null; } if (_slider != null) { _slider.UnregisterValueChangedCallback(OnSliderChanged); _slider = null; } base.UnbindUI(element); } public override void RefreshUI() { if (_floatField != null && !Mathf.Approximately(_floatField.value, Value)) { _floatField.SetValueWithoutNotify(Value); } if (_slider != null && !Mathf.Approximately(_slider.value, Value)) { _slider.SetValueWithoutNotify(Value); } } protected override void UpdateReadOnlyState() { base.UpdateReadOnlyState(); _floatField?.SetEnabled(!IsReadOnly); _slider?.SetEnabled(!IsReadOnly); } #endregion #region Private Methods private void OnFloatFieldChanged(ChangeEvent evt) { float clampedValue = _useSlider ? Mathf.Clamp(evt.newValue, _minValue, _maxValue) : evt.newValue; if (_slider != null && !Mathf.Approximately(_slider.value, clampedValue)) { _slider.SetValueWithoutNotify(clampedValue); } DebounceValueChange(clampedValue, 100).Forget(); } private void OnSliderChanged(ChangeEvent evt) { if (_floatField != null && !Mathf.Approximately(_floatField.value, evt.newValue)) { _floatField.SetValueWithoutNotify(evt.newValue); } // 슬라이더는 빠른 반응이 필요하므로 짧은 디바운스 DebounceValueChange(evt.newValue, 50).Forget(); } #endregion } }