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