Files
XRLib/Assets/Scripts/UVC/UIToolkit/Property/Views/UTKRadioPropertyItemView.cs
2026-02-04 20:31:52 +09:00

320 lines
8.6 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// Radio 속성 View 클래스입니다.
/// UTKRadioButton 그룹을 사용하여 단일 선택을 제공합니다.
/// </summary>
[UxmlElement]
public partial class UTKRadioPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<int>
{
#region Fields
private VisualElement? _radioContainer;
private List<UTKRadioButton> _radioButtons = new();
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()
{
_radioContainer = this.Q<VisualElement>("radio-container");
// Fallback: UXML에서 못 찾으면 생성
if (_valueContainer != null && _radioContainer == null)
{
_radioContainer = new VisualElement { name = "radio-container" };
_radioContainer.AddToClassList("utk-property-item-view__radio-container");
_valueContainer.Add(_radioContainer);
}
}
#endregion
#region Override Methods
protected override void CreateValueUI(VisualElement container)
{
// UXML/QueryUIElements 기반으로 생성하므로 여기서는 생성하지 않음
}
public override void RefreshUI()
{
UpdateSelection();
}
protected override void OnReadOnlyStateChanged(bool isReadOnly)
{
foreach (var radio in _radioButtons)
{
radio.IsEnabled = !isReadOnly;
}
}
#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()
{
for (int i = 0; i < _radioButtons.Count; i++)
{
_radioButtons[i].SetChecked(i == _value, false);
}
}
#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;
Label = data.Name;
_value = data.Value;
IsReadOnly = data.IsReadOnly;
IsVisible = data.IsVisible;
TooltipText = data.Tooltip;
if (data is UTKRadioPropertyItem radioItem)
{
_choices = radioItem.Choices;
RebuildRadioButtons();
}
data.OnTypedValueChanged += OnDataValueChanged;
UpdateSelection();
UpdateReadOnlyState();
}
public void Unbind()
{
if (_boundData != null)
{
_boundData.OnTypedValueChanged -= OnDataValueChanged;
_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;
}
base.Dispose(disposing);
}
#endregion
}
}