UTKFloatStepper 추가. UTKFloatPropertyItem, UTKFloatPropertyItemView에 추가
This commit is contained in:
@@ -28,6 +28,9 @@ namespace UVC.UIToolkit
|
||||
/// <summary>읽기 전용 여부</summary>
|
||||
bool IsReadOnly { get; set; }
|
||||
|
||||
/// <summary>라벨 표시 여부 (false면 value가 전체 너비 사용)</summary>
|
||||
bool ShowLabel { get; set; }
|
||||
|
||||
/// <summary>속성 타입</summary>
|
||||
UTKPropertyType PropertyType { get; }
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ namespace UVC.UIToolkit
|
||||
/// <summary>문자열 목록 드롭다운</summary>
|
||||
DropdownList,
|
||||
|
||||
/// <summary>다중 선택 드롭다운</summary>
|
||||
MultiSelectDropdownList,
|
||||
|
||||
/// <summary>라디오 버튼 그룹</summary>
|
||||
RadioGroup,
|
||||
|
||||
@@ -57,6 +60,9 @@ namespace UVC.UIToolkit
|
||||
DateTimeRange,
|
||||
|
||||
/// <summary>상태 + 색상 복합 타입</summary>
|
||||
ColorState
|
||||
ColorState,
|
||||
|
||||
/// <summary>버튼 (액션 트리거)</summary>
|
||||
Button
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace UVC.UIToolkit
|
||||
private T _value;
|
||||
private bool _isReadOnly;
|
||||
private bool _isVisible = true;
|
||||
private bool _showLabel = true;
|
||||
private string? _description;
|
||||
private string? _tooltip;
|
||||
private string? _groupId;
|
||||
@@ -103,6 +104,13 @@ namespace UVC.UIToolkit
|
||||
get => _groupId;
|
||||
set => _groupId = value;
|
||||
}
|
||||
|
||||
/// <summary>라벨 표시 여부 (false면 value가 전체 너비 사용)</summary>
|
||||
public bool ShowLabel
|
||||
{
|
||||
get => _showLabel;
|
||||
set => _showLabel = value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
119
Assets/Scripts/UVC/UIToolkit/Property/Items/UTKButtonItem.cs
Normal file
119
Assets/Scripts/UVC/UIToolkit/Property/Items/UTKButtonItem.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 버튼 속성 Item 클래스입니다.
|
||||
/// UTKButton 설정을 포함하며, 클릭 시 Action 이름을 전달합니다.
|
||||
/// </summary>
|
||||
public class UTKButtonItem : UTKPropertyItemBase<string>
|
||||
{
|
||||
#region Properties
|
||||
public override UTKPropertyType PropertyType => UTKPropertyType.Button;
|
||||
|
||||
/// <summary>버튼 텍스트</summary>
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>버튼 아이콘</summary>
|
||||
public string Icon { get; set; }
|
||||
|
||||
/// <summary>아이콘 크기</summary>
|
||||
public int IconSize { get; set; }
|
||||
|
||||
/// <summary>버튼 스타일 변형</summary>
|
||||
public UTKButton.ButtonVariant Variant { get; set; }
|
||||
|
||||
/// <summary>버튼 크기</summary>
|
||||
public UTKButton.ButtonSize Size { get; set; }
|
||||
|
||||
/// <summary>커스텀 배경 색상</summary>
|
||||
public Color? BackgroundColor { get; set; }
|
||||
|
||||
/// <summary>외곽선 굵기</summary>
|
||||
public int BorderWidth { get; set; }
|
||||
|
||||
/// <summary>아이콘만 표시 모드</summary>
|
||||
public bool IconOnly { get; set; }
|
||||
|
||||
/// <summary>라벨 표시 여부 (false면 버튼이 전체 너비 사용)</summary>
|
||||
public bool ShowLabel { get; set; }
|
||||
|
||||
/// <summary>액션 이름 (버튼 클릭 시 전달되는 고유 이름)</summary>
|
||||
public string ActionName { get; }
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// 기본 버튼 아이템을 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="id">고유 ID</param>
|
||||
/// <param name="actionName">액션 이름 (클릭 시 전달)</param>
|
||||
/// <param name="text">버튼 텍스트</param>
|
||||
/// <param name="icon">버튼 아이콘</param>
|
||||
/// <param name="variant">버튼 스타일</param>
|
||||
/// <param name="size">버튼 크기</param>
|
||||
public UTKButtonItem(
|
||||
string id,
|
||||
string actionName,
|
||||
string text = "",
|
||||
string icon = "",
|
||||
UTKButton.ButtonVariant variant = UTKButton.ButtonVariant.Normal,
|
||||
UTKButton.ButtonSize size = UTKButton.ButtonSize.Medium)
|
||||
: base(id, actionName, actionName)
|
||||
{
|
||||
ActionName = actionName;
|
||||
Text = text;
|
||||
Icon = icon;
|
||||
IconSize = 12;
|
||||
Variant = variant;
|
||||
Size = size;
|
||||
BackgroundColor = null;
|
||||
BorderWidth = -1;
|
||||
IconOnly = false;
|
||||
ShowLabel = true; // 기본적으로 라벨 표시
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 전체 설정을 포함한 버튼 아이템을 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="id">고유 ID</param>
|
||||
/// <param name="actionName">액션 이름</param>
|
||||
/// <param name="text">버튼 텍스트</param>
|
||||
/// <param name="icon">버튼 아이콘</param>
|
||||
/// <param name="iconSize">아이콘 크기</param>
|
||||
/// <param name="variant">버튼 스타일</param>
|
||||
/// <param name="size">버튼 크기</param>
|
||||
/// <param name="backgroundColor">배경 색상</param>
|
||||
/// <param name="borderWidth">외곽선 굵기</param>
|
||||
/// <param name="iconOnly">아이콘만 표시</param>
|
||||
/// <param name="showLabel">라벨 표시 여부</param>
|
||||
public UTKButtonItem(
|
||||
string id,
|
||||
string actionName,
|
||||
string text,
|
||||
string icon,
|
||||
int iconSize,
|
||||
UTKButton.ButtonVariant variant,
|
||||
UTKButton.ButtonSize size,
|
||||
Color? backgroundColor,
|
||||
int borderWidth,
|
||||
bool iconOnly,
|
||||
bool showLabel = true)
|
||||
: base(id, actionName, actionName)
|
||||
{
|
||||
ActionName = actionName;
|
||||
Text = text;
|
||||
Icon = icon;
|
||||
IconSize = iconSize;
|
||||
Variant = variant;
|
||||
Size = size;
|
||||
BackgroundColor = backgroundColor;
|
||||
BorderWidth = borderWidth;
|
||||
IconOnly = iconOnly;
|
||||
ShowLabel = showLabel;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 180e39abb0136364c90d52fbd5b63fd6
|
||||
@@ -10,8 +10,10 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
#region Fields
|
||||
private bool _useSlider;
|
||||
private bool _useStepper;
|
||||
private float _minValue;
|
||||
private float _maxValue = 1f;
|
||||
private float _step = 1.0f;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
@@ -25,6 +27,20 @@ namespace UVC.UIToolkit
|
||||
set => _useSlider = value;
|
||||
}
|
||||
|
||||
/// <summary>스테퍼(증감 버튼) 사용 여부</summary>
|
||||
public bool UseStepper
|
||||
{
|
||||
get => _useStepper;
|
||||
set => _useStepper = value;
|
||||
}
|
||||
|
||||
/// <summary>스테퍼 증감 단위 (기본값: 0.1)</summary>
|
||||
public float Step
|
||||
{
|
||||
get => _step;
|
||||
set => _step = value > 0 ? value : 0.1f;
|
||||
}
|
||||
|
||||
/// <summary>최소값 (슬라이더 모드)</summary>
|
||||
public float MinValue
|
||||
{
|
||||
@@ -63,13 +79,15 @@ namespace UVC.UIToolkit
|
||||
/// <param name="minValue">최소값</param>
|
||||
/// <param name="maxValue">최대값</param>
|
||||
/// <param name="useSlider">슬라이더 사용 여부</param>
|
||||
/// <param name="useStepper">스테퍼 사용 여부</param>
|
||||
/// <param name="isReadOnly">읽기 전용 여부</param>
|
||||
public UTKFloatPropertyItem(string id, string name, float initialValue, float minValue = float.MinValue, float maxValue = 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 useStepper = false, bool isReadOnly = false)
|
||||
: base(id, name, initialValue)
|
||||
{
|
||||
_minValue = minValue;
|
||||
_maxValue = maxValue;
|
||||
_useSlider = useSlider;
|
||||
_useStepper = useStepper;
|
||||
IsReadOnly = isReadOnly;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 다중 선택 드롭다운 속성 데이터 클래스입니다.
|
||||
/// UI는 UTKMultiSelectDropdownPropertyItemView에서 담당합니다.
|
||||
/// </summary>
|
||||
public class UTKMultiSelectDropdownPropertyItem : UTKPropertyItemBase<List<string>>
|
||||
{
|
||||
#region Fields
|
||||
private List<string> _choices;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>속성 타입</summary>
|
||||
public override UTKPropertyType PropertyType => UTKPropertyType.MultiSelectDropdownList;
|
||||
|
||||
/// <summary>선택 가능한 항목 목록</summary>
|
||||
public List<string> Choices
|
||||
{
|
||||
get => _choices;
|
||||
set => _choices = value ?? new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>선택된 인덱스 목록</summary>
|
||||
public List<int> SelectedIndices
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Value == null || Value.Count == 0)
|
||||
return new List<int>();
|
||||
|
||||
return Value
|
||||
.Select(v => _choices.IndexOf(v))
|
||||
.Where(i => i >= 0)
|
||||
.OrderBy(i => i)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// 다중 선택 드롭다운 속성을 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="id">고유 ID</param>
|
||||
/// <param name="name">표시 이름</param>
|
||||
/// <param name="choices">선택 항목 목록</param>
|
||||
/// <param name="initialValues">초기 선택 값 목록</param>
|
||||
/// <param name="isReadOnly">읽기 전용 여부</param>
|
||||
public UTKMultiSelectDropdownPropertyItem(
|
||||
string id,
|
||||
string name,
|
||||
List<string> choices,
|
||||
List<string>? initialValues = null,
|
||||
bool isReadOnly = false)
|
||||
: base(id, name, initialValues ?? new List<string>())
|
||||
{
|
||||
_choices = choices ?? new List<string>();
|
||||
|
||||
// initialValues가 유효한지 확인하고 필터링
|
||||
if (initialValues != null && initialValues.Count > 0)
|
||||
{
|
||||
var validValues = initialValues.Where(v => _choices.Contains(v)).ToList();
|
||||
Value = validValues;
|
||||
}
|
||||
|
||||
IsReadOnly = isReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 다중 선택 드롭다운 속성을 생성합니다 (인덱스 기반).
|
||||
/// </summary>
|
||||
/// <param name="id">고유 ID</param>
|
||||
/// <param name="name">표시 이름</param>
|
||||
/// <param name="choices">선택 항목 목록</param>
|
||||
/// <param name="selectedIndices">초기 선택 인덱스 목록</param>
|
||||
/// <param name="isReadOnly">읽기 전용 여부</param>
|
||||
public UTKMultiSelectDropdownPropertyItem(
|
||||
string id,
|
||||
string name,
|
||||
IEnumerable<string> choices,
|
||||
IEnumerable<int>? selectedIndices = null,
|
||||
bool isReadOnly = false)
|
||||
: base(id, name, new List<string>())
|
||||
{
|
||||
_choices = choices?.ToList() ?? new List<string>();
|
||||
|
||||
if (selectedIndices != null)
|
||||
{
|
||||
var validValues = selectedIndices
|
||||
.Where(i => i >= 0 && i < _choices.Count)
|
||||
.Select(i => _choices[i])
|
||||
.ToList();
|
||||
Value = validValues;
|
||||
}
|
||||
|
||||
IsReadOnly = isReadOnly;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>선택 항목 추가</summary>
|
||||
public void AddChoice(string choice)
|
||||
{
|
||||
if (!_choices.Contains(choice))
|
||||
{
|
||||
_choices.Add(choice);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>선택 항목 제거</summary>
|
||||
public bool RemoveChoice(string choice)
|
||||
{
|
||||
bool removed = _choices.Remove(choice);
|
||||
if (removed && Value != null && Value.Contains(choice))
|
||||
{
|
||||
// 값 목록에서도 제거
|
||||
var newValue = Value.Where(v => v != choice).ToList();
|
||||
Value = newValue;
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>인덱스로 선택 설정</summary>
|
||||
public void SetSelectedIndices(List<int> indices)
|
||||
{
|
||||
var validValues = indices
|
||||
.Where(i => i >= 0 && i < _choices.Count)
|
||||
.Select(i => _choices[i])
|
||||
.Distinct()
|
||||
.ToList();
|
||||
Value = validValues;
|
||||
}
|
||||
|
||||
/// <summary>값으로 선택 설정</summary>
|
||||
public void SetSelectedValues(List<string> values)
|
||||
{
|
||||
var validValues = values
|
||||
.Where(v => _choices.Contains(v))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
Value = validValues;
|
||||
}
|
||||
|
||||
/// <summary>모든 항목 선택</summary>
|
||||
public void SelectAll()
|
||||
{
|
||||
Value = new List<string>(_choices);
|
||||
}
|
||||
|
||||
/// <summary>모든 선택 해제</summary>
|
||||
public void ClearSelection()
|
||||
{
|
||||
Value = new List<string>();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ffce5db6e1fd4748b4e6c1d72812fb7
|
||||
@@ -30,6 +30,9 @@ namespace UVC.UIToolkit
|
||||
get => _maxLength;
|
||||
set => _maxLength = value;
|
||||
}
|
||||
|
||||
/// <summary>액션 버튼 설정 (null이면 버튼 미표시)</summary>
|
||||
public UTKButtonItem? ActionButton { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -42,12 +45,14 @@ namespace UVC.UIToolkit
|
||||
/// <param name="isMultiline">멀티라인 모드</param>
|
||||
/// <param name="maxLength">최대 문자 길이</param>
|
||||
/// <param name="isReadOnly">읽기 전용 여부</param>
|
||||
public UTKStringPropertyItem(string id, string name, string initialValue = "", bool isMultiline = false, int maxLength = -1, bool isReadOnly = false)
|
||||
/// <param name="actionButton">액션 버튼</param>
|
||||
public UTKStringPropertyItem(string id, string name, string initialValue = "", bool isMultiline = false, int maxLength = -1, bool isReadOnly = false, UTKButtonItem? actionButton = null)
|
||||
: base(id, name, initialValue ?? string.Empty)
|
||||
{
|
||||
IsReadOnly = isReadOnly;
|
||||
_isMultiline = isMultiline;
|
||||
_maxLength = maxLength;
|
||||
ActionButton = actionButton;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
222
Assets/Scripts/UVC/UIToolkit/Property/Views/UTKButtonItemView.cs
Normal file
222
Assets/Scripts/UVC/UIToolkit/Property/Views/UTKButtonItemView.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 버튼 속성 View 클래스입니다.
|
||||
/// UTKButton을 표시하고 클릭 시 Action 이름을 이벤트로 전달합니다.
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKButtonItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<string>
|
||||
{
|
||||
#region Fields
|
||||
private UTKButton? _button;
|
||||
private IUTKPropertyItem<string>? _boundData;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
protected override string ViewTypeName => "UTKButtonItemView";
|
||||
|
||||
/// <summary>값 변경 이벤트 (IUTKPropertyItemView 인터페이스 구현)</summary>
|
||||
public event Action<string>? OnValueChanged;
|
||||
|
||||
/// <summary>버튼 클릭 이벤트</summary>
|
||||
public event Action<string>? OnButtonClicked;
|
||||
|
||||
/// <summary>액션 이름 (현재 Value)</summary>
|
||||
public string Value
|
||||
{
|
||||
get => _boundData?.Value ?? "";
|
||||
set
|
||||
{
|
||||
if (_boundData != null)
|
||||
{
|
||||
_boundData.Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public UTKButtonItemView() : base()
|
||||
{
|
||||
InitializeUI();
|
||||
}
|
||||
|
||||
public UTKButtonItemView(UTKButtonItem data) : this()
|
||||
{
|
||||
Bind(data);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
private void InitializeUI()
|
||||
{
|
||||
AddToClassList("utk-property-item-view");
|
||||
AddToClassList("utk-property-item-view--button");
|
||||
|
||||
if (!CreateUIFromUxml())
|
||||
{
|
||||
CreateUIFallback();
|
||||
}
|
||||
|
||||
QueryUIElements();
|
||||
}
|
||||
|
||||
private void QueryUIElements()
|
||||
{
|
||||
if (_valueContainer == null) return;
|
||||
|
||||
_button = this.Q<UTKButton>("button-field");
|
||||
if (_button == null)
|
||||
{
|
||||
_button = new UTKButton { name = "button-field" };
|
||||
_button.AddToClassList("utk-property-item-view__button");
|
||||
_valueContainer.Add(_button);
|
||||
}
|
||||
|
||||
// 버튼 클릭 이벤트 등록
|
||||
_button.OnClicked += OnButtonClickedInternal;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Override Methods
|
||||
protected override void CreateValueUI(VisualElement container)
|
||||
{
|
||||
// Fallback: UXML 로드 실패 시 코드로 생성
|
||||
_button = new UTKButton { name = "button-field" };
|
||||
_button.AddToClassList("utk-property-item-view__button");
|
||||
container.Add(_button);
|
||||
|
||||
_button.OnClicked += OnButtonClickedInternal;
|
||||
}
|
||||
|
||||
public override void RefreshUI()
|
||||
{
|
||||
// 버튼은 정적이므로 별도 갱신 불필요
|
||||
}
|
||||
|
||||
protected override void OnReadOnlyStateChanged(bool isReadOnly)
|
||||
{
|
||||
// 버튼은 ReadOnly 개념이 적용되지 않으므로 IsEnabled로 제어
|
||||
if (_button != null)
|
||||
{
|
||||
_button.IsEnabled = !isReadOnly;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handling
|
||||
private void OnButtonClickedInternal()
|
||||
{
|
||||
if (_boundData is UTKButtonItem buttonItem)
|
||||
{
|
||||
OnButtonClicked?.Invoke(buttonItem.ActionName);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Data Binding
|
||||
public void Bind(IUTKPropertyItem data)
|
||||
{
|
||||
if (data is IUTKPropertyItem<string> stringData)
|
||||
{
|
||||
Bind(stringData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[UTKButtonItemView] Cannot bind to non-string data: {data.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Bind(IUTKPropertyItem<string> data)
|
||||
{
|
||||
Unbind();
|
||||
|
||||
_boundData = data;
|
||||
|
||||
// 라벨 텍스트 설정
|
||||
if (_labelElement != null)
|
||||
{
|
||||
_labelElement.Text = data.DisplayName;
|
||||
}
|
||||
|
||||
// UTKButtonItem 설정 적용
|
||||
if (data is UTKButtonItem buttonItem && _button != null)
|
||||
{
|
||||
_button.Text = buttonItem.Text;
|
||||
_button.Icon = buttonItem.Icon;
|
||||
_button.IconSize = buttonItem.IconSize;
|
||||
_button.Variant = buttonItem.Variant;
|
||||
_button.Size = buttonItem.Size;
|
||||
_button.IconOnly = buttonItem.IconOnly;
|
||||
|
||||
if (buttonItem.BackgroundColor.HasValue)
|
||||
{
|
||||
_button.BackgroundColor = buttonItem.BackgroundColor;
|
||||
}
|
||||
|
||||
if (buttonItem.BorderWidth >= 0)
|
||||
{
|
||||
_button.BorderWidth = buttonItem.BorderWidth;
|
||||
}
|
||||
|
||||
_button.IsEnabled = !buttonItem.IsReadOnly;
|
||||
|
||||
// 라벨 표시 여부 제어
|
||||
if (_labelElement != null)
|
||||
{
|
||||
if (buttonItem.ShowLabel)
|
||||
{
|
||||
_labelElement.style.display = DisplayStyle.Flex;
|
||||
RemoveFromClassList("utk-property-item-view--button-no-label");
|
||||
}
|
||||
else
|
||||
{
|
||||
_labelElement.style.display = DisplayStyle.None;
|
||||
AddToClassList("utk-property-item-view--button-no-label");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IsReadOnly = data.IsReadOnly;
|
||||
IsVisible = data.IsVisible;
|
||||
TooltipText = data.Tooltip;
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
if (_boundData != null)
|
||||
{
|
||||
_boundData = null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Dispose
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
if (_button != null)
|
||||
{
|
||||
_button.OnClicked -= OnButtonClickedInternal;
|
||||
_button.Dispose();
|
||||
}
|
||||
|
||||
Unbind();
|
||||
OnValueChanged = null;
|
||||
OnButtonClicked = null;
|
||||
_button = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b8d1cca2a2e54e4ea3536f4de0e1938
|
||||
@@ -10,12 +10,14 @@ namespace UVC.UIToolkit
|
||||
/// <summary>
|
||||
/// Dropdown 속성 View 클래스입니다.
|
||||
/// UTKDropdown을 사용하여 문자열 목록에서 선택합니다.
|
||||
/// ReadOnly 상태에서는 UTKInputField로 표시됩니다.
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKDropdownPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<string>
|
||||
{
|
||||
#region Fields
|
||||
private UTKDropdown? _dropdown;
|
||||
private UTKInputField? _readOnlyField;
|
||||
|
||||
private string _value = string.Empty;
|
||||
private List<string> _choices = new();
|
||||
@@ -125,23 +127,48 @@ namespace UVC.UIToolkit
|
||||
|
||||
private void QueryUIElements()
|
||||
{
|
||||
_dropdown = this.Q<UTKDropdown>("dropdown-field");
|
||||
if (_valueContainer == null) return;
|
||||
|
||||
// Fallback: UXML에서 못 찾으면 생성
|
||||
if (_valueContainer != null && _dropdown == null)
|
||||
if (IsReadOnly)
|
||||
{
|
||||
_dropdown = new UTKDropdown { name = "dropdown-field" };
|
||||
_dropdown.AddToClassList("utk-property-item-view__dropdown");
|
||||
_valueContainer.Add(_dropdown);
|
||||
// ReadOnly: UTKInputField 사용
|
||||
_readOnlyField = this.Q<UTKInputField>("readonly-field");
|
||||
if (_readOnlyField == null)
|
||||
{
|
||||
_readOnlyField = new UTKInputField { name = "readonly-field" };
|
||||
_readOnlyField.AddToClassList("utk-property-item-view__readonly-field");
|
||||
_valueContainer.Add(_readOnlyField);
|
||||
}
|
||||
_readOnlyField.Value = _value;
|
||||
_readOnlyField.isReadOnly = true;
|
||||
|
||||
// Dropdown 숨기기
|
||||
if (_dropdown != null)
|
||||
{
|
||||
_dropdown.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
|
||||
// 초기 값 설정
|
||||
if (_dropdown != null)
|
||||
else
|
||||
{
|
||||
// Editable: UTKDropdown 사용
|
||||
_dropdown = this.Q<UTKDropdown>("dropdown-field");
|
||||
if (_dropdown == null)
|
||||
{
|
||||
_dropdown = new UTKDropdown { name = "dropdown-field" };
|
||||
_dropdown.AddToClassList("utk-property-item-view__dropdown");
|
||||
_valueContainer.Add(_dropdown);
|
||||
}
|
||||
_dropdown.SetOptions(_choices);
|
||||
int selectedIndex = _choices.IndexOf(_value);
|
||||
_dropdown.SelectedIndex = Math.Max(0, selectedIndex);
|
||||
_dropdown.IsEnabled = !IsReadOnly;
|
||||
_dropdown.IsEnabled = true;
|
||||
_dropdown.style.display = DisplayStyle.Flex;
|
||||
|
||||
// ReadOnly 필드 숨기기
|
||||
if (_readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +202,46 @@ namespace UVC.UIToolkit
|
||||
|
||||
protected override void OnReadOnlyStateChanged(bool isReadOnly)
|
||||
{
|
||||
if (_dropdown != null)
|
||||
if (_valueContainer == null) return;
|
||||
|
||||
if (isReadOnly)
|
||||
{
|
||||
_dropdown.IsEnabled = !isReadOnly;
|
||||
// Dropdown → InputField 전환
|
||||
if (_readOnlyField == null)
|
||||
{
|
||||
_readOnlyField = new UTKInputField { name = "readonly-field" };
|
||||
_readOnlyField.AddToClassList("utk-property-item-view__readonly-field");
|
||||
_valueContainer.Add(_readOnlyField);
|
||||
}
|
||||
_readOnlyField.Value = _value;
|
||||
_readOnlyField.isReadOnly = true;
|
||||
_readOnlyField.style.display = DisplayStyle.Flex;
|
||||
|
||||
if (_dropdown != null)
|
||||
{
|
||||
_dropdown.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// InputField → Dropdown 전환
|
||||
if (_dropdown == null)
|
||||
{
|
||||
_dropdown = new UTKDropdown { name = "dropdown-field" };
|
||||
_dropdown.AddToClassList("utk-property-item-view__dropdown");
|
||||
_valueContainer.Add(_dropdown);
|
||||
_dropdown.SetOptions(_choices);
|
||||
RegisterEvents();
|
||||
}
|
||||
int selectedIndex = _choices.IndexOf(_value);
|
||||
_dropdown.SelectedIndex = Math.Max(0, selectedIndex);
|
||||
_dropdown.IsEnabled = true;
|
||||
_dropdown.style.display = DisplayStyle.Flex;
|
||||
|
||||
if (_readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -201,7 +265,11 @@ namespace UVC.UIToolkit
|
||||
#region Value Update
|
||||
private void UpdateValueUI()
|
||||
{
|
||||
if (_dropdown != null && _dropdown.SelectedValue != _value)
|
||||
if (IsReadOnly && _readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.Value = _value;
|
||||
}
|
||||
else if (_dropdown != null && _dropdown.SelectedValue != _value)
|
||||
{
|
||||
_dropdown.SetSelectedValue(_value, notify: false);
|
||||
}
|
||||
@@ -310,6 +378,7 @@ namespace UVC.UIToolkit
|
||||
|
||||
OnValueChanged = null;
|
||||
_dropdown = null;
|
||||
_readOnlyField = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -8,12 +8,14 @@ namespace UVC.UIToolkit
|
||||
/// <summary>
|
||||
/// Enum 속성 View 클래스입니다.
|
||||
/// UTKEnumDropDown을 사용하여 열거형 값을 표시/편집합니다.
|
||||
/// ReadOnly 상태에서는 UTKInputField로 표시됩니다.
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKEnumPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<Enum>
|
||||
{
|
||||
#region Fields
|
||||
private UTKEnumDropDown? _enumDropdown;
|
||||
private UTKInputField? _readOnlyField;
|
||||
|
||||
private Enum? _value;
|
||||
private Type? _enumType;
|
||||
@@ -103,24 +105,49 @@ namespace UVC.UIToolkit
|
||||
|
||||
private void QueryUIElements()
|
||||
{
|
||||
_enumDropdown = this.Q<UTKEnumDropDown>("enum-dropdown");
|
||||
if (_valueContainer == null) return;
|
||||
|
||||
// Fallback: UXML에서 못 찾으면 생성
|
||||
if (_valueContainer != null && _enumDropdown == null)
|
||||
if (IsReadOnly)
|
||||
{
|
||||
_enumDropdown = new UTKEnumDropDown { name = "enum-dropdown" };
|
||||
_enumDropdown.AddToClassList("utk-property-item-view__dropdown");
|
||||
_valueContainer.Add(_enumDropdown);
|
||||
// ReadOnly: UTKInputField 사용
|
||||
_readOnlyField = this.Q<UTKInputField>("readonly-field");
|
||||
if (_readOnlyField == null)
|
||||
{
|
||||
_readOnlyField = new UTKInputField { name = "readonly-field" };
|
||||
_readOnlyField.AddToClassList("utk-property-item-view__readonly-field");
|
||||
_valueContainer.Add(_readOnlyField);
|
||||
}
|
||||
_readOnlyField.Value = _value?.ToString() ?? "";
|
||||
_readOnlyField.isReadOnly = true;
|
||||
|
||||
// Dropdown 숨기기
|
||||
if (_enumDropdown != null)
|
||||
{
|
||||
_enumDropdown.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
|
||||
// 초기 값 설정
|
||||
if (_enumDropdown != null)
|
||||
else
|
||||
{
|
||||
// Editable: UTKEnumDropDown 사용
|
||||
_enumDropdown = this.Q<UTKEnumDropDown>("enum-dropdown");
|
||||
if (_enumDropdown == null)
|
||||
{
|
||||
_enumDropdown = new UTKEnumDropDown { name = "enum-dropdown" };
|
||||
_enumDropdown.AddToClassList("utk-property-item-view__dropdown");
|
||||
_valueContainer.Add(_enumDropdown);
|
||||
}
|
||||
if (_value != null)
|
||||
{
|
||||
_enumDropdown.Init(_value);
|
||||
}
|
||||
_enumDropdown.IsEnabled = !IsReadOnly;
|
||||
_enumDropdown.IsEnabled = true;
|
||||
_enumDropdown.style.display = DisplayStyle.Flex;
|
||||
|
||||
// ReadOnly 필드 숨기기
|
||||
if (_readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,9 +181,47 @@ namespace UVC.UIToolkit
|
||||
|
||||
protected override void OnReadOnlyStateChanged(bool isReadOnly)
|
||||
{
|
||||
if (_enumDropdown != null)
|
||||
if (_valueContainer == null) return;
|
||||
|
||||
if (isReadOnly)
|
||||
{
|
||||
_enumDropdown.IsEnabled = !isReadOnly;
|
||||
// Dropdown → InputField 전환
|
||||
if (_readOnlyField == null)
|
||||
{
|
||||
_readOnlyField = new UTKInputField { name = "readonly-field" };
|
||||
_readOnlyField.AddToClassList("utk-property-item-view__readonly-field");
|
||||
_valueContainer.Add(_readOnlyField);
|
||||
}
|
||||
_readOnlyField.Value = _value?.ToString() ?? "";
|
||||
_readOnlyField.isReadOnly = true;
|
||||
_readOnlyField.style.display = DisplayStyle.Flex;
|
||||
|
||||
if (_enumDropdown != null)
|
||||
{
|
||||
_enumDropdown.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// InputField → Dropdown 전환
|
||||
if (_enumDropdown == null)
|
||||
{
|
||||
_enumDropdown = new UTKEnumDropDown { name = "enum-dropdown" };
|
||||
_enumDropdown.AddToClassList("utk-property-item-view__dropdown");
|
||||
_valueContainer.Add(_enumDropdown);
|
||||
if (_value != null)
|
||||
{
|
||||
_enumDropdown.Init(_value);
|
||||
}
|
||||
RegisterEvents();
|
||||
}
|
||||
_enumDropdown.IsEnabled = true;
|
||||
_enumDropdown.style.display = DisplayStyle.Flex;
|
||||
|
||||
if (_readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -180,7 +245,11 @@ namespace UVC.UIToolkit
|
||||
#region Value Update
|
||||
private void UpdateValueUI()
|
||||
{
|
||||
if (_enumDropdown != null && _value != null)
|
||||
if (IsReadOnly && _readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.Value = _value?.ToString() ?? "";
|
||||
}
|
||||
else if (_enumDropdown != null && _value != null)
|
||||
{
|
||||
if (_enumDropdown.Value != _value)
|
||||
{
|
||||
@@ -258,6 +327,7 @@ namespace UVC.UIToolkit
|
||||
|
||||
OnValueChanged = null;
|
||||
_enumDropdown = null;
|
||||
_readOnlyField = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -37,11 +37,14 @@ namespace UVC.UIToolkit
|
||||
#region Fields
|
||||
private UTKFloatField? _floatField;
|
||||
private UTKSlider? _slider;
|
||||
private UTKFloatStepper? _stepper;
|
||||
|
||||
private float _value;
|
||||
private float _minValue;
|
||||
private float _maxValue = 1f;
|
||||
private float _step = 0.1f;
|
||||
private bool _useSlider;
|
||||
private bool _useStepper;
|
||||
|
||||
private IUTKPropertyItem<float>? _boundData;
|
||||
#endregion
|
||||
@@ -112,10 +115,35 @@ namespace UVC.UIToolkit
|
||||
if (_useSlider != value)
|
||||
{
|
||||
_useSlider = value;
|
||||
UpdateSliderClass();
|
||||
if (value) _useStepper = false;
|
||||
UpdateModeClass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>스테퍼(증감 버튼) 사용 여부</summary>
|
||||
[UxmlAttribute("use-stepper")]
|
||||
public bool UseStepper
|
||||
{
|
||||
get => _useStepper;
|
||||
set
|
||||
{
|
||||
if (_useStepper != value)
|
||||
{
|
||||
_useStepper = value;
|
||||
if (value) _useSlider = false;
|
||||
UpdateModeClass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>스테퍼 증감 단위</summary>
|
||||
[UxmlAttribute("step")]
|
||||
public float Step
|
||||
{
|
||||
get => _step;
|
||||
set => _step = value > 0 ? value : 0.1f;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
@@ -141,7 +169,9 @@ namespace UVC.UIToolkit
|
||||
_value = data.Value;
|
||||
_minValue = data.MinValue;
|
||||
_maxValue = data.MaxValue;
|
||||
_step = data.Step;
|
||||
_useSlider = data.UseSlider;
|
||||
_useStepper = data.UseStepper;
|
||||
Label = data.Name;
|
||||
_isReadOnly = data.IsReadOnly;
|
||||
InitializeUI();
|
||||
@@ -176,8 +206,8 @@ namespace UVC.UIToolkit
|
||||
// 이벤트 등록
|
||||
RegisterEvents();
|
||||
|
||||
// 슬라이더 클래스 업데이트
|
||||
UpdateSliderClass();
|
||||
// 모드 클래스 업데이트
|
||||
UpdateModeClass();
|
||||
|
||||
UpdateValueUI();
|
||||
UpdateReadOnlyState();
|
||||
@@ -187,6 +217,7 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
_slider = this.Q<UTKSlider>("slider-field");
|
||||
_floatField = this.Q<UTKFloatField>("value-field");
|
||||
_stepper = this.Q<UTKFloatStepper>("stepper-field");
|
||||
|
||||
// Fallback: UXML에서 못 찾으면 생성
|
||||
if (_valueContainer != null)
|
||||
@@ -207,6 +238,16 @@ namespace UVC.UIToolkit
|
||||
_floatField.AddToClassList("utk-property-item-view__field");
|
||||
_valueContainer.Add(_floatField);
|
||||
}
|
||||
|
||||
if (_stepper == null)
|
||||
{
|
||||
_stepper = new UTKFloatStepper(_minValue, _maxValue, _value, _step, IsReadOnly)
|
||||
{
|
||||
name = "stepper-field"
|
||||
};
|
||||
_stepper.AddToClassList("utk-property-item-view__stepper");
|
||||
_valueContainer.Add(_stepper);
|
||||
}
|
||||
}
|
||||
|
||||
// 초기 값 설정
|
||||
@@ -221,10 +262,28 @@ namespace UVC.UIToolkit
|
||||
_floatField.SetValueWithoutNotify(_value);
|
||||
_floatField.isReadOnly = IsReadOnly;
|
||||
}
|
||||
if (_stepper != null)
|
||||
{
|
||||
_stepper.MinValue = _minValue;
|
||||
_stepper.MaxValue = _maxValue;
|
||||
_stepper.Step = _step;
|
||||
_stepper.SetValue(_value, false);
|
||||
_stepper.IsReadOnly = IsReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSliderClass()
|
||||
private void UpdateModeClass()
|
||||
{
|
||||
// ReadOnly 상태에서는 무조건 FloatField만 표시
|
||||
if (IsReadOnly)
|
||||
{
|
||||
RemoveFromClassList("utk-property-item-view--slider");
|
||||
RemoveFromClassList("utk-property-item-view--stepper");
|
||||
UpdateControlVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
// 슬라이더 클래스
|
||||
if (_useSlider)
|
||||
{
|
||||
AddToClassList("utk-property-item-view--slider");
|
||||
@@ -233,6 +292,52 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
RemoveFromClassList("utk-property-item-view--slider");
|
||||
}
|
||||
|
||||
// 스테퍼 클래스
|
||||
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 showSlider = !isReadOnlyMode && _useSlider;
|
||||
bool showStepper = !isReadOnlyMode && _useStepper;
|
||||
bool showFloatField = !showStepper; // 스테퍼 모드가 아니면 항상 FloatField 표시
|
||||
|
||||
if (_slider != null)
|
||||
{
|
||||
_slider.style.display = showSlider ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
if (_stepper != null)
|
||||
{
|
||||
_stepper.style.display = showStepper ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
if (_floatField != null)
|
||||
{
|
||||
_floatField.style.display = showFloatField ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
|
||||
// 슬라이더 모드일 때 필드 너비 조정
|
||||
if (showSlider)
|
||||
{
|
||||
_floatField.AddToClassList("utk-property-item-view__field--with-slider");
|
||||
}
|
||||
else
|
||||
{
|
||||
_floatField.RemoveFromClassList("utk-property-item-view__field--with-slider");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -258,6 +363,14 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
_slider.IsEnabled = !isReadOnly;
|
||||
}
|
||||
|
||||
if (_stepper != null)
|
||||
{
|
||||
_stepper.IsReadOnly = isReadOnly;
|
||||
}
|
||||
|
||||
// ReadOnly 상태 변경 시 모드 클래스 업데이트
|
||||
UpdateModeClass();
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -273,6 +386,11 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
_slider.OnValueChanged += OnSliderChanged;
|
||||
}
|
||||
|
||||
if (_stepper != null)
|
||||
{
|
||||
_stepper.OnValueChanged += OnStepperChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterEvents()
|
||||
@@ -286,6 +404,11 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
_slider.OnValueChanged -= OnSliderChanged;
|
||||
}
|
||||
|
||||
if (_stepper != null)
|
||||
{
|
||||
_stepper.OnValueChanged -= OnStepperChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFloatFieldChanged(float newValue)
|
||||
@@ -322,6 +445,11 @@ namespace UVC.UIToolkit
|
||||
_floatField.SetValueWithoutNotify(newValue);
|
||||
}
|
||||
|
||||
if (_stepper != null && !Mathf.Approximately(_stepper.Value, newValue))
|
||||
{
|
||||
_stepper.SetValue(newValue, false);
|
||||
}
|
||||
|
||||
// 값 설정 (이벤트 발생)
|
||||
if (!Mathf.Approximately(_value, newValue))
|
||||
{
|
||||
@@ -334,6 +462,20 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStepperChanged(float newValue)
|
||||
{
|
||||
if (!Mathf.Approximately(_value, newValue))
|
||||
{
|
||||
_value = newValue;
|
||||
OnValueChanged?.Invoke(newValue);
|
||||
|
||||
if (_boundData != null && !Mathf.Approximately(_boundData.Value, newValue))
|
||||
{
|
||||
_boundData.Value = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Value Update
|
||||
@@ -348,6 +490,11 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
_slider.SetValueWithoutNotify(_value);
|
||||
}
|
||||
|
||||
if (_stepper != null && !Mathf.Approximately(_stepper.Value, _value))
|
||||
{
|
||||
_stepper.SetValue(_value, false);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -378,16 +525,28 @@ namespace UVC.UIToolkit
|
||||
IsVisible = data.IsVisible;
|
||||
TooltipText = data.Tooltip;
|
||||
|
||||
// 슬라이더 관련 속성 동기화
|
||||
// 슬라이더 및 스테퍼 관련 속성 동기화
|
||||
if (data is UTKFloatPropertyItem floatItem)
|
||||
{
|
||||
_minValue = floatItem.MinValue;
|
||||
_maxValue = floatItem.MaxValue;
|
||||
_step = floatItem.Step;
|
||||
|
||||
// 모드 변경 확인
|
||||
bool modeChanged = false;
|
||||
if (_useSlider != floatItem.UseSlider)
|
||||
{
|
||||
_useSlider = floatItem.UseSlider;
|
||||
UpdateSliderClass();
|
||||
modeChanged = true;
|
||||
}
|
||||
if (_useStepper != floatItem.UseStepper)
|
||||
{
|
||||
_useStepper = floatItem.UseStepper;
|
||||
modeChanged = true;
|
||||
}
|
||||
if (modeChanged)
|
||||
{
|
||||
UpdateModeClass();
|
||||
}
|
||||
|
||||
// 슬라이더 범위 업데이트
|
||||
@@ -396,6 +555,14 @@ namespace UVC.UIToolkit
|
||||
_slider.lowValue = _minValue;
|
||||
_slider.highValue = _maxValue;
|
||||
}
|
||||
|
||||
// 스테퍼 설정 업데이트
|
||||
if (_stepper != null)
|
||||
{
|
||||
_stepper.MinValue = _minValue;
|
||||
_stepper.MaxValue = _maxValue;
|
||||
_stepper.Step = _step;
|
||||
}
|
||||
}
|
||||
|
||||
// 데이터 변경 이벤트 구독
|
||||
@@ -438,6 +605,8 @@ namespace UVC.UIToolkit
|
||||
OnValueChanged = null;
|
||||
_floatField = null;
|
||||
_slider = null;
|
||||
_stepper?.Dispose();
|
||||
_stepper = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// Int 속성 View 클래스입니다.
|
||||
/// UTKIntegerField, UTKSliderInt, 또는 UTKNumberStepper를 사용하여 int 값을 표시/편집합니다.
|
||||
/// UTKIntegerField, UTKSliderInt, 또는 UTKIntStepper를 사용하여 int 값을 표시/편집합니다.
|
||||
///
|
||||
/// <para><b>사용법 (단독 사용):</b></para>
|
||||
/// <code>
|
||||
@@ -36,7 +36,7 @@ namespace UVC.UIToolkit
|
||||
#region Fields
|
||||
private UTKIntegerField? _intField;
|
||||
private UTKSliderInt? _slider;
|
||||
private UTKNumberStepper? _stepper;
|
||||
private UTKIntStepper? _stepper;
|
||||
|
||||
private int _value;
|
||||
private int _minValue;
|
||||
@@ -219,7 +219,7 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
_slider = this.Q<UTKSliderInt>("slider-field");
|
||||
_intField = this.Q<UTKIntegerField>("value-field");
|
||||
_stepper = this.Q<UTKNumberStepper>("stepper-field");
|
||||
_stepper = this.Q<UTKIntStepper>("stepper-field");
|
||||
|
||||
// Fallback: UXML에서 못 찾으면 생성
|
||||
if (_valueContainer != null)
|
||||
@@ -243,7 +243,7 @@ namespace UVC.UIToolkit
|
||||
|
||||
if (_stepper == null)
|
||||
{
|
||||
_stepper = new UTKNumberStepper(_minValue, _maxValue, _value, _step, IsReadOnly)
|
||||
_stepper = new UTKIntStepper(_minValue, _maxValue, _value, _step, IsReadOnly)
|
||||
{
|
||||
name = "stepper-field"
|
||||
};
|
||||
@@ -347,7 +347,7 @@ namespace UVC.UIToolkit
|
||||
bool isReadOnlyMode = IsReadOnly;
|
||||
bool showSlider = !isReadOnlyMode && _useSlider;
|
||||
bool showStepper = !isReadOnlyMode && _useStepper;
|
||||
bool showIntField = isReadOnlyMode || (!_useSlider && !_useStepper);
|
||||
bool showIntField = !showStepper; // 스테퍼 모드가 아니면 항상 IntField 표시
|
||||
|
||||
if (_slider != null)
|
||||
{
|
||||
@@ -362,6 +362,16 @@ namespace UVC.UIToolkit
|
||||
if (_intField != null)
|
||||
{
|
||||
_intField.style.display = showIntField ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
|
||||
// 슬라이더 모드일 때 필드 너비 조정
|
||||
if (showSlider)
|
||||
{
|
||||
_intField.AddToClassList("utk-property-item-view__field--with-slider");
|
||||
}
|
||||
else
|
||||
{
|
||||
_intField.RemoveFromClassList("utk-property-item-view__field--with-slider");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -436,6 +446,11 @@ namespace UVC.UIToolkit
|
||||
_intField.SetValueWithoutNotify(newValue);
|
||||
}
|
||||
|
||||
if (_stepper != null && _stepper.Value != newValue)
|
||||
{
|
||||
_stepper.SetValue(newValue, false);
|
||||
}
|
||||
|
||||
if (_value != newValue)
|
||||
{
|
||||
_value = newValue;
|
||||
|
||||
@@ -15,8 +15,8 @@ namespace UVC.UIToolkit
|
||||
#region Fields
|
||||
private UTKIntegerField? _minField;
|
||||
private UTKIntegerField? _maxField;
|
||||
private UTKNumberStepper? _minStepper;
|
||||
private UTKNumberStepper? _maxStepper;
|
||||
private UTKIntStepper? _minStepper;
|
||||
private UTKIntStepper? _maxStepper;
|
||||
|
||||
private UTKIntRange _value;
|
||||
private bool _useStepper;
|
||||
@@ -150,8 +150,8 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
_minField = this.Q<UTKIntegerField>("min-field");
|
||||
_maxField = this.Q<UTKIntegerField>("max-field");
|
||||
_minStepper = this.Q<UTKNumberStepper>("min-stepper");
|
||||
_maxStepper = this.Q<UTKNumberStepper>("max-stepper");
|
||||
_minStepper = this.Q<UTKIntStepper>("min-stepper");
|
||||
_maxStepper = this.Q<UTKIntStepper>("max-stepper");
|
||||
|
||||
// Fallback: UXML에서 못 찾으면 생성
|
||||
if (_valueContainer != null)
|
||||
@@ -182,7 +182,7 @@ namespace UVC.UIToolkit
|
||||
// NumberStepper 생성
|
||||
if (_minStepper == null)
|
||||
{
|
||||
_minStepper = new UTKNumberStepper(_stepperMinValue, _stepperMaxValue, _value.Min, _stepperStep, IsReadOnly)
|
||||
_minStepper = new UTKIntStepper(_stepperMinValue, _stepperMaxValue, _value.Min, _stepperStep, IsReadOnly)
|
||||
{
|
||||
name = "min-stepper"
|
||||
};
|
||||
@@ -193,7 +193,7 @@ namespace UVC.UIToolkit
|
||||
|
||||
if (_maxStepper == null)
|
||||
{
|
||||
_maxStepper = new UTKNumberStepper(_stepperMinValue, _stepperMaxValue, _value.Max, _stepperStep, IsReadOnly)
|
||||
_maxStepper = new UTKIntStepper(_stepperMinValue, _stepperMaxValue, _value.Max, _stepperStep, IsReadOnly)
|
||||
{
|
||||
name = "max-stepper"
|
||||
};
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// MultiSelectDropdown 속성 View 클래스입니다.
|
||||
/// UTKMultiSelectDropdown을 사용하여 문자열 목록에서 다중 선택합니다.
|
||||
/// ReadOnly 상태에서는 UTKInputField로 표시됩니다.
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKMultiSelectDropdownPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<List<string>>
|
||||
{
|
||||
#region Fields
|
||||
private UTKMultiSelectDropdown? _multiSelectDropdown;
|
||||
private UTKInputField? _readOnlyField;
|
||||
|
||||
private List<string> _value = new();
|
||||
private List<string> _choices = new();
|
||||
|
||||
private IUTKPropertyItem<List<string>>? _boundData;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
protected override string ViewTypeName => "UTKMultiSelectDropdownPropertyItemView";
|
||||
|
||||
/// <summary>현재 값 (선택된 항목 목록)</summary>
|
||||
public List<string> Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
var newValue = value ?? new List<string>();
|
||||
|
||||
// 리스트 내용 비교
|
||||
if (!AreListsEqual(_value, newValue))
|
||||
{
|
||||
_value = new List<string>(newValue);
|
||||
UpdateValueUI();
|
||||
OnValueChanged?.Invoke(_value);
|
||||
|
||||
if (_boundData != null && !AreListsEqual(_boundData.Value, _value))
|
||||
{
|
||||
_boundData.Value = new List<string>(_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>선택 가능한 항목 목록</summary>
|
||||
public List<string> Choices
|
||||
{
|
||||
get => _choices;
|
||||
set
|
||||
{
|
||||
_choices = value ?? new List<string>();
|
||||
if (_multiSelectDropdown != null)
|
||||
{
|
||||
_multiSelectDropdown.SetOptions(_choices);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public event Action<List<string>>? OnValueChanged;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public UTKMultiSelectDropdownPropertyItemView() : base()
|
||||
{
|
||||
InitializeUI();
|
||||
}
|
||||
|
||||
public UTKMultiSelectDropdownPropertyItemView(UTKMultiSelectDropdownPropertyItem data) : base()
|
||||
{
|
||||
_choices = data.Choices;
|
||||
_value = data.Value != null ? new List<string>(data.Value) : new List<string>();
|
||||
Label = data.Name;
|
||||
_isReadOnly = data.IsReadOnly;
|
||||
|
||||
InitializeUI();
|
||||
Bind(data);
|
||||
}
|
||||
|
||||
public UTKMultiSelectDropdownPropertyItemView(
|
||||
string label,
|
||||
List<string> choices,
|
||||
List<string>? initialValues = null) : base()
|
||||
{
|
||||
_choices = choices ?? new List<string>();
|
||||
_value = initialValues != null ? new List<string>(initialValues) : new List<string>();
|
||||
Label = label;
|
||||
InitializeUI();
|
||||
}
|
||||
|
||||
public UTKMultiSelectDropdownPropertyItemView(
|
||||
string label,
|
||||
IEnumerable<string> choices,
|
||||
IEnumerable<int>? selectedIndices = null) : base()
|
||||
{
|
||||
_choices = choices?.ToList() ?? new List<string>();
|
||||
|
||||
if (selectedIndices != null)
|
||||
{
|
||||
_value = selectedIndices
|
||||
.Where(i => i >= 0 && i < _choices.Count)
|
||||
.Select(i => _choices[i])
|
||||
.ToList();
|
||||
}
|
||||
|
||||
Label = label;
|
||||
InitializeUI();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
private void InitializeUI()
|
||||
{
|
||||
AddToClassList("utk-property-item-view");
|
||||
AddToClassList("utk-property-item-view--multiselect-dropdown");
|
||||
|
||||
if (!CreateUIFromUxml())
|
||||
{
|
||||
CreateUIFallback();
|
||||
}
|
||||
|
||||
// UXML에서 요소 가져오기
|
||||
QueryUIElements();
|
||||
|
||||
// 이벤트 등록
|
||||
RegisterEvents();
|
||||
|
||||
UpdateValueUI();
|
||||
UpdateReadOnlyState();
|
||||
}
|
||||
|
||||
private void QueryUIElements()
|
||||
{
|
||||
if (_valueContainer == null) return;
|
||||
|
||||
if (IsReadOnly)
|
||||
{
|
||||
// ReadOnly: UTKInputField 사용
|
||||
_readOnlyField = this.Q<UTKInputField>("readonly-field");
|
||||
if (_readOnlyField == null)
|
||||
{
|
||||
_readOnlyField = new UTKInputField { name = "readonly-field" };
|
||||
_readOnlyField.AddToClassList("utk-property-item-view__readonly-field");
|
||||
_valueContainer.Add(_readOnlyField);
|
||||
}
|
||||
_readOnlyField.Value = string.Join(", ", _value);
|
||||
_readOnlyField.isReadOnly = true;
|
||||
|
||||
// Dropdown 숨기기
|
||||
if (_multiSelectDropdown != null)
|
||||
{
|
||||
_multiSelectDropdown.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Editable: UTKMultiSelectDropdown 사용
|
||||
_multiSelectDropdown = this.Q<UTKMultiSelectDropdown>("multiselect-dropdown-field");
|
||||
if (_multiSelectDropdown == null)
|
||||
{
|
||||
_multiSelectDropdown = new UTKMultiSelectDropdown { name = "multiselect-dropdown-field" };
|
||||
_multiSelectDropdown.AddToClassList("utk-property-item-view__multiselect-dropdown");
|
||||
_valueContainer.Add(_multiSelectDropdown);
|
||||
}
|
||||
_multiSelectDropdown.SetOptions(_choices);
|
||||
_multiSelectDropdown.SetSelectedValues(_value, notify: false);
|
||||
_multiSelectDropdown.IsEnabled = true;
|
||||
_multiSelectDropdown.style.display = DisplayStyle.Flex;
|
||||
|
||||
// ReadOnly 필드 숨기기
|
||||
if (_readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterEvents()
|
||||
{
|
||||
if (_multiSelectDropdown != null)
|
||||
{
|
||||
_multiSelectDropdown.OnSelectionChanged += OnMultiSelectDropdownChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterEvents()
|
||||
{
|
||||
if (_multiSelectDropdown != null)
|
||||
{
|
||||
_multiSelectDropdown.OnSelectionChanged -= OnMultiSelectDropdownChanged;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Override Methods
|
||||
protected override void CreateValueUI(VisualElement container)
|
||||
{
|
||||
// Fallback: UXML 로드 실패 시 코드로 생성
|
||||
_multiSelectDropdown = new UTKMultiSelectDropdown { name = "multiselect-dropdown-field" };
|
||||
_multiSelectDropdown.AddToClassList("utk-property-item-view__multiselect-dropdown");
|
||||
container.Add(_multiSelectDropdown);
|
||||
}
|
||||
|
||||
public override void RefreshUI()
|
||||
{
|
||||
UpdateValueUI();
|
||||
}
|
||||
|
||||
protected override void OnReadOnlyStateChanged(bool isReadOnly)
|
||||
{
|
||||
if (_valueContainer == null) return;
|
||||
|
||||
if (isReadOnly)
|
||||
{
|
||||
// Dropdown → InputField 전환
|
||||
if (_readOnlyField == null)
|
||||
{
|
||||
_readOnlyField = new UTKInputField { name = "readonly-field" };
|
||||
_readOnlyField.AddToClassList("utk-property-item-view__readonly-field");
|
||||
_valueContainer.Add(_readOnlyField);
|
||||
}
|
||||
_readOnlyField.Value = string.Join(", ", _value);
|
||||
_readOnlyField.isReadOnly = true;
|
||||
_readOnlyField.style.display = DisplayStyle.Flex;
|
||||
|
||||
if (_multiSelectDropdown != null)
|
||||
{
|
||||
_multiSelectDropdown.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// InputField → Dropdown 전환
|
||||
if (_multiSelectDropdown == null)
|
||||
{
|
||||
_multiSelectDropdown = new UTKMultiSelectDropdown { name = "multiselect-dropdown-field" };
|
||||
_multiSelectDropdown.AddToClassList("utk-property-item-view__multiselect-dropdown");
|
||||
_valueContainer.Add(_multiSelectDropdown);
|
||||
_multiSelectDropdown.SetOptions(_choices);
|
||||
RegisterEvents();
|
||||
}
|
||||
_multiSelectDropdown.SetSelectedValues(_value, notify: false);
|
||||
_multiSelectDropdown.IsEnabled = true;
|
||||
_multiSelectDropdown.style.display = DisplayStyle.Flex;
|
||||
|
||||
if (_readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handling
|
||||
private void OnMultiSelectDropdownChanged(List<int> indices, List<string> values)
|
||||
{
|
||||
if (!AreListsEqual(_value, values))
|
||||
{
|
||||
_value = new List<string>(values);
|
||||
OnValueChanged?.Invoke(_value);
|
||||
|
||||
if (_boundData != null && !AreListsEqual(_boundData.Value, _value))
|
||||
{
|
||||
_boundData.Value = new List<string>(_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Value Update
|
||||
private void UpdateValueUI()
|
||||
{
|
||||
if (IsReadOnly && _readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.Value = string.Join(", ", _value);
|
||||
}
|
||||
else if (_multiSelectDropdown != null && !AreListsEqual(_multiSelectDropdown.SelectedValues, _value))
|
||||
{
|
||||
_multiSelectDropdown.SetSelectedValues(_value, notify: false);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
/// <summary>두 리스트의 내용이 같은지 비교 (순서 무관)</summary>
|
||||
private bool AreListsEqual(List<string>? list1, List<string>? list2)
|
||||
{
|
||||
if (list1 == null && list2 == null) return true;
|
||||
if (list1 == null || list2 == null) return false;
|
||||
if (list1.Count != list2.Count) return false;
|
||||
|
||||
var set1 = new HashSet<string>(list1);
|
||||
var set2 = new HashSet<string>(list2);
|
||||
return set1.SetEquals(set2);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>선택 항목 추가</summary>
|
||||
public void AddChoice(string choice)
|
||||
{
|
||||
if (!_choices.Contains(choice))
|
||||
{
|
||||
_choices.Add(choice);
|
||||
if (_multiSelectDropdown != null)
|
||||
{
|
||||
_multiSelectDropdown.SetOptions(_choices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>선택 항목 제거</summary>
|
||||
public bool RemoveChoice(string choice)
|
||||
{
|
||||
bool removed = _choices.Remove(choice);
|
||||
if (removed && _multiSelectDropdown != null)
|
||||
{
|
||||
_multiSelectDropdown.SetOptions(_choices);
|
||||
|
||||
if (_value.Contains(choice))
|
||||
{
|
||||
var newValue = _value.Where(v => v != choice).ToList();
|
||||
Value = newValue;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>인덱스로 선택 설정</summary>
|
||||
public void SetSelectedIndices(List<int> indices, bool notify = true)
|
||||
{
|
||||
if (_multiSelectDropdown != null)
|
||||
{
|
||||
_multiSelectDropdown.SetSelectedIndices(indices, notify);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>값으로 선택 설정</summary>
|
||||
public void SetSelectedValues(List<string> values, bool notify = true)
|
||||
{
|
||||
if (_multiSelectDropdown != null)
|
||||
{
|
||||
_multiSelectDropdown.SetSelectedValues(values, notify);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>모든 항목 선택</summary>
|
||||
public void SelectAll()
|
||||
{
|
||||
_multiSelectDropdown?.SelectAll();
|
||||
}
|
||||
|
||||
/// <summary>모든 선택 해제</summary>
|
||||
public void ClearSelection()
|
||||
{
|
||||
_multiSelectDropdown?.ClearSelection();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Data Binding
|
||||
public void Bind(IUTKPropertyItem data)
|
||||
{
|
||||
if (data is IUTKPropertyItem<List<string>> listData)
|
||||
{
|
||||
Bind(listData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[UTKMultiSelectDropdownPropertyItemView] Cannot bind to non-List<string> data: {data.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Bind(IUTKPropertyItem<List<string>> data)
|
||||
{
|
||||
Unbind();
|
||||
|
||||
_boundData = data;
|
||||
|
||||
Label = data.Name;
|
||||
_value = data.Value != null ? new List<string>(data.Value) : new List<string>();
|
||||
IsReadOnly = data.IsReadOnly;
|
||||
IsVisible = data.IsVisible;
|
||||
TooltipText = data.Tooltip;
|
||||
|
||||
if (data is UTKMultiSelectDropdownPropertyItem multiSelectItem)
|
||||
{
|
||||
_choices = multiSelectItem.Choices;
|
||||
if (_multiSelectDropdown != null)
|
||||
{
|
||||
_multiSelectDropdown.SetOptions(_choices);
|
||||
}
|
||||
}
|
||||
|
||||
data.OnTypedValueChanged += OnDataValueChanged;
|
||||
|
||||
UpdateValueUI();
|
||||
UpdateReadOnlyState();
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
if (_boundData != null)
|
||||
{
|
||||
_boundData.OnTypedValueChanged -= OnDataValueChanged;
|
||||
_boundData = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDataValueChanged(IUTKPropertyItem<List<string>> item, List<string> oldValue, List<string> newValue)
|
||||
{
|
||||
if (!AreListsEqual(_value, newValue))
|
||||
{
|
||||
_value = newValue != null ? new List<string>(newValue) : new List<string>();
|
||||
UpdateValueUI();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Dispose
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
UnregisterEvents();
|
||||
Unbind();
|
||||
|
||||
OnValueChanged = null;
|
||||
_multiSelectDropdown = null;
|
||||
_readOnlyField = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bcad826251c165b429f843262a7835bc
|
||||
@@ -35,6 +35,7 @@ namespace UVC.UIToolkit
|
||||
|
||||
protected const string USS_CLASS_READONLY = "utk-property-item-view--readonly";
|
||||
protected const string USS_CLASS_HIDDEN = "utk-property-item-view--hidden";
|
||||
protected const string USS_CLASS_NO_LABEL = "utk-property-item-view--no-label";
|
||||
#endregion
|
||||
|
||||
#region Static Cache
|
||||
@@ -51,6 +52,7 @@ namespace UVC.UIToolkit
|
||||
protected string _label = string.Empty;
|
||||
protected bool _isReadOnly = false;
|
||||
protected bool _isVisible = true;
|
||||
protected bool _showLabel = true;
|
||||
protected string? _tooltipText;
|
||||
#endregion
|
||||
|
||||
@@ -111,6 +113,21 @@ namespace UVC.UIToolkit
|
||||
UpdateTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>라벨 표시 여부 (false면 value가 전체 너비 사용)</summary>
|
||||
[UxmlAttribute("show-label")]
|
||||
public bool ShowLabel
|
||||
{
|
||||
get => _showLabel;
|
||||
set
|
||||
{
|
||||
if (_showLabel != value)
|
||||
{
|
||||
_showLabel = value;
|
||||
UpdateShowLabelState();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -241,6 +258,27 @@ namespace UVC.UIToolkit
|
||||
this.ClearTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>라벨 표시 상태 업데이트</summary>
|
||||
protected virtual void UpdateShowLabelState()
|
||||
{
|
||||
if (_showLabel)
|
||||
{
|
||||
RemoveFromClassList(USS_CLASS_NO_LABEL);
|
||||
if (_labelElement != null)
|
||||
{
|
||||
_labelElement.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddToClassList(USS_CLASS_NO_LABEL);
|
||||
if (_labelElement != null)
|
||||
{
|
||||
_labelElement.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Theme
|
||||
@@ -339,6 +377,21 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
/// <summary>
|
||||
/// PropertyItem의 공통 속성을 View에 적용합니다.
|
||||
/// 하위 클래스의 Bind 메서드에서 호출하세요.
|
||||
/// </summary>
|
||||
protected void ApplyCommonProperties(IUTKPropertyItem item)
|
||||
{
|
||||
Label = item.Name;
|
||||
IsReadOnly = item.IsReadOnly;
|
||||
IsVisible = item.IsVisible;
|
||||
TooltipText = item.Tooltip;
|
||||
ShowLabel = item.ShowLabel;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -76,12 +76,14 @@ namespace UVC.UIToolkit
|
||||
UTKPropertyType.DateTime => new UTKDateTimePropertyItemView(),
|
||||
UTKPropertyType.Enum => CreateEnumView(data),
|
||||
UTKPropertyType.DropdownList => CreateDropdownView(data),
|
||||
UTKPropertyType.MultiSelectDropdownList => CreateMultiSelectDropdownView(data),
|
||||
UTKPropertyType.RadioGroup => CreateRadioView(data),
|
||||
UTKPropertyType.IntRange => new UTKIntRangePropertyItemView(),
|
||||
UTKPropertyType.FloatRange => new UTKFloatRangePropertyItemView(),
|
||||
UTKPropertyType.DateRange => new UTKDateRangePropertyItemView(),
|
||||
UTKPropertyType.DateTimeRange => new UTKDateTimeRangePropertyItemView(),
|
||||
UTKPropertyType.ColorState => new UTKColorStatePropertyItemView(),
|
||||
UTKPropertyType.Button => CreateButtonView(data),
|
||||
_ => throw new ArgumentException($"Unknown property type: {data.PropertyType}")
|
||||
};
|
||||
}
|
||||
@@ -112,12 +114,14 @@ namespace UVC.UIToolkit
|
||||
UTKPropertyType.DateTime => new UTKDateTimePropertyItemView(),
|
||||
UTKPropertyType.Enum => new UTKEnumPropertyItemView(),
|
||||
UTKPropertyType.DropdownList => new UTKDropdownPropertyItemView(),
|
||||
UTKPropertyType.MultiSelectDropdownList => new UTKMultiSelectDropdownPropertyItemView(),
|
||||
UTKPropertyType.RadioGroup => new UTKRadioPropertyItemView(),
|
||||
UTKPropertyType.IntRange => new UTKIntRangePropertyItemView(),
|
||||
UTKPropertyType.FloatRange => new UTKFloatRangePropertyItemView(),
|
||||
UTKPropertyType.DateRange => new UTKDateRangePropertyItemView(),
|
||||
UTKPropertyType.DateTimeRange => new UTKDateTimeRangePropertyItemView(),
|
||||
UTKPropertyType.ColorState => new UTKColorStatePropertyItemView(),
|
||||
UTKPropertyType.Button => new UTKButtonItemView(),
|
||||
_ => throw new ArgumentException($"Unknown property type: {propertyType}")
|
||||
};
|
||||
}
|
||||
@@ -210,6 +214,26 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
return new UTKRadioPropertyItemView();
|
||||
}
|
||||
|
||||
private static IUTKPropertyItemView CreateMultiSelectDropdownView(IUTKPropertyItem data)
|
||||
{
|
||||
// UTKMultiSelectDropdownPropertyItem의 Choices로 MultiSelectDropdown 초기화
|
||||
if (data is UTKMultiSelectDropdownPropertyItem multiSelectItem)
|
||||
{
|
||||
return new UTKMultiSelectDropdownPropertyItemView("", multiSelectItem.Choices);
|
||||
}
|
||||
return new UTKMultiSelectDropdownPropertyItemView();
|
||||
}
|
||||
|
||||
private static IUTKPropertyItemView CreateButtonView(IUTKPropertyItem data)
|
||||
{
|
||||
// UTKButtonItem으로 ButtonView 초기화
|
||||
if (data is UTKButtonItem buttonItem)
|
||||
{
|
||||
return new UTKButtonItemView(buttonItem);
|
||||
}
|
||||
return new UTKButtonItemView();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace UVC.UIToolkit
|
||||
/// <summary>
|
||||
/// Radio 속성 View 클래스입니다.
|
||||
/// UTKRadioButton 그룹을 사용하여 단일 선택을 제공합니다.
|
||||
/// ReadOnly 상태에서는 UTKInputField로 표시됩니다.
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKRadioPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<int>
|
||||
@@ -17,6 +18,7 @@ namespace UVC.UIToolkit
|
||||
#region Fields
|
||||
private VisualElement? _radioContainer;
|
||||
private List<UTKRadioButton> _radioButtons = new();
|
||||
private UTKInputField? _readOnlyField;
|
||||
|
||||
private int _value;
|
||||
private List<string> _choices = new();
|
||||
@@ -129,14 +131,45 @@ namespace UVC.UIToolkit
|
||||
|
||||
private void QueryUIElements()
|
||||
{
|
||||
_radioContainer = this.Q<VisualElement>("radio-container");
|
||||
if (_valueContainer == null) return;
|
||||
|
||||
// Fallback: UXML에서 못 찾으면 생성
|
||||
if (_valueContainer != null && _radioContainer == null)
|
||||
if (IsReadOnly)
|
||||
{
|
||||
_radioContainer = new VisualElement { name = "radio-container" };
|
||||
_radioContainer.AddToClassList("utk-property-item-view__radio-container");
|
||||
_valueContainer.Add(_radioContainer);
|
||||
// ReadOnly: UTKInputField 사용
|
||||
_readOnlyField = this.Q<UTKInputField>("readonly-field");
|
||||
if (_readOnlyField == null)
|
||||
{
|
||||
_readOnlyField = new UTKInputField { name = "readonly-field" };
|
||||
_readOnlyField.AddToClassList("utk-property-item-view__readonly-field");
|
||||
_valueContainer.Add(_readOnlyField);
|
||||
}
|
||||
_readOnlyField.Value = SelectedText ?? "";
|
||||
_readOnlyField.isReadOnly = true;
|
||||
|
||||
// Radio 컨테이너 숨기기
|
||||
_radioContainer = this.Q<VisualElement>("radio-container");
|
||||
if (_radioContainer != null)
|
||||
{
|
||||
_radioContainer.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Editable: Radio 버튼 사용
|
||||
_radioContainer = this.Q<VisualElement>("radio-container");
|
||||
if (_radioContainer == null)
|
||||
{
|
||||
_radioContainer = new VisualElement { name = "radio-container" };
|
||||
_radioContainer.AddToClassList("utk-property-item-view__radio-container");
|
||||
_valueContainer.Add(_radioContainer);
|
||||
}
|
||||
_radioContainer.style.display = DisplayStyle.Flex;
|
||||
|
||||
// ReadOnly 필드 숨기기
|
||||
if (_readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -154,9 +187,48 @@ namespace UVC.UIToolkit
|
||||
|
||||
protected override void OnReadOnlyStateChanged(bool isReadOnly)
|
||||
{
|
||||
foreach (var radio in _radioButtons)
|
||||
if (_valueContainer == null) return;
|
||||
|
||||
if (isReadOnly)
|
||||
{
|
||||
radio.IsEnabled = !isReadOnly;
|
||||
// Radio → InputField 전환
|
||||
if (_readOnlyField == null)
|
||||
{
|
||||
_readOnlyField = new UTKInputField { name = "readonly-field" };
|
||||
_readOnlyField.AddToClassList("utk-property-item-view__readonly-field");
|
||||
_valueContainer.Add(_readOnlyField);
|
||||
}
|
||||
_readOnlyField.Value = SelectedText ?? "";
|
||||
_readOnlyField.isReadOnly = true;
|
||||
_readOnlyField.style.display = DisplayStyle.Flex;
|
||||
|
||||
if (_radioContainer != null)
|
||||
{
|
||||
_radioContainer.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// InputField → Radio 전환
|
||||
if (_radioContainer == null)
|
||||
{
|
||||
_radioContainer = new VisualElement { name = "radio-container" };
|
||||
_radioContainer.AddToClassList("utk-property-item-view__radio-container");
|
||||
_valueContainer.Add(_radioContainer);
|
||||
CreateRadioButtons();
|
||||
}
|
||||
_radioContainer.style.display = DisplayStyle.Flex;
|
||||
|
||||
// 라디오 버튼 활성화
|
||||
foreach (var radio in _radioButtons)
|
||||
{
|
||||
radio.IsEnabled = true;
|
||||
}
|
||||
|
||||
if (_readOnlyField != null)
|
||||
{
|
||||
_readOnlyField.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -223,9 +295,16 @@ namespace UVC.UIToolkit
|
||||
#region Value Update
|
||||
private void UpdateSelection()
|
||||
{
|
||||
for (int i = 0; i < _radioButtons.Count; i++)
|
||||
if (IsReadOnly && _readOnlyField != null)
|
||||
{
|
||||
_radioButtons[i].SetChecked(i == _value, false);
|
||||
_readOnlyField.Value = SelectedText ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < _radioButtons.Count; i++)
|
||||
{
|
||||
_radioButtons[i].SetChecked(i == _value, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -310,6 +389,7 @@ namespace UVC.UIToolkit
|
||||
|
||||
OnValueChanged = null;
|
||||
_radioContainer = null;
|
||||
_readOnlyField = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
#region Fields
|
||||
private UTKInputField? _inputField;
|
||||
private UTKButton? _actionButton;
|
||||
private Action? _actionButtonClickHandler; // 이벤트 핸들러 참조 보관
|
||||
|
||||
private string _value = string.Empty;
|
||||
private bool _isMultiline = false;
|
||||
@@ -93,6 +95,9 @@ namespace UVC.UIToolkit
|
||||
|
||||
#region Events
|
||||
public event Action<string>? OnValueChanged;
|
||||
|
||||
/// <summary>액션 버튼 클릭 이벤트 (actionName 전달)</summary>
|
||||
public event Action<string>? OnActionButtonClicked;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -232,6 +237,67 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Action Button
|
||||
private void CreateActionButton(UTKButtonItem buttonItem)
|
||||
{
|
||||
// 기존 버튼 제거
|
||||
RemoveActionButton();
|
||||
|
||||
// 새 버튼 생성
|
||||
_actionButton = new UTKButton
|
||||
{
|
||||
name = buttonItem.Name,
|
||||
Text = buttonItem.Text,
|
||||
Icon = buttonItem.Icon,
|
||||
IconSize = buttonItem.IconSize,
|
||||
Variant = buttonItem.Variant,
|
||||
Size = buttonItem.Size,
|
||||
IconOnly = buttonItem.IconOnly
|
||||
};
|
||||
if(buttonItem.IconOnly == false){
|
||||
_actionButton.AddToClassList("utk-property-item-view__action-button");
|
||||
}
|
||||
else
|
||||
{
|
||||
_actionButton.AddToClassList("utk-property-item-view__action-button-icon");
|
||||
}
|
||||
|
||||
if (buttonItem.BackgroundColor.HasValue)
|
||||
{
|
||||
_actionButton.BackgroundColor = buttonItem.BackgroundColor;
|
||||
}
|
||||
|
||||
if (buttonItem.BorderWidth >= 0)
|
||||
{
|
||||
_actionButton.BorderWidth = buttonItem.BorderWidth;
|
||||
}
|
||||
|
||||
// 버튼 클릭 이벤트 등록 (핸들러 참조 보관)
|
||||
_actionButtonClickHandler = () => OnActionButtonClicked?.Invoke(buttonItem.ActionName);
|
||||
_actionButton.OnClicked += _actionButtonClickHandler;
|
||||
|
||||
// value-container에 추가 (InputField 다음)
|
||||
_valueContainer?.Add(_actionButton);
|
||||
}
|
||||
|
||||
private void RemoveActionButton()
|
||||
{
|
||||
if (_actionButton != null)
|
||||
{
|
||||
// 보관된 핸들러로 이벤트 해제
|
||||
if (_actionButtonClickHandler != null)
|
||||
{
|
||||
_actionButton.OnClicked -= _actionButtonClickHandler;
|
||||
_actionButtonClickHandler = null;
|
||||
}
|
||||
|
||||
_actionButton.RemoveFromHierarchy();
|
||||
_actionButton.Dispose();
|
||||
_actionButton = null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Data Binding
|
||||
public void Bind(IUTKPropertyItem data)
|
||||
{
|
||||
@@ -256,7 +322,7 @@ namespace UVC.UIToolkit
|
||||
IsReadOnly = data.IsReadOnly;
|
||||
IsVisible = data.IsVisible;
|
||||
TooltipText = data.Tooltip;
|
||||
|
||||
|
||||
if (data is UTKStringPropertyItem stringItem)
|
||||
{
|
||||
_isMultiline = stringItem.IsMultiline;
|
||||
@@ -266,6 +332,16 @@ namespace UVC.UIToolkit
|
||||
_inputField.multiline = _isMultiline;
|
||||
_inputField.maxLength = _maxLength;
|
||||
}
|
||||
|
||||
// ActionButton이 있으면 버튼 생성
|
||||
if (stringItem.ActionButton != null && _valueContainer != null)
|
||||
{
|
||||
CreateActionButton(stringItem.ActionButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveActionButton();
|
||||
}
|
||||
}
|
||||
|
||||
data.OnTypedValueChanged += OnDataValueChanged;
|
||||
@@ -281,6 +357,9 @@ namespace UVC.UIToolkit
|
||||
_boundData.OnTypedValueChanged -= OnDataValueChanged;
|
||||
_boundData = null;
|
||||
}
|
||||
|
||||
// ActionButton 제거
|
||||
RemoveActionButton();
|
||||
}
|
||||
|
||||
private void OnDataValueChanged(IUTKPropertyItem<string> item, string oldValue, string newValue)
|
||||
@@ -303,7 +382,14 @@ namespace UVC.UIToolkit
|
||||
UnregisterEvents();
|
||||
Unbind();
|
||||
|
||||
// UTKInputField Dispose 호출
|
||||
if (_inputField is IDisposable disposableField)
|
||||
{
|
||||
disposableField.Dispose();
|
||||
}
|
||||
|
||||
OnValueChanged = null;
|
||||
OnActionButtonClicked = null;
|
||||
_inputField = null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user