593 lines
22 KiB
C#
593 lines
22 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace UVC.UIToolkit
|
|
{
|
|
/// <summary>
|
|
/// UTKButton 하나를 트리거로 사용하는 드롭다운 컴포넌트.
|
|
/// 기본 표시는 외부에서 주입된 UTKButton 하나만 보이며, 클릭 시 팝업 목록이 열립니다.
|
|
/// 선택된 항목은 버튼 텍스트에 반영되고, 팝업 목록에서는 체크 아이콘으로 표시됩니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para><b>특징:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description>기본 UI: <c>UTKButton</c> 하나만 표시</description></item>
|
|
/// <item><description>트리거 버튼은 생성자 기본값 또는 <c>SetButton()</c>으로 교체 가능</description></item>
|
|
/// <item><description>팝업: 루트 패널로 이동하여 다른 UI 위에 렌더링</description></item>
|
|
/// <item><description>선택된 항목은 체크 아이콘으로 표시</description></item>
|
|
/// <item><description>외부 클릭 시 팝업 자동 닫힘</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>주요 메서드:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description><c>SetButton(UTKButton)</c> - 트리거 버튼 교체</description></item>
|
|
/// <item><description><c>SetOptions(List<string>)</c> - 옵션 목록 교체</description></item>
|
|
/// <item><description><c>SetSelectedIndex(int, bool)</c> - 인덱스로 선택</description></item>
|
|
/// <item><description><c>SetSelectedValue(string, bool)</c> - 값으로 선택</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>주요 이벤트:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description><c>OnSelectionChanged</c> - 선택 변경 시 발생 (인덱스, 값 전달)</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <code>
|
|
/// // 기본 생성 (Language 아이콘 + Text variant)
|
|
/// var dropdown = new UTKButtonDropdown();
|
|
/// dropdown.SetOptions(new List<string> { "한국어", "English", "日本語" });
|
|
/// dropdown.OnSelectionChanged += (index, value) => Debug.Log($"선택: {value}");
|
|
///
|
|
/// // 버튼 교체
|
|
/// var myBtn = new UTKButton("EN", UTKMaterialIcons.Language, UTKButton.ButtonVariant.Ghost);
|
|
/// dropdown.SetButton(myBtn);
|
|
///
|
|
/// // 트리거 버튼에 직접 접근
|
|
/// dropdown.TriggerButton.Size = UTKButton.ButtonSize.Small;
|
|
///
|
|
/// // UXML
|
|
/// // <utk:UTKButtonDropdown choices="한국어,English,日本語" index="0" />
|
|
/// </code>
|
|
/// </example>
|
|
[UxmlElement]
|
|
public partial class UTKButtonDropdown : VisualElement, IDisposable
|
|
{
|
|
#region Constants
|
|
private const string USS_PATH = "UIToolkit/Dropdown/UTKButtonDropdownUss";
|
|
private const string DEFAULT_PLACEHOLDER = "Select";
|
|
#endregion
|
|
|
|
#region Fields
|
|
private bool _disposed;
|
|
|
|
// 트리거 버튼
|
|
private UTKButton? _triggerButton;
|
|
|
|
// 팝업 관련
|
|
// _popupLayer: 화면 전체를 덮는 레이어 (panel.visualTree에 추가).
|
|
// pickingMode 기본값(Position) 유지로 이벤트 경로에 포함되어
|
|
// UI Toolkit 패널 내 어디를 클릭해도 OnPanelPointerDown이 발화됨.
|
|
private VisualElement? _popupLayer;
|
|
private VisualElement? _popupContainer;
|
|
private ScrollView? _optionsScrollView;
|
|
|
|
// 상태
|
|
private List<string> _choices = new();
|
|
private int _selectedIndex = -1;
|
|
private string _placeholder = DEFAULT_PLACEHOLDER;
|
|
private bool _isEnabled = true;
|
|
private bool _isPopupOpen;
|
|
|
|
// 팝업 USS 재사용 캐시
|
|
private readonly StyleSheet? _loadedUss;
|
|
|
|
// 옵션 항목과 체크 아이콘 목록 (인덱스 순서 유지)
|
|
private readonly List<(VisualElement container, UTKLabel checkIcon)> _optionItems = new();
|
|
#endregion
|
|
|
|
#region Events
|
|
/// <summary>선택 변경 이벤트 (선택된 인덱스, 선택된 값)</summary>
|
|
public event Action<int, string>? OnSelectionChanged;
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>현재 트리거 버튼. <c>SetButton()</c>으로 교체 가능.</summary>
|
|
public UTKButton? TriggerButton => _triggerButton;
|
|
|
|
/// <summary>선택 가능한 옵션 목록 (쉼표 구분 문자열)</summary>
|
|
[UxmlAttribute("choices")]
|
|
public string Choices
|
|
{
|
|
get => string.Join(",", _choices);
|
|
set
|
|
{
|
|
_choices = string.IsNullOrEmpty(value)
|
|
? new List<string>()
|
|
: new List<string>(value.Split(','));
|
|
RebuildOptions();
|
|
}
|
|
}
|
|
|
|
/// <summary>선택된 인덱스 (0부터, -1은 미선택)</summary>
|
|
[UxmlAttribute("index")]
|
|
public int SelectedIndex
|
|
{
|
|
get => _selectedIndex;
|
|
set => SetSelectedIndex(value, true);
|
|
}
|
|
|
|
/// <summary>선택된 값 (읽기 전용)</summary>
|
|
public string? SelectedValue =>
|
|
_selectedIndex >= 0 && _selectedIndex < _choices.Count
|
|
? _choices[_selectedIndex]
|
|
: null;
|
|
|
|
/// <summary>미선택 시 버튼에 표시될 텍스트</summary>
|
|
[UxmlAttribute("placeholder")]
|
|
public string Placeholder
|
|
{
|
|
get => _placeholder;
|
|
set
|
|
{
|
|
_placeholder = value;
|
|
UpdateButtonText();
|
|
}
|
|
}
|
|
|
|
/// <summary>활성화 상태</summary>
|
|
[UxmlAttribute("is-enabled")]
|
|
public bool IsEnabled
|
|
{
|
|
get => _isEnabled;
|
|
set
|
|
{
|
|
_isEnabled = value;
|
|
if(_triggerButton != null) _triggerButton.IsEnabled = value;
|
|
EnableInClassList("utk-button-dropdown--disabled", !value);
|
|
// _popupLayer는 panel.visualTree로 이동하므로 부모 CSS 선택자가 적용되지 않음
|
|
// _popupLayer에 직접 disabled 클래스를 토글하여 옵션 hover 억제
|
|
_popupLayer?.EnableInClassList("utk-button-dropdown--disabled", !value);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Constructor
|
|
public UTKButtonDropdown()
|
|
{
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
|
|
_loadedUss = Resources.Load<StyleSheet>(USS_PATH);
|
|
if (_loadedUss != null)
|
|
{
|
|
styleSheets.Add(_loadedUss);
|
|
}
|
|
|
|
CreateUI();
|
|
SetupEvents();
|
|
SubscribeToThemeChanges();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 옵션 목록을 지정하여 생성합니다.
|
|
/// </summary>
|
|
/// <param name="options">옵션 목록</param>
|
|
public UTKButtonDropdown(List<string>? options) : this()
|
|
{
|
|
if (options != null)
|
|
{
|
|
SetOptions(options);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region UI Creation
|
|
private void CreateUI()
|
|
{
|
|
AddToClassList("utk-button-dropdown");
|
|
focusable = true;
|
|
|
|
// 기본 트리거 버튼: Check 아이콘 + Text variant
|
|
_triggerButton = new UTKButton(
|
|
text: _placeholder,
|
|
icon: UTKMaterialIcons.Check,
|
|
variant: UTKButton.ButtonVariant.Text
|
|
);
|
|
_triggerButton.AddToClassList("utk-button-dropdown__trigger");
|
|
Add(_triggerButton);
|
|
|
|
// 팝업 레이어: 화면 전체를 덮는 컨테이너 (OpenPopup 시 panel.visualTree에 추가)
|
|
// pickingMode 기본값(Position) 유지 — 이벤트 경로에 포함되어야
|
|
// UI Toolkit 패널 내 어디를 클릭해도 OnPanelPointerDown이 발화됨
|
|
_popupLayer = new VisualElement();
|
|
_popupLayer.name = "utk-button-dropdown-layer";
|
|
_popupLayer.style.position = Position.Absolute;
|
|
_popupLayer.style.left = 0;
|
|
_popupLayer.style.top = 0;
|
|
_popupLayer.style.right = 0;
|
|
_popupLayer.style.bottom = 0;
|
|
_popupLayer.style.display = DisplayStyle.None;
|
|
|
|
// 팝업 컨테이너 (실제 옵션 목록이 표시되는 영역)
|
|
_popupContainer = new VisualElement();
|
|
_popupContainer.AddToClassList("utk-button-dropdown__popup");
|
|
_popupLayer.Add(_popupContainer);
|
|
|
|
// 옵션 스크롤뷰
|
|
_optionsScrollView = new ScrollView(ScrollViewMode.Vertical);
|
|
_optionsScrollView.AddToClassList("utk-button-dropdown__options");
|
|
_popupContainer.Add(_optionsScrollView);
|
|
}
|
|
|
|
private void SetupEvents()
|
|
{
|
|
_triggerButton?.RegisterCallback<ClickEvent>(OnTriggerClicked);
|
|
RegisterCallback<AttachToPanelEvent>(OnAttachedToPanel);
|
|
RegisterCallback<DetachFromPanelEvent>(OnDetachedFromPanel);
|
|
}
|
|
|
|
private void SubscribeToThemeChanges()
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
|
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
}
|
|
#endregion
|
|
|
|
#region Theme Handlers
|
|
private void OnAttachToPanelForTheme(AttachToPanelEvent evt)
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
}
|
|
|
|
private void OnDetachFromPanelForTheme(DetachFromPanelEvent evt)
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
}
|
|
|
|
private void OnThemeChanged(UTKTheme theme)
|
|
{
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
if (_popupLayer != null && _isPopupOpen)
|
|
{
|
|
UTKThemeManager.Instance.ApplyThemeToElement(_popupLayer);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
private void OnTriggerClicked(ClickEvent evt)
|
|
{
|
|
if (!_isEnabled) return;
|
|
|
|
if (_isPopupOpen) ClosePopup();
|
|
else OpenPopup();
|
|
|
|
evt.StopPropagation();
|
|
}
|
|
|
|
private void OnAttachedToPanel(AttachToPanelEvent evt)
|
|
{
|
|
if (panel != null)
|
|
{
|
|
panel.visualTree.RegisterCallback<PointerDownEvent>(OnPanelPointerDown, TrickleDown.TrickleDown);
|
|
}
|
|
}
|
|
|
|
private void OnDetachedFromPanel(DetachFromPanelEvent evt)
|
|
{
|
|
if (_isPopupOpen) ForceClosePopup();
|
|
|
|
if (panel != null)
|
|
{
|
|
panel.visualTree.UnregisterCallback<PointerDownEvent>(OnPanelPointerDown, TrickleDown.TrickleDown);
|
|
}
|
|
}
|
|
|
|
private void OnPanelPointerDown(PointerDownEvent evt)
|
|
{
|
|
if (!_isPopupOpen) return;
|
|
|
|
var target = evt.target as VisualElement;
|
|
if (target == null) return;
|
|
|
|
// 트리거 버튼 클릭이면 무시 (OnTriggerClicked에서 처리)
|
|
if (_triggerButton != null && target.FindCommonAncestor(_triggerButton) == _triggerButton) return;
|
|
// 팝업 컨테이너 내부 클릭이면 무시
|
|
if (_popupContainer != null && target.FindCommonAncestor(_popupContainer) == _popupContainer) return;
|
|
|
|
// 팝업 레이어 외부(_popupLayer 자체 포함) 또는 다른 UI 영역 클릭 → 닫기
|
|
ClosePopup();
|
|
}
|
|
|
|
private void OnOptionClicked(int index)
|
|
{
|
|
SetSelectedIndex(index, true);
|
|
ClosePopup();
|
|
}
|
|
|
|
/// <summary>userData에서 인덱스를 읽어 선택 처리 — 클로저 미사용으로 GC 최소화</summary>
|
|
private void OnOptionItemClicked(ClickEvent evt)
|
|
{
|
|
if (evt.currentTarget is VisualElement ve && ve.userData is int index)
|
|
{
|
|
OnOptionClicked(index);
|
|
}
|
|
evt.StopPropagation();
|
|
}
|
|
|
|
private void OnOptionMouseEnter(MouseEnterEvent evt)
|
|
{
|
|
(evt.currentTarget as VisualElement)?.AddToClassList("utk-button-dropdown__option--hover");
|
|
}
|
|
|
|
private void OnOptionMouseLeave(MouseLeaveEvent evt)
|
|
{
|
|
(evt.currentTarget as VisualElement)?.RemoveFromClassList("utk-button-dropdown__option--hover");
|
|
}
|
|
#endregion
|
|
|
|
#region Popup
|
|
private void OpenPopup()
|
|
{
|
|
if (_popupLayer == null || _popupContainer == null || _triggerButton == null || panel == null) return;
|
|
|
|
// _popupLayer를 panel.visualTree에 추가 (다른 UI 위에 렌더링, 한 번만 수행)
|
|
if (_popupLayer.parent != panel.visualTree)
|
|
{
|
|
_popupLayer.RemoveFromHierarchy();
|
|
panel.visualTree.Add(_popupLayer);
|
|
UTKThemeManager.Instance.ApplyThemeToElement(_popupLayer);
|
|
|
|
// USS를 _popupLayer에 적용 (루트로 이동하면 부모 StyleSheet 상속 안 됨)
|
|
if (_loadedUss != null && !_popupLayer.styleSheets.Contains(_loadedUss))
|
|
{
|
|
_popupLayer.styleSheets.Add(_loadedUss);
|
|
}
|
|
}
|
|
|
|
// 트리거 버튼의 월드 좌표 기준으로 팝업 위치 설정
|
|
var bounds = _triggerButton.worldBound;
|
|
_popupContainer.style.position = Position.Absolute;
|
|
_popupContainer.style.left = bounds.x;
|
|
_popupContainer.style.top = bounds.yMax + 2f;
|
|
_popupContainer.style.minWidth = bounds.width;
|
|
|
|
_popupLayer.style.display = DisplayStyle.Flex;
|
|
|
|
_isPopupOpen = true;
|
|
EnableInClassList("utk-button-dropdown--open", true);
|
|
}
|
|
|
|
private void ClosePopup()
|
|
{
|
|
if (_popupLayer == null) return;
|
|
|
|
// 레이어를 숨김 처리 (RemoveFromHierarchy 없이 DisplayStyle 토글 — 레이아웃 재계산 최소화)
|
|
_popupLayer.style.display = DisplayStyle.None;
|
|
|
|
_isPopupOpen = false;
|
|
EnableInClassList("utk-button-dropdown--open", false);
|
|
}
|
|
|
|
/// <summary>패널 해제 등 강제 종료 시 _popupLayer를 계층에서 제거</summary>
|
|
private void ForceClosePopup()
|
|
{
|
|
if (_popupLayer == null) return;
|
|
_popupLayer.style.display = DisplayStyle.None;
|
|
_popupLayer.RemoveFromHierarchy();
|
|
_isPopupOpen = false;
|
|
}
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
/// <summary>
|
|
/// 트리거 버튼을 외부에서 전달한 UTKButton으로 교체합니다.
|
|
/// 기존 버튼의 이벤트를 해제하고 Dispose한 뒤, 새 버튼을 등록합니다.
|
|
/// </summary>
|
|
/// <param name="button">새로 사용할 UTKButton 인스턴스</param>
|
|
public void SetButton(UTKButton button)
|
|
{
|
|
if (_disposed) return;
|
|
|
|
// 기존 버튼 정리
|
|
if (_triggerButton != null)
|
|
{
|
|
_triggerButton.UnregisterCallback<ClickEvent>(OnTriggerClicked);
|
|
_triggerButton.RemoveFromHierarchy();
|
|
_triggerButton.Dispose();
|
|
}
|
|
|
|
// 새 버튼 설정
|
|
_triggerButton = button;
|
|
_triggerButton.AddToClassList("utk-button-dropdown__trigger");
|
|
_triggerButton.SetEnabled(_isEnabled);
|
|
|
|
// 선택 상태 반영
|
|
UpdateButtonText();
|
|
|
|
// 팝업 컨테이너보다 앞에 삽입 (index 0)
|
|
Insert(0, _triggerButton);
|
|
_triggerButton.RegisterCallback<ClickEvent>(OnTriggerClicked);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 옵션 목록 전체 교체
|
|
/// </summary>
|
|
/// <param name="options">옵션 목록</param>
|
|
public void SetOptions(List<string> options)
|
|
{
|
|
_choices = options ?? new List<string>();
|
|
RebuildOptions();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 옵션 하나 추가
|
|
/// </summary>
|
|
/// <param name="option">추가할 옵션 이름</param>
|
|
public void AddOption(string option)
|
|
{
|
|
_choices.Add(option);
|
|
RebuildOptions();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 인덱스로 선택
|
|
/// </summary>
|
|
/// <param name="index">선택할 인덱스 (-1은 미선택)</param>
|
|
/// <param name="notify">true면 OnSelectionChanged 발생</param>
|
|
public void SetSelectedIndex(int index, bool notify)
|
|
{
|
|
if (index < -1 || index >= _choices.Count) return;
|
|
if (_selectedIndex == index) return;
|
|
|
|
_selectedIndex = index;
|
|
UpdateButtonText();
|
|
UpdateCheckIcons();
|
|
|
|
if (notify && _selectedIndex >= 0)
|
|
{
|
|
OnSelectionChanged?.Invoke(_selectedIndex, _choices[_selectedIndex]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 값으로 선택
|
|
/// </summary>
|
|
/// <param name="selectedValue">선택할 값 (null이면 미선택)</param>
|
|
/// <param name="notify">true면 OnSelectionChanged 발생</param>
|
|
public void SetSelectedValue(string? selectedValue, bool notify = true)
|
|
{
|
|
if (selectedValue == null)
|
|
{
|
|
SetSelectedIndex(-1, notify);
|
|
return;
|
|
}
|
|
|
|
var idx = _choices.IndexOf(selectedValue);
|
|
if (idx >= 0)
|
|
{
|
|
SetSelectedIndex(idx, notify);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
/// <summary>
|
|
/// 옵션 목록을 다시 빌드합니다.
|
|
/// 기존 이벤트 핸들러를 명시적으로 해제하여 메모리 누수를 방지합니다.
|
|
/// </summary>
|
|
private void RebuildOptions()
|
|
{
|
|
if (_optionsScrollView == null) return;
|
|
|
|
// 기존 옵션의 이벤트 핸들러 해제
|
|
foreach (var (container, _) in _optionItems)
|
|
{
|
|
container.UnregisterCallback<ClickEvent>(OnOptionItemClicked);
|
|
container.UnregisterCallback<MouseEnterEvent>(OnOptionMouseEnter);
|
|
container.UnregisterCallback<MouseLeaveEvent>(OnOptionMouseLeave);
|
|
}
|
|
|
|
_optionItems.Clear();
|
|
_optionsScrollView.Clear();
|
|
|
|
for (int i = 0; i < _choices.Count; i++)
|
|
{
|
|
var optionContainer = new VisualElement();
|
|
optionContainer.AddToClassList("utk-button-dropdown__option");
|
|
// 인덱스를 userData에 저장 — 클로저 대신 userData 활용으로 GC 부담 감소
|
|
optionContainer.userData = i;
|
|
|
|
// 체크 아이콘
|
|
var checkIcon = new UTKLabel(UTKMaterialIcons.Check, 16);
|
|
checkIcon.AddToClassList("utk-button-dropdown__check-icon");
|
|
checkIcon.style.display = _selectedIndex == i ? DisplayStyle.Flex : DisplayStyle.None;
|
|
optionContainer.Add(checkIcon);
|
|
|
|
// 옵션 라벨 (클릭 이벤트는 container에서만 처리)
|
|
var optionLabel = new Label(_choices[i]);
|
|
optionLabel.AddToClassList("utk-button-dropdown__option-label");
|
|
optionLabel.pickingMode = PickingMode.Ignore;
|
|
optionContainer.Add(optionLabel);
|
|
|
|
// 비클로저 핸들러로 이벤트 등록
|
|
optionContainer.RegisterCallback<ClickEvent>(OnOptionItemClicked);
|
|
optionContainer.RegisterCallback<MouseEnterEvent>(OnOptionMouseEnter);
|
|
optionContainer.RegisterCallback<MouseLeaveEvent>(OnOptionMouseLeave);
|
|
|
|
_optionItems.Add((optionContainer, checkIcon));
|
|
_optionsScrollView.Add(optionContainer);
|
|
}
|
|
|
|
UpdateButtonText();
|
|
}
|
|
|
|
private void UpdateButtonText()
|
|
{
|
|
if (_triggerButton == null) return;
|
|
|
|
_triggerButton.Text = _selectedIndex >= 0 && _selectedIndex < _choices.Count
|
|
? _choices[_selectedIndex]
|
|
: _placeholder;
|
|
}
|
|
|
|
private void UpdateCheckIcons()
|
|
{
|
|
for (int i = 0; i < _optionItems.Count; i++)
|
|
{
|
|
var (_, checkIcon) = _optionItems[i];
|
|
checkIcon.style.display = _selectedIndex == i ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
|
|
// 테마 이벤트 해제
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
|
|
// 패널 포인터 이벤트 해제
|
|
UnregisterCallback<AttachToPanelEvent>(OnAttachedToPanel);
|
|
UnregisterCallback<DetachFromPanelEvent>(OnDetachedFromPanel);
|
|
if (panel != null)
|
|
{
|
|
panel.visualTree.UnregisterCallback<PointerDownEvent>(OnPanelPointerDown, TrickleDown.TrickleDown);
|
|
}
|
|
|
|
// 트리거 버튼 이벤트 해제 및 Dispose
|
|
if (_triggerButton != null)
|
|
{
|
|
_triggerButton.UnregisterCallback<ClickEvent>(OnTriggerClicked);
|
|
_triggerButton.Dispose();
|
|
}
|
|
|
|
// 옵션 이벤트 해제
|
|
foreach (var (container, _) in _optionItems)
|
|
{
|
|
container.UnregisterCallback<ClickEvent>(OnOptionItemClicked);
|
|
container.UnregisterCallback<MouseEnterEvent>(OnOptionMouseEnter);
|
|
container.UnregisterCallback<MouseLeaveEvent>(OnOptionMouseLeave);
|
|
}
|
|
_optionItems.Clear();
|
|
|
|
// 팝업 정리 (_popupLayer를 계층에서 제거)
|
|
if (_isPopupOpen) ForceClosePopup();
|
|
else _popupLayer?.RemoveFromHierarchy();
|
|
|
|
OnSelectionChanged = null;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|