From 18af2fc9c680396ab25f72e7f1b7894e95341b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EC=9D=B8?= Date: Fri, 6 Feb 2026 00:35:15 +0900 Subject: [PATCH] =?UTF-8?q?UTKIntPropertyItemView,=20UTKIntRangePropertyIt?= =?UTF-8?q?emView=20UTKNumberStepper=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Views/UTKIntRangePropertyItemView.uxml | 2 + .../Views/UTKIntRangePropertyItemViewUss.uss | 8 + .../UIToolkit/UTKPropertyListWindowSample.cs | 18 +- .../Property/Items/UTKDatePropertyItem.cs | 35 +++ .../Property/Items/UTKFloatPropertyItem.cs | 2 +- .../Property/Items/UTKIntPropertyItem.cs | 8 +- .../Property/Items/UTKIntRangePropertyItem.cs | 72 ++++- .../Views/UTKIntRangePropertyItemView.cs | 248 +++++++++++++++++- 8 files changed, 371 insertions(+), 22 deletions(-) diff --git a/Assets/Resources/UIToolkit/Property/Views/UTKIntRangePropertyItemView.uxml b/Assets/Resources/UIToolkit/Property/Views/UTKIntRangePropertyItemView.uxml index 5800a9ce..1c1482ed 100644 --- a/Assets/Resources/UIToolkit/Property/Views/UTKIntRangePropertyItemView.uxml +++ b/Assets/Resources/UIToolkit/Property/Views/UTKIntRangePropertyItemView.uxml @@ -4,8 +4,10 @@ + + diff --git a/Assets/Resources/UIToolkit/Property/Views/UTKIntRangePropertyItemViewUss.uss b/Assets/Resources/UIToolkit/Property/Views/UTKIntRangePropertyItemViewUss.uss index a78b3f95..802f6205 100644 --- a/Assets/Resources/UIToolkit/Property/Views/UTKIntRangePropertyItemViewUss.uss +++ b/Assets/Resources/UIToolkit/Property/Views/UTKIntRangePropertyItemViewUss.uss @@ -9,6 +9,14 @@ .utk-property-item-view--int-range .utk-property-item-view__field { flex-grow: 1; + flex-shrink: 1; + flex-basis: 0; +} + +.utk-property-item-view--int-range .utk-property-item-view__stepper { + flex-grow: 1; + flex-shrink: 1; + flex-basis: 0; } .utk-property-item-view--int-range .utk-property-item-view__range-separator { diff --git a/Assets/Sample/UIToolkit/UTKPropertyListWindowSample.cs b/Assets/Sample/UIToolkit/UTKPropertyListWindowSample.cs index ee1b519e..0b7ef0b0 100644 --- a/Assets/Sample/UIToolkit/UTKPropertyListWindowSample.cs +++ b/Assets/Sample/UIToolkit/UTKPropertyListWindowSample.cs @@ -180,17 +180,13 @@ namespace UVC.Sample.UIToolkit entries.Add(new UTKDatePropertyItem("date", "Date", DateTime.Today)); // Date (읽기 전용) - var roDate = new UTKDatePropertyItem("date_ro", "Date (RO)", DateTime.Today.AddDays(7)); - roDate.IsReadOnly = true; - entries.Add(roDate); + entries.Add(new UTKDatePropertyItem("date_ro", "Date (RO)", DateTime.Today.AddDays(7), true)); // DateTime (편집 가능) entries.Add(new UTKDateTimePropertyItem("datetime", "DateTime", DateTime.Now)); // DateTime (읽기 전용) - var roDateTime = new UTKDateTimePropertyItem("datetime_ro", "DateTime (RO)", DateTime.Now.AddHours(1)); - roDateTime.IsReadOnly = true; - entries.Add(roDateTime); + entries.Add(new UTKDateTimePropertyItem("datetime_ro", "DateTime (RO)", DateTime.Now.AddHours(1), true)); // DateRange (편집 가능) entries.Add(new UTKDateRangePropertyItem("daterange", "DateRange", @@ -244,9 +240,13 @@ namespace UVC.Sample.UIToolkit entries.Add(new UTKIntRangePropertyItem("intrange", "IntRange", 10, 90)); // IntRange (읽기 전용) - var roIntRange = new UTKIntRangePropertyItem("intrange_ro", "IntRange (RO)", 20, 80); - roIntRange.IsReadOnly = true; - entries.Add(roIntRange); + entries.Add(new UTKIntRangePropertyItem("intrange_ro", "IntRange (RO)", 20, 80, isReadOnly: true)); + + // IntRange (편집 가능) + entries.Add(new UTKIntRangePropertyItem("intrange2", "IntRange2", 10, 90, useStepper: true)); + + // IntRange (읽기 전용) + entries.Add(new UTKIntRangePropertyItem("intrange2_ro", "IntRange2 (RO)", 20, 80, useStepper: true, isReadOnly: true)); // FloatRange (편집 가능) entries.Add(new UTKFloatRangePropertyItem("floatrange", "FloatRange", 1.5f, 8.5f)); diff --git a/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKDatePropertyItem.cs b/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKDatePropertyItem.cs index 5a3f01d1..f4efd824 100644 --- a/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKDatePropertyItem.cs +++ b/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKDatePropertyItem.cs @@ -11,6 +11,8 @@ namespace UVC.UIToolkit { #region Fields private string _dateFormat = "yyyy-MM-dd"; + private DateTime _minDay = DateTime.MinValue; + private DateTime _maxDay = DateTime.MaxValue; #endregion #region Properties @@ -23,6 +25,20 @@ namespace UVC.UIToolkit get => _dateFormat; set => _dateFormat = value ?? "yyyy-MM-dd"; } + + /// 최소 날짜 + public DateTime MinDay + { + get => _minDay; + set => _minDay = value; + } + + /// 최대 날짜 + public DateTime MaxDay + { + get => _maxDay; + set => _maxDay = value; + } #endregion #region Constructor @@ -36,6 +52,25 @@ namespace UVC.UIToolkit public UTKDatePropertyItem(string id, string name, DateTime initialValue = default, bool isReadOnly = false) : base(id, name, initialValue == default ? DateTime.Today : initialValue) { + _minDay = DateTime.MinValue; + _maxDay = DateTime.MaxValue; + IsReadOnly = isReadOnly; + } + + /// + /// 날짜 속성을 생성합니다 (날짜 제한 포함). + /// + /// 고유 ID + /// 표시 이름 + /// 초기 값 + /// 최소 날짜 (null이면 제한 없음) + /// 최대 날짜 (null이면 제한 없음) + /// 읽기 전용 여부 + public UTKDatePropertyItem(string id, string name, DateTime initialValue, DateTime? minDay, DateTime? maxDay, bool isReadOnly = false) + : base(id, name, initialValue == default ? DateTime.Today : initialValue) + { + _minDay = minDay ?? DateTime.MinValue; + _maxDay = maxDay ?? DateTime.MaxValue; IsReadOnly = isReadOnly; } #endregion diff --git a/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKFloatPropertyItem.cs b/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKFloatPropertyItem.cs index 913694c5..5078ac15 100644 --- a/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKFloatPropertyItem.cs +++ b/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKFloatPropertyItem.cs @@ -64,7 +64,7 @@ namespace UVC.UIToolkit /// 최대값 /// 슬라이더 사용 여부 /// 읽기 전용 여부 - public UTKFloatPropertyItem(string id, string name, float initialValue, float minValue, float maxValue, bool useSlider = true, bool isReadOnly = false) + public UTKFloatPropertyItem(string id, string name, float initialValue, float minValue = float.MinValue, float maxValue = float.MaxValue, bool useSlider = true, bool isReadOnly = false) : base(id, name, initialValue) { _minValue = minValue; diff --git a/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKIntPropertyItem.cs b/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKIntPropertyItem.cs index 33916779..500d2c55 100644 --- a/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKIntPropertyItem.cs +++ b/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKIntPropertyItem.cs @@ -12,8 +12,8 @@ namespace UVC.UIToolkit #region Fields private bool _useSlider; private bool _useStepper; - private int _minValue; - private int _maxValue = 100; + private int _minValue = int.MinValue; + private int _maxValue = int.MaxValue; private int _step = 1; #endregion @@ -28,8 +28,6 @@ namespace UVC.UIToolkit set { _useSlider = value; - // UseSlider가 true이면 UseStepper는 false로 - if (value) _useStepper = false; } } @@ -40,8 +38,6 @@ namespace UVC.UIToolkit set { _useStepper = value; - // UseStepper가 true이면 UseSlider는 false로 - if (value) _useSlider = false; } } diff --git a/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKIntRangePropertyItem.cs b/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKIntRangePropertyItem.cs index 8b3ce8c0..233c5bc5 100644 --- a/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKIntRangePropertyItem.cs +++ b/Assets/Scripts/UVC/UIToolkit/Property/Items/UTKIntRangePropertyItem.cs @@ -1,4 +1,5 @@ #nullable enable +using System; namespace UVC.UIToolkit { @@ -8,14 +9,52 @@ namespace UVC.UIToolkit /// public class UTKIntRangePropertyItem : UTKPropertyItemBase { + #region Fields + private bool _useStepper; + private int _stepperMinValue = int.MinValue; + private int _stepperMaxValue = int.MaxValue; + private int _stepperStep = 1; + #endregion + #region Properties /// 속성 타입 public override UTKPropertyType PropertyType => UTKPropertyType.IntRange; + + /// 스테퍼(증감 버튼) 사용 여부 + public bool UseStepper + { + get => _useStepper; + set + { + _useStepper = value; + } + } + + /// 스테퍼 증감 단위 (기본값: 1) + public int StepperStep + { + get => _stepperStep; + set => _stepperStep = Math.Max(1, value); + } + + /// 스테퍼 최소값 + public int StepperMinValue + { + get => _stepperMinValue; + set => _stepperMinValue = value; + } + + /// 스테퍼 최대값 + public int StepperMaxValue + { + get => _stepperMaxValue; + set => _stepperMaxValue = value; + } #endregion #region Constructor /// - /// 정수 범위 속성을 생성합니다. + /// 정수 범위 속성을 생성합니다 (필드 모드). /// /// 고유 ID /// 표시 이름 @@ -34,10 +73,39 @@ namespace UVC.UIToolkit /// 표시 이름 /// 최소값 /// 최대값 + /// 스테퍼 증감 단위 + /// 스테퍼 최소값 + /// 스테퍼 최대값 + /// 스테퍼 사용 여부 /// 읽기 전용 여부 - public UTKIntRangePropertyItem(string id, string name, int min, int max, bool isReadOnly = false) + public UTKIntRangePropertyItem(string id, string name, int min, int max, int stepperStep = 1, int stepperMinValue = int.MinValue, int stepperMaxValue = int.MaxValue, bool useStepper = false, bool isReadOnly = false) : base(id, name, new UTKIntRange(min, max)) { + _stepperMinValue = stepperMinValue; + _stepperMaxValue = stepperMaxValue; + _useStepper = useStepper; + _stepperStep = stepperStep; + IsReadOnly = isReadOnly; + } + + /// + /// 정수 범위 속성을 생성합니다 (슬라이더/스테퍼 모드). + /// + /// 고유 ID + /// 표시 이름 + /// 초기 범위 값 + /// 스테퍼 증감 단위 + /// 스테퍼 최소값 + /// 스테퍼 최대값 + /// 스테퍼 사용 여부 + /// 읽기 전용 여부 + public UTKIntRangePropertyItem(string id, string name, UTKIntRange initialValue, int stepperStep = 1, int stepperMinValue = int.MinValue, int stepperMaxValue = int.MaxValue, bool useStepper = false, bool isReadOnly = false) + : base(id, name, initialValue) + { + _stepperMinValue = stepperMinValue; + _stepperMaxValue = stepperMaxValue; + _useStepper = useStepper; + _stepperStep = stepperStep; IsReadOnly = isReadOnly; } #endregion diff --git a/Assets/Scripts/UVC/UIToolkit/Property/Views/UTKIntRangePropertyItemView.cs b/Assets/Scripts/UVC/UIToolkit/Property/Views/UTKIntRangePropertyItemView.cs index ae09065b..df7bb79b 100644 --- a/Assets/Scripts/UVC/UIToolkit/Property/Views/UTKIntRangePropertyItemView.cs +++ b/Assets/Scripts/UVC/UIToolkit/Property/Views/UTKIntRangePropertyItemView.cs @@ -15,14 +15,70 @@ namespace UVC.UIToolkit #region Fields private UTKIntegerField? _minField; private UTKIntegerField? _maxField; + private UTKNumberStepper? _minStepper; + private UTKNumberStepper? _maxStepper; private UTKIntRange _value; + private bool _useStepper; + private int _stepperMinValue = int.MinValue; + private int _stepperMaxValue = int.MaxValue; + private int _stepperStep = 1; private IUTKPropertyItem? _boundData; #endregion #region Properties protected override string ViewTypeName => "UTKIntRangePropertyItemView"; + /// 스테퍼(증감 버튼) 사용 여부 + public bool UseStepper + { + get => _useStepper; + set + { + if (_useStepper != value) + { + _useStepper = value; + UpdateModeClass(); + } + } + } + + /// 스테퍼 최소값 + public int StepperMinValue + { + get => _stepperMinValue; + set + { + _stepperMinValue = value; + if (_minStepper != null) _minStepper.MinValue = value; + if (_maxStepper != null) _maxStepper.MinValue = value; + } + } + + /// 스테퍼 최대값 + public int StepperMaxValue + { + get => _stepperMaxValue; + set + { + _stepperMaxValue = value; + if (_minStepper != null) _minStepper.MaxValue = value; + if (_maxStepper != null) _maxStepper.MaxValue = value; + } + } + + /// 스테퍼 증감 단위 + public int StepperStep + { + get => _stepperStep; + set + { + _stepperStep = Math.Max(1, value); + if (_minStepper != null) _minStepper.Step = _stepperStep; + if (_maxStepper != null) _maxStepper.Step = _stepperStep; + } + } + /// 현재 값 public UTKIntRange Value { @@ -94,31 +150,57 @@ namespace UVC.UIToolkit { _minField = this.Q("min-field"); _maxField = this.Q("max-field"); + _minStepper = this.Q("min-stepper"); + _maxStepper = this.Q("max-stepper"); // Fallback: UXML에서 못 찾으면 생성 if (_valueContainer != null) { _valueContainer.style.flexDirection = FlexDirection.Row; + // IntegerField 생성 if (_minField == null) { _minField = new UTKIntegerField { name = "min-field" }; _minField.style.flexGrow = 1; _minField.AddToClassList("utk-property-item-view__field"); _valueContainer.Add(_minField); - - var separator = new UTKLabel("~", UTKLabel.LabelSize.Body2); - separator.AddToClassList("utk-property-item-view__range-separator"); - _valueContainer.Add(separator); } if (_maxField == null) { + var separator = new UTKLabel("~", UTKLabel.LabelSize.Body2); + separator.AddToClassList("utk-property-item-view__range-separator"); + _valueContainer.Add(separator); + _maxField = new UTKIntegerField { name = "max-field" }; _maxField.style.flexGrow = 1; _maxField.AddToClassList("utk-property-item-view__field"); _valueContainer.Add(_maxField); } + + // NumberStepper 생성 + if (_minStepper == null) + { + _minStepper = new UTKNumberStepper(_stepperMinValue, _stepperMaxValue, _value.Min, _stepperStep, IsReadOnly) + { + name = "min-stepper" + }; + _minStepper.style.flexGrow = 1; + _minStepper.AddToClassList("utk-property-item-view__stepper"); + _valueContainer.Add(_minStepper); + } + + if (_maxStepper == null) + { + _maxStepper = new UTKNumberStepper(_stepperMinValue, _stepperMaxValue, _value.Max, _stepperStep, IsReadOnly) + { + name = "max-stepper" + }; + _maxStepper.style.flexGrow = 1; + _maxStepper.AddToClassList("utk-property-item-view__stepper"); + _valueContainer.Add(_maxStepper); + } } // 초기 값 설정 @@ -132,6 +214,25 @@ namespace UVC.UIToolkit _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() @@ -144,6 +245,14 @@ namespace UVC.UIToolkit { _maxField.OnValueChanged += OnMaxChanged; } + if (_minStepper != null) + { + _minStepper.OnValueChanged += OnMinStepperChanged; + } + if (_maxStepper != null) + { + _maxStepper.OnValueChanged += OnMaxStepperChanged; + } } private void UnregisterEvents() @@ -156,6 +265,65 @@ namespace UVC.UIToolkit { _maxField.OnValueChanged -= OnMaxChanged; } + if (_minStepper != null) + { + _minStepper.OnValueChanged -= OnMinStepperChanged; + } + if (_maxStepper != null) + { + _maxStepper.OnValueChanged -= OnMaxStepperChanged; + } + } + + private void UpdateModeClass() + { + // ReadOnly 상태에서는 무조건 IntegerField만 표시 + 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(); + } + + /// 컨트롤 가시성을 업데이트합니다. + private void UpdateControlVisibility() + { + bool isReadOnlyMode = IsReadOnly; + bool showStepper = !isReadOnlyMode && _useStepper; + bool showIntField = isReadOnlyMode || !_useStepper; + + if (_minField != null) + { + _minField.style.display = showIntField ? DisplayStyle.Flex : DisplayStyle.None; + } + + if (_maxField != null) + { + _maxField.style.display = showIntField ? 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 @@ -174,6 +342,11 @@ namespace UVC.UIToolkit { 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 @@ -207,6 +380,36 @@ namespace UVC.UIToolkit } } } + + private void OnMinStepperChanged(int newMin) + { + var newValue = new UTKIntRange(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(int newMax) + { + var newValue = new UTKIntRange(_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 @@ -221,6 +424,16 @@ namespace UVC.UIToolkit { _maxField.SetValueWithoutNotify(_value.Max); } + + if (_minStepper != null && _minStepper.Value != _value.Min) + { + _minStepper.SetValue(_value.Min, false); + } + + if (_maxStepper != null && _maxStepper.Value != _value.Max) + { + _maxStepper.SetValue(_value.Max, false); + } } #endregion @@ -249,6 +462,31 @@ namespace UVC.UIToolkit IsVisible = data.IsVisible; TooltipText = data.Tooltip; + // UTKIntRangePropertyItem의 스테퍼 설정 적용 + if (data is UTKIntRangePropertyItem 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(); @@ -287,6 +525,8 @@ namespace UVC.UIToolkit OnValueChanged = null; _minField = null; _maxField = null; + _minStepper = null; + _maxStepper = null; } base.Dispose(disposing);