2026-02-04 20:31:52 +09:00
|
|
|
#nullable enable
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.UIElements;
|
|
|
|
|
|
|
|
|
|
namespace UVC.UIToolkit
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Radio 속성 View 클래스입니다.
|
|
|
|
|
/// UTKRadioButton 그룹을 사용하여 단일 선택을 제공합니다.
|
2026-02-09 20:28:09 +09:00
|
|
|
/// ReadOnly 상태에서는 UTKInputField로 표시됩니다.
|
2026-02-04 20:31:52 +09:00
|
|
|
/// </summary>
|
|
|
|
|
[UxmlElement]
|
|
|
|
|
public partial class UTKRadioPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<int>
|
|
|
|
|
{
|
|
|
|
|
#region Fields
|
|
|
|
|
private VisualElement? _radioContainer;
|
|
|
|
|
private List<UTKRadioButton> _radioButtons = new();
|
2026-02-09 20:28:09 +09:00
|
|
|
private UTKInputField? _readOnlyField;
|
2026-02-04 20:31:52 +09:00
|
|
|
|
|
|
|
|
private int _value;
|
|
|
|
|
private List<string> _choices = new();
|
|
|
|
|
|
|
|
|
|
private IUTKPropertyItem<int>? _boundData;
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Properties
|
|
|
|
|
protected override string ViewTypeName => "UTKRadioPropertyItemView";
|
|
|
|
|
|
|
|
|
|
/// <summary>현재 선택된 인덱스</summary>
|
|
|
|
|
public int Value
|
|
|
|
|
{
|
|
|
|
|
get => _value;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (_value != value && value >= 0 && value < _choices.Count)
|
|
|
|
|
{
|
|
|
|
|
_value = value;
|
|
|
|
|
UpdateSelection();
|
|
|
|
|
OnValueChanged?.Invoke(value);
|
|
|
|
|
|
|
|
|
|
if (_boundData != null && _boundData.Value != value)
|
|
|
|
|
{
|
|
|
|
|
_boundData.Value = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>선택 가능한 항목 목록</summary>
|
|
|
|
|
public List<string> Choices
|
|
|
|
|
{
|
|
|
|
|
get => _choices;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
_choices = value ?? new List<string>();
|
|
|
|
|
RebuildRadioButtons();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>현재 선택된 항목의 텍스트</summary>
|
|
|
|
|
public string? SelectedText
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_value >= 0 && _value < _choices.Count)
|
|
|
|
|
{
|
|
|
|
|
return _choices[_value];
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Events
|
|
|
|
|
public event Action<int>? OnValueChanged;
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Constructor
|
|
|
|
|
public UTKRadioPropertyItemView() : base()
|
|
|
|
|
{
|
|
|
|
|
InitializeUI();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public UTKRadioPropertyItemView(UTKRadioPropertyItem data) : this()
|
|
|
|
|
{
|
|
|
|
|
_choices = data.Choices;
|
|
|
|
|
_value = data.Value;
|
|
|
|
|
Label = data.Name;
|
|
|
|
|
_isReadOnly = data.IsReadOnly;
|
|
|
|
|
|
|
|
|
|
InitializeUI();
|
|
|
|
|
Bind(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public UTKRadioPropertyItemView(string label, List<string> choices, int selectedIndex = 0) : base()
|
|
|
|
|
{
|
|
|
|
|
_choices = choices ?? new List<string>();
|
|
|
|
|
_value = Math.Max(0, Math.Min(selectedIndex, _choices.Count - 1));
|
|
|
|
|
Label = label;
|
|
|
|
|
InitializeUI();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public UTKRadioPropertyItemView(string label, IEnumerable<string> choices, int selectedIndex = 0) : this(label, choices?.ToList() ?? new List<string>(), selectedIndex)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Initialization
|
|
|
|
|
private void InitializeUI()
|
|
|
|
|
{
|
|
|
|
|
AddToClassList("utk-property-item-view");
|
|
|
|
|
AddToClassList("utk-property-item-view--radio");
|
|
|
|
|
|
|
|
|
|
if (!CreateUIFromUxml())
|
|
|
|
|
{
|
|
|
|
|
CreateUIFallback();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UXML에서 요소 가져오기
|
|
|
|
|
QueryUIElements();
|
|
|
|
|
|
|
|
|
|
// 라디오 버튼 생성
|
|
|
|
|
CreateRadioButtons();
|
|
|
|
|
|
|
|
|
|
UpdateSelection();
|
|
|
|
|
UpdateReadOnlyState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void QueryUIElements()
|
|
|
|
|
{
|
2026-02-09 20:28:09 +09:00
|
|
|
if (_valueContainer == null) return;
|
2026-02-04 20:31:52 +09:00
|
|
|
|
2026-02-09 20:28:09 +09:00
|
|
|
if (IsReadOnly)
|
2026-02-04 20:31:52 +09:00
|
|
|
{
|
2026-02-09 20:28:09 +09:00
|
|
|
// 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;
|
|
|
|
|
}
|
2026-02-04 20:31:52 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Override Methods
|
|
|
|
|
protected override void CreateValueUI(VisualElement container)
|
|
|
|
|
{
|
|
|
|
|
// UXML/QueryUIElements 기반으로 생성하므로 여기서는 생성하지 않음
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void RefreshUI()
|
|
|
|
|
{
|
|
|
|
|
UpdateSelection();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void OnReadOnlyStateChanged(bool isReadOnly)
|
|
|
|
|
{
|
2026-02-09 20:28:09 +09:00
|
|
|
if (_valueContainer == null) return;
|
|
|
|
|
|
|
|
|
|
if (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
|
2026-02-04 20:31:52 +09:00
|
|
|
{
|
2026-02-09 20:28:09 +09:00
|
|
|
// 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;
|
|
|
|
|
}
|
2026-02-04 20:31:52 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region UI Creation
|
|
|
|
|
private void CreateRadioButtons()
|
|
|
|
|
{
|
|
|
|
|
if (_radioContainer == null) return;
|
|
|
|
|
|
|
|
|
|
_radioContainer.Clear();
|
|
|
|
|
_radioButtons.Clear();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _choices.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
var radio = new UTKRadioButton(_choices[i]);
|
|
|
|
|
radio.name = $"radio-{i}";
|
|
|
|
|
radio.AddToClassList("utk-property-item-view__radio");
|
|
|
|
|
|
|
|
|
|
int index = i;
|
|
|
|
|
radio.OnValueChanged += (isChecked) => OnRadioChanged(index, isChecked);
|
|
|
|
|
radio.IsEnabled = !IsReadOnly;
|
|
|
|
|
|
|
|
|
|
_radioButtons.Add(radio);
|
|
|
|
|
_radioContainer.Add(radio);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UpdateSelection();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RebuildRadioButtons()
|
|
|
|
|
{
|
|
|
|
|
if (_radioContainer != null)
|
|
|
|
|
{
|
|
|
|
|
CreateRadioButtons();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Event Handling
|
|
|
|
|
private void OnRadioChanged(int index, bool isChecked)
|
|
|
|
|
{
|
|
|
|
|
if (isChecked && index != _value)
|
|
|
|
|
{
|
|
|
|
|
// 다른 라디오 버튼 해제
|
|
|
|
|
for (int i = 0; i < _radioButtons.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i != index)
|
|
|
|
|
{
|
|
|
|
|
_radioButtons[i].SetChecked(false, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_value = index;
|
|
|
|
|
OnValueChanged?.Invoke(index);
|
|
|
|
|
|
|
|
|
|
if (_boundData != null && _boundData.Value != index)
|
|
|
|
|
{
|
|
|
|
|
_boundData.Value = index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Value Update
|
|
|
|
|
private void UpdateSelection()
|
|
|
|
|
{
|
2026-02-09 20:28:09 +09:00
|
|
|
if (IsReadOnly && _readOnlyField != null)
|
2026-02-04 20:31:52 +09:00
|
|
|
{
|
2026-02-09 20:28:09 +09:00
|
|
|
_readOnlyField.Value = SelectedText ?? "";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < _radioButtons.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
_radioButtons[i].SetChecked(i == _value, false);
|
|
|
|
|
}
|
2026-02-04 20:31:52 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Public Methods
|
|
|
|
|
/// <summary>텍스트로 선택</summary>
|
|
|
|
|
public void SelectByText(string text)
|
|
|
|
|
{
|
|
|
|
|
int index = _choices.IndexOf(text);
|
|
|
|
|
if (index >= 0)
|
|
|
|
|
{
|
|
|
|
|
Value = index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Data Binding
|
|
|
|
|
public void Bind(IUTKPropertyItem data)
|
|
|
|
|
{
|
|
|
|
|
if (data is IUTKPropertyItem<int> intData)
|
|
|
|
|
{
|
|
|
|
|
Bind(intData);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"[UTKRadioPropertyItemView] Cannot bind to non-int data: {data.GetType().Name}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Bind(IUTKPropertyItem<int> data)
|
|
|
|
|
{
|
|
|
|
|
Unbind();
|
|
|
|
|
|
|
|
|
|
_boundData = data;
|
2026-02-10 20:48:49 +09:00
|
|
|
BindBase(data);
|
2026-02-04 20:31:52 +09:00
|
|
|
|
|
|
|
|
Label = data.Name;
|
|
|
|
|
_value = data.Value;
|
|
|
|
|
IsVisible = data.IsVisible;
|
|
|
|
|
TooltipText = data.Tooltip;
|
2026-02-10 20:48:49 +09:00
|
|
|
ShowLabel = data.ShowLabel;
|
2026-02-04 20:31:52 +09:00
|
|
|
|
|
|
|
|
if (data is UTKRadioPropertyItem radioItem)
|
|
|
|
|
{
|
|
|
|
|
_choices = radioItem.Choices;
|
|
|
|
|
RebuildRadioButtons();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data.OnTypedValueChanged += OnDataValueChanged;
|
|
|
|
|
|
|
|
|
|
UpdateSelection();
|
2026-02-10 20:48:49 +09:00
|
|
|
IsReadOnly = data.IsReadOnly;
|
2026-02-04 20:31:52 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Unbind()
|
|
|
|
|
{
|
|
|
|
|
if (_boundData != null)
|
|
|
|
|
{
|
|
|
|
|
_boundData.OnTypedValueChanged -= OnDataValueChanged;
|
2026-02-10 20:48:49 +09:00
|
|
|
UnbindBase();
|
2026-02-04 20:31:52 +09:00
|
|
|
_boundData = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnDataValueChanged(IUTKPropertyItem<int> item, int oldValue, int newValue)
|
|
|
|
|
{
|
|
|
|
|
if (_value != newValue)
|
|
|
|
|
{
|
|
|
|
|
_value = newValue;
|
|
|
|
|
UpdateSelection();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Dispose
|
|
|
|
|
protected override void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (_disposed) return;
|
|
|
|
|
|
|
|
|
|
if (disposing)
|
|
|
|
|
{
|
|
|
|
|
_radioButtons.Clear();
|
|
|
|
|
Unbind();
|
|
|
|
|
|
|
|
|
|
OnValueChanged = null;
|
|
|
|
|
_radioContainer = null;
|
2026-02-09 20:28:09 +09:00
|
|
|
_readOnlyField = null;
|
2026-02-04 20:31:52 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
base.Dispose(disposing);
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|