#nullable enable using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// Dropdown 속성 View 클래스입니다. /// UTKDropdown을 사용하여 문자열 목록에서 선택합니다. /// [UxmlElement] public partial class UTKDropdownPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView { #region Fields private UTKDropdown? _dropdown; private string _value = string.Empty; private List _choices = new(); private IUTKPropertyItem? _boundData; #endregion #region Properties protected override string ViewTypeName => "UTKDropdownPropertyItemView"; /// 현재 값 public string Value { get => _value; set { if (_value != value) { _value = value ?? string.Empty; UpdateValueUI(); OnValueChanged?.Invoke(_value); if (_boundData != null && _boundData.Value != _value) { _boundData.Value = _value; } } } } /// 선택 가능한 항목 목록 public List Choices { get => _choices; set { _choices = value ?? new List(); if (_dropdown != null) { _dropdown.SetOptions(_choices); } } } #endregion #region Events public event Action? OnValueChanged; #endregion #region Constructor public UTKDropdownPropertyItemView() : base() { InitializeUI(); } public UTKDropdownPropertyItemView(UTKDropdownPropertyItem data) : base() { _choices = data.Choices; _value = data.Value ?? (_choices.Count > 0 ? _choices[0] : string.Empty); Label = data.Name; _isReadOnly = data.IsReadOnly; InitializeUI(); Bind(data); } public UTKDropdownPropertyItemView(string label, List choices, string initialValue = "") : base() { _choices = choices ?? new List(); _value = initialValue ?? (_choices.Count > 0 ? _choices[0] : string.Empty); Label = label; InitializeUI(); } public UTKDropdownPropertyItemView(string label, IEnumerable choices, int selectedIndex = 0) : base() { _choices = choices?.ToList() ?? new List(); if (_choices.Count > 0 && selectedIndex >= 0 && selectedIndex < _choices.Count) { _value = _choices[selectedIndex]; } Label = label; InitializeUI(); } #endregion #region Initialization private void InitializeUI() { AddToClassList("utk-property-item-view"); AddToClassList("utk-property-item-view--dropdown"); if (!CreateUIFromUxml()) { CreateUIFallback(); } // UXML에서 요소 가져오기 QueryUIElements(); // 이벤트 등록 RegisterEvents(); UpdateValueUI(); UpdateReadOnlyState(); } private void QueryUIElements() { _dropdown = this.Q("dropdown-field"); // Fallback: UXML에서 못 찾으면 생성 if (_valueContainer != null && _dropdown == null) { _dropdown = new UTKDropdown { name = "dropdown-field" }; _dropdown.AddToClassList("utk-property-item-view__dropdown"); _valueContainer.Add(_dropdown); } // 초기 값 설정 if (_dropdown != null) { _dropdown.SetOptions(_choices); int selectedIndex = _choices.IndexOf(_value); _dropdown.SelectedIndex = Math.Max(0, selectedIndex); _dropdown.IsEnabled = !IsReadOnly; } } private void RegisterEvents() { if (_dropdown != null) { _dropdown.OnSelectionChanged += OnDropdownChanged; } } private void UnregisterEvents() { if (_dropdown != null) { _dropdown.OnSelectionChanged -= OnDropdownChanged; } } #endregion #region Override Methods protected override void CreateValueUI(VisualElement container) { // UXML/QueryUIElements 기반으로 생성하므로 여기서는 생성하지 않음 } public override void RefreshUI() { UpdateValueUI(); } protected override void OnReadOnlyStateChanged(bool isReadOnly) { if (_dropdown != null) { _dropdown.IsEnabled = !isReadOnly; } } #endregion #region Event Handling private void OnDropdownChanged(int index, string newValue) { if (_value != newValue) { _value = newValue; OnValueChanged?.Invoke(newValue); if (_boundData != null && _boundData.Value != newValue) { _boundData.Value = newValue; } } } #endregion #region Value Update private void UpdateValueUI() { if (_dropdown != null && _dropdown.SelectedValue != _value) { _dropdown.SetSelectedValue(_value, notify: false); } } #endregion #region Public Methods /// 선택 항목 추가 public void AddChoice(string choice) { if (!_choices.Contains(choice)) { _choices.Add(choice); if (_dropdown != null) { _dropdown.AddOption(choice); } } } /// 선택 항목 제거 public bool RemoveChoice(string choice) { bool removed = _choices.Remove(choice); if (removed && _dropdown != null) { _dropdown.SetOptions(_choices); if (_value == choice && _choices.Count > 0) { Value = _choices[0]; } } return removed; } #endregion #region Data Binding public void Bind(IUTKPropertyItem data) { if (data is IUTKPropertyItem stringData) { Bind(stringData); } else { Debug.LogWarning($"[UTKDropdownPropertyItemView] Cannot bind to non-string data: {data.GetType().Name}"); } } public void Bind(IUTKPropertyItem data) { Unbind(); _boundData = data; Label = data.Name; _value = data.Value ?? string.Empty; IsReadOnly = data.IsReadOnly; IsVisible = data.IsVisible; TooltipText = data.Tooltip; if (data is UTKDropdownPropertyItem dropdownItem) { _choices = dropdownItem.Choices; if (_dropdown != null) { _dropdown.SetOptions(_choices); } } data.OnTypedValueChanged += OnDataValueChanged; UpdateValueUI(); UpdateReadOnlyState(); } public void Unbind() { if (_boundData != null) { _boundData.OnTypedValueChanged -= OnDataValueChanged; _boundData = null; } } private void OnDataValueChanged(IUTKPropertyItem item, string oldValue, string newValue) { if (_value != newValue) { _value = newValue ?? string.Empty; UpdateValueUI(); } } #endregion #region Dispose protected override void Dispose(bool disposing) { if (_disposed) return; if (disposing) { UnregisterEvents(); Unbind(); OnValueChanged = null; _dropdown = null; } base.Dispose(disposing); } #endregion } }