#nullable enable using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// MultiSelectDropdown 속성 View 클래스입니다. /// UTKMultiSelectDropdown을 사용하여 문자열 목록에서 다중 선택합니다. /// ReadOnly 상태에서는 UTKInputField로 표시됩니다. /// [UxmlElement] public partial class UTKMultiSelectDropdownPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView> { #region Fields private UTKMultiSelectDropdown? _multiSelectDropdown; private UTKInputField? _readOnlyField; private List _value = new(); private List _choices = new(); private IUTKPropertyItem>? _boundData; #endregion #region Properties protected override string ViewTypeName => "UTKMultiSelectDropdownPropertyItemView"; /// 현재 값 (선택된 항목 목록) public List Value { get => _value; set { var newValue = value ?? new List(); // 리스트 내용 비교 if (!AreListsEqual(_value, newValue)) { _value = new List(newValue); UpdateValueUI(); OnValueChanged?.Invoke(_value); if (_boundData != null && !AreListsEqual(_boundData.Value, _value)) { _boundData.Value = new List(_value); } } } } /// 선택 가능한 항목 목록 public List Choices { get => _choices; set { _choices = value ?? new List(); if (_multiSelectDropdown != null) { _multiSelectDropdown.SetOptions(_choices); } } } #endregion #region Events public event Action>? OnValueChanged; #endregion #region Constructor public UTKMultiSelectDropdownPropertyItemView() : base() { InitializeUI(); } public UTKMultiSelectDropdownPropertyItemView(UTKMultiSelectDropdownPropertyItem data) : base() { _choices = data.Choices; _value = data.Value != null ? new List(data.Value) : new List(); Label = data.Name; _isReadOnly = data.IsReadOnly; InitializeUI(); Bind(data); } public UTKMultiSelectDropdownPropertyItemView( string label, List choices, List? initialValues = null) : base() { _choices = choices ?? new List(); _value = initialValues != null ? new List(initialValues) : new List(); Label = label; InitializeUI(); } public UTKMultiSelectDropdownPropertyItemView( string label, IEnumerable choices, IEnumerable? selectedIndices = null) : base() { _choices = choices?.ToList() ?? new List(); 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("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("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 indices, List values) { if (!AreListsEqual(_value, values)) { _value = new List(values); OnValueChanged?.Invoke(_value); if (_boundData != null && !AreListsEqual(_boundData.Value, _value)) { _boundData.Value = new List(_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 /// 두 리스트의 내용이 같은지 비교 (순서 무관) private bool AreListsEqual(List? list1, List? 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(list1); var set2 = new HashSet(list2); return set1.SetEquals(set2); } #endregion #region Public Methods /// 선택 항목 추가 public void AddChoice(string choice) { if (!_choices.Contains(choice)) { _choices.Add(choice); if (_multiSelectDropdown != null) { _multiSelectDropdown.SetOptions(_choices); } } } /// 선택 항목 제거 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; } /// 인덱스로 선택 설정 public void SetSelectedIndices(List indices, bool notify = true) { if (_multiSelectDropdown != null) { _multiSelectDropdown.SetSelectedIndices(indices, notify); } } /// 값으로 선택 설정 public void SetSelectedValues(List values, bool notify = true) { if (_multiSelectDropdown != null) { _multiSelectDropdown.SetSelectedValues(values, notify); } } /// 모든 항목 선택 public void SelectAll() { _multiSelectDropdown?.SelectAll(); } /// 모든 선택 해제 public void ClearSelection() { _multiSelectDropdown?.ClearSelection(); } #endregion #region Data Binding public void Bind(IUTKPropertyItem data) { if (data is IUTKPropertyItem> listData) { Bind(listData); } else { Debug.LogWarning($"[UTKMultiSelectDropdownPropertyItemView] Cannot bind to non-List data: {data.GetType().Name}"); } } public void Bind(IUTKPropertyItem> data) { Unbind(); _boundData = data; BindBase(data); Label = data.Name; _value = data.Value != null ? new List(data.Value) : new List(); IsVisible = data.IsVisible; TooltipText = data.Tooltip; ShowLabel = data.ShowLabel; if (data is UTKMultiSelectDropdownPropertyItem multiSelectItem) { _choices = multiSelectItem.Choices; if (_multiSelectDropdown != null) { _multiSelectDropdown.SetOptions(_choices); } } data.OnTypedValueChanged += OnDataValueChanged; UpdateValueUI(); IsReadOnly = data.IsReadOnly; } public void Unbind() { if (_boundData != null) { _boundData.OnTypedValueChanged -= OnDataValueChanged; UnbindBase(); _boundData = null; } } private void OnDataValueChanged(IUTKPropertyItem> item, List oldValue, List newValue) { if (!AreListsEqual(_value, newValue)) { _value = newValue != null ? new List(newValue) : new List(); 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 } }