#nullable enable using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// Radio 속성 View 클래스입니다. /// UTKRadioButton 그룹을 사용하여 단일 선택을 제공합니다. /// ReadOnly 상태에서는 UTKInputField로 표시됩니다. /// [UxmlElement] public partial class UTKRadioPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView { #region Fields private VisualElement? _radioContainer; private List _radioButtons = new(); private UTKInputField? _readOnlyField; private int _value; private List _choices = new(); private IUTKPropertyItem? _boundData; #endregion #region Properties protected override string ViewTypeName => "UTKRadioPropertyItemView"; /// 현재 선택된 인덱스 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; } } } } /// 선택 가능한 항목 목록 public List Choices { get => _choices; set { _choices = value ?? new List(); RebuildRadioButtons(); } } /// 현재 선택된 항목의 텍스트 public string? SelectedText { get { if (_value >= 0 && _value < _choices.Count) { return _choices[_value]; } return null; } } #endregion #region Events public event Action? 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 choices, int selectedIndex = 0) : base() { _choices = choices ?? new List(); _value = Math.Max(0, Math.Min(selectedIndex, _choices.Count - 1)); Label = label; InitializeUI(); } public UTKRadioPropertyItemView(string label, IEnumerable choices, int selectedIndex = 0) : this(label, choices?.ToList() ?? new List(), 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() { if (_valueContainer == null) return; if (IsReadOnly) { // ReadOnly: UTKInputField 사용 _readOnlyField = this.Q("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("radio-container"); if (_radioContainer != null) { _radioContainer.style.display = DisplayStyle.None; } } else { // Editable: Radio 버튼 사용 _radioContainer = this.Q("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 #region Override Methods protected override void CreateValueUI(VisualElement container) { // UXML/QueryUIElements 기반으로 생성하므로 여기서는 생성하지 않음 } public override void RefreshUI() { UpdateSelection(); } protected override void OnReadOnlyStateChanged(bool isReadOnly) { 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 { // 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 #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() { if (IsReadOnly && _readOnlyField != null) { _readOnlyField.Value = SelectedText ?? ""; } else { for (int i = 0; i < _radioButtons.Count; i++) { _radioButtons[i].SetChecked(i == _value, false); } } } #endregion #region Public Methods /// 텍스트로 선택 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 intData) { Bind(intData); } else { Debug.LogWarning($"[UTKRadioPropertyItemView] Cannot bind to non-int data: {data.GetType().Name}"); } } public void Bind(IUTKPropertyItem data) { Unbind(); _boundData = data; BindBase(data); Label = data.Name; _value = data.Value; IsVisible = data.IsVisible; TooltipText = data.Tooltip; ShowLabel = data.ShowLabel; if (data is UTKRadioPropertyItem radioItem) { _choices = radioItem.Choices; RebuildRadioButtons(); } data.OnTypedValueChanged += OnDataValueChanged; UpdateSelection(); IsReadOnly = data.IsReadOnly; } public void Unbind() { if (_boundData != null) { _boundData.OnTypedValueChanged -= OnDataValueChanged; UnbindBase(); _boundData = null; } } private void OnDataValueChanged(IUTKPropertyItem 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; _readOnlyField = null; } base.Dispose(disposing); } #endregion } }