822 lines
30 KiB
C#
822 lines
30 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace UVC.UIToolkit
|
|
{
|
|
/// <summary>
|
|
/// UTKPropertyList에 탭 기능을 결합한 프로퍼티 윈도우입니다.
|
|
/// 헤더(타이틀 + 닫기 버튼), 탭 버튼 영역, 프로퍼티 리스트로 구성되며,
|
|
/// 탭별로 서로 다른 프로퍼티 데이터를 설정할 수 있습니다.
|
|
///
|
|
/// <para><b>주요 기능:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item>윈도우 프레임 (헤더, 타이틀, 닫기 버튼)</item>
|
|
/// <item>Button 기반 탭 전환 (UTKComponentTabListWindow와 동일 구조)</item>
|
|
/// <item>탭별 프로퍼티 데이터 관리 (Flat/Grouped/Mixed 지원)</item>
|
|
/// <item>탭별 검색어 저장/복원</item>
|
|
/// <item>선택적 "전체(All)" 탭</item>
|
|
/// <item>헤더 드래그로 위치 이동</item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>사용 예 (C#):</b></para>
|
|
/// <code>
|
|
/// var window = new UTKPropertyTabListWindow("속성 편집기");
|
|
/// window.ShowCloseButton = true;
|
|
/// window.ShowAllTab = false;
|
|
///
|
|
/// var generalTab = new TabPropertyData("일반");
|
|
/// generalTab.SetGroupedData(new List<IUTKPropertyGroup> { transformGroup, renderGroup });
|
|
///
|
|
/// var advancedTab = new TabPropertyData("고급");
|
|
/// advancedTab.SetFlatData(new List<IUTKPropertyItem> { debugItem, logItem });
|
|
///
|
|
/// window.SetTabData(new List<TabPropertyData> { generalTab, advancedTab });
|
|
///
|
|
/// window.OnTabChanged += (index, data) => Debug.Log($"탭 변경: {data?.Name}");
|
|
/// window.OnPropertyValueChanged += args => Debug.Log($"{args.PropertyId} = {args.NewValue}");
|
|
///
|
|
/// root.Add(window);
|
|
/// </code>
|
|
/// </summary>
|
|
[UxmlElement]
|
|
public partial class UTKPropertyTabListWindow : VisualElement, IDisposable
|
|
{
|
|
#region Constants
|
|
private const string UXML_PATH = "UIToolkit/Window/UTKPropertyTabListWindow";
|
|
private const string USS_PATH = "UIToolkit/Window/UTKPropertyTabListWindowUss";
|
|
private const int ALL_TAB_INDEX = -1;
|
|
#endregion
|
|
|
|
#region Fields
|
|
private bool _disposed;
|
|
|
|
// UI 요소 참조
|
|
private VisualElement? _header;
|
|
private Label? _titleLabel;
|
|
private UTKButton? _closeButton;
|
|
private VisualElement? _tabContainer;
|
|
private UTKPropertyList? _propertyList;
|
|
|
|
// 탭 버튼
|
|
private readonly List<Button> _tabButtons = new();
|
|
|
|
// 탭 데이터
|
|
private readonly List<TabPropertyData> _tabDataList = new();
|
|
private int _selectedTabIndex = ALL_TAB_INDEX;
|
|
private bool _showAllTab = false;
|
|
|
|
// 탭별 검색어 저장
|
|
private readonly Dictionary<int, string> _tabSearchQueries = new();
|
|
|
|
// 드래그 상태
|
|
private bool _isDragging;
|
|
private Vector2 _dragStartPosition;
|
|
private Vector2 _dragStartMousePosition;
|
|
|
|
// 윈도우 속성
|
|
private string _title = "Properties";
|
|
private bool _showCloseButton;
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>윈도우 타이틀</summary>
|
|
[UxmlAttribute("title")]
|
|
public string Title
|
|
{
|
|
get => _title;
|
|
set
|
|
{
|
|
_title = value;
|
|
if (_titleLabel != null)
|
|
_titleLabel.text = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>닫기 버튼 표시 여부</summary>
|
|
[UxmlAttribute("show-close-button")]
|
|
public bool ShowCloseButton
|
|
{
|
|
get => _showCloseButton;
|
|
set
|
|
{
|
|
_showCloseButton = value;
|
|
if (_closeButton != null)
|
|
_closeButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
}
|
|
|
|
/// <summary>"전체(All)" 탭 표시 여부</summary>
|
|
[UxmlAttribute("show-all-tab")]
|
|
public bool ShowAllTab
|
|
{
|
|
get => _showAllTab;
|
|
set
|
|
{
|
|
if (_showAllTab == value) return;
|
|
_showAllTab = value;
|
|
if (_tabDataList.Count > 0)
|
|
{
|
|
RebuildTabs();
|
|
SelectTab(_showAllTab ? ALL_TAB_INDEX : 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>현재 선택된 탭 인덱스 (-1: All 탭)</summary>
|
|
public int SelectedTabIndex => _selectedTabIndex;
|
|
|
|
/// <summary>내부 UTKPropertyList 접근</summary>
|
|
public UTKPropertyList PropertyList => _propertyList ??= new UTKPropertyList();
|
|
#endregion
|
|
|
|
#region Events
|
|
/// <summary>닫기 버튼 클릭 이벤트</summary>
|
|
public event Action? OnCloseClicked;
|
|
|
|
/// <summary>탭 변경 이벤트 (탭 인덱스, TabPropertyData)</summary>
|
|
public event Action<int, TabPropertyData?>? OnTabChanged;
|
|
|
|
/// <summary>속성 값 변경 이벤트 (PropertyList 위임)</summary>
|
|
public event Action<UTKPropertyValueChangedEventArgs>? OnPropertyValueChanged
|
|
{
|
|
add => PropertyList.OnPropertyValueChanged += value;
|
|
remove => PropertyList.OnPropertyValueChanged -= value;
|
|
}
|
|
|
|
/// <summary>그룹 펼침/접힘 이벤트 (PropertyList 위임)</summary>
|
|
public event Action<IUTKPropertyGroup, bool>? OnGroupExpandedChanged
|
|
{
|
|
add => PropertyList.OnGroupExpandedChanged += value;
|
|
remove => PropertyList.OnGroupExpandedChanged -= value;
|
|
}
|
|
|
|
/// <summary>속성 클릭 이벤트 (PropertyList 위임)</summary>
|
|
public event Action<IUTKPropertyItem>? OnPropertyClicked
|
|
{
|
|
add => PropertyList.OnPropertyClicked += value;
|
|
remove => PropertyList.OnPropertyClicked -= value;
|
|
}
|
|
|
|
/// <summary>버튼 클릭 이벤트 (PropertyList 위임)</summary>
|
|
public event Action<string, string>? OnPropertyButtonClicked
|
|
{
|
|
add => PropertyList.OnPropertyButtonClicked += value;
|
|
remove => PropertyList.OnPropertyButtonClicked -= value;
|
|
}
|
|
#endregion
|
|
|
|
#region Constructor
|
|
public UTKPropertyTabListWindow()
|
|
{
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
SubscribeToThemeChanges();
|
|
|
|
var styleSheet = Resources.Load<StyleSheet>(USS_PATH);
|
|
if (styleSheet != null)
|
|
styleSheets.Add(styleSheet);
|
|
|
|
CreateUI();
|
|
}
|
|
|
|
public UTKPropertyTabListWindow(string title) : this()
|
|
{
|
|
Title = title;
|
|
}
|
|
|
|
public UTKPropertyTabListWindow(string title, bool showAllTab) : this(title)
|
|
{
|
|
_showAllTab = showAllTab;
|
|
}
|
|
#endregion
|
|
|
|
#region UI Creation
|
|
private void CreateUI()
|
|
{
|
|
AddToClassList("utk-property-tab-window");
|
|
|
|
var asset = Resources.Load<VisualTreeAsset>(UXML_PATH);
|
|
if (asset != null)
|
|
CreateUIFromUxml(asset);
|
|
else
|
|
CreateUIFallback();
|
|
|
|
// 드래그 이벤트
|
|
if (_header != null)
|
|
{
|
|
_header.RegisterCallback<PointerDownEvent>(OnHeaderPointerDown);
|
|
_header.RegisterCallback<PointerMoveEvent>(OnHeaderPointerMove);
|
|
_header.RegisterCallback<PointerUpEvent>(OnHeaderPointerUp);
|
|
}
|
|
}
|
|
|
|
private void CreateUIFromUxml(VisualTreeAsset asset)
|
|
{
|
|
var root = asset.Instantiate();
|
|
var container = root.Q<VisualElement>("container");
|
|
|
|
if (container != null)
|
|
{
|
|
foreach (var child in container.Children().ToArray())
|
|
Add(child);
|
|
}
|
|
else
|
|
{
|
|
Add(root);
|
|
}
|
|
|
|
// 요소 참조 가져오기
|
|
_header = this.Q<VisualElement>("header");
|
|
_titleLabel = this.Q<Label>("title");
|
|
_closeButton = this.Q<UTKButton>("close-btn");
|
|
_tabContainer = this.Q<VisualElement>("tab-container");
|
|
_propertyList = this.Q<UTKPropertyList>();
|
|
|
|
// 타이틀 설정
|
|
if (_titleLabel != null)
|
|
_titleLabel.text = _title;
|
|
|
|
// 닫기 버튼 설정
|
|
if (_closeButton != null)
|
|
{
|
|
_closeButton.SetMaterialIcon(UTKMaterialIcons.Close, 14);
|
|
_closeButton.IconOnly = true;
|
|
_closeButton.OnClicked += () => OnCloseClicked?.Invoke();
|
|
_closeButton.style.display = _showCloseButton ? DisplayStyle.Flex : DisplayStyle.None;
|
|
}
|
|
|
|
// tabContainer가 없으면 생성
|
|
if (_tabContainer == null)
|
|
{
|
|
var scrollView = new ScrollView(ScrollViewMode.Horizontal);
|
|
scrollView.horizontalScrollerVisibility = ScrollerVisibility.Hidden;
|
|
scrollView.verticalScrollerVisibility = ScrollerVisibility.Hidden;
|
|
scrollView.style.flexShrink = 0;
|
|
scrollView.style.marginBottom = 8;
|
|
scrollView.style.maxHeight = 28;
|
|
|
|
_tabContainer = new VisualElement();
|
|
_tabContainer.name = "tab-container";
|
|
_tabContainer.style.flexDirection = FlexDirection.Row;
|
|
_tabContainer.style.flexShrink = 0;
|
|
scrollView.Add(_tabContainer);
|
|
|
|
// PropertyList 앞에 삽입
|
|
if (_propertyList != null)
|
|
{
|
|
int index = IndexOf(_propertyList);
|
|
Insert(index, scrollView);
|
|
}
|
|
else
|
|
{
|
|
Add(scrollView);
|
|
}
|
|
}
|
|
|
|
// PropertyList가 없으면 생성
|
|
if (_propertyList == null)
|
|
{
|
|
_propertyList = new UTKPropertyList();
|
|
_propertyList.style.flexGrow = 1;
|
|
_propertyList.style.width = Length.Percent(100);
|
|
Add(_propertyList);
|
|
}
|
|
}
|
|
|
|
private void CreateUIFallback()
|
|
{
|
|
// 헤더
|
|
_header = new VisualElement();
|
|
_header.name = "header";
|
|
_header.AddToClassList("utk-property-tab-window__header");
|
|
|
|
_titleLabel = new Label(_title);
|
|
_titleLabel.name = "title";
|
|
_titleLabel.AddToClassList("utk-property-tab-window__title");
|
|
_header.Add(_titleLabel);
|
|
|
|
_closeButton = new UTKButton("", UTKMaterialIcons.Close, UTKButton.ButtonVariant.Text, 14);
|
|
_closeButton.name = "close-btn";
|
|
_closeButton.IconOnly = true;
|
|
_closeButton.AddToClassList("utk-property-tab-window__close-btn");
|
|
_closeButton.OnClicked += () => OnCloseClicked?.Invoke();
|
|
_closeButton.style.display = _showCloseButton ? DisplayStyle.Flex : DisplayStyle.None;
|
|
_header.Add(_closeButton);
|
|
|
|
Add(_header);
|
|
|
|
// 탭 스크롤 영역
|
|
var scrollView = new ScrollView(ScrollViewMode.Horizontal);
|
|
scrollView.horizontalScrollerVisibility = ScrollerVisibility.Hidden;
|
|
scrollView.verticalScrollerVisibility = ScrollerVisibility.Hidden;
|
|
scrollView.style.flexShrink = 0;
|
|
scrollView.style.marginBottom = 8;
|
|
scrollView.style.maxHeight = 28;
|
|
|
|
_tabContainer = new VisualElement();
|
|
_tabContainer.name = "tab-container";
|
|
_tabContainer.style.flexDirection = FlexDirection.Row;
|
|
_tabContainer.style.flexShrink = 0;
|
|
scrollView.Add(_tabContainer);
|
|
|
|
Add(scrollView);
|
|
|
|
// PropertyList
|
|
_propertyList = new UTKPropertyList();
|
|
_propertyList.style.flexGrow = 1;
|
|
_propertyList.style.width = Length.Percent(100);
|
|
Add(_propertyList);
|
|
}
|
|
#endregion
|
|
|
|
#region Public Methods - Tab Data Management
|
|
|
|
/// <summary>
|
|
/// 탭 데이터 목록을 설정합니다.
|
|
/// 기존 탭을 모두 제거하고 새로운 탭을 생성합니다.
|
|
/// </summary>
|
|
public void SetTabData(List<TabPropertyData> tabDataList)
|
|
{
|
|
_tabDataList.Clear();
|
|
_tabDataList.AddRange(tabDataList);
|
|
_tabSearchQueries.Clear();
|
|
|
|
RebuildTabs();
|
|
|
|
// 기본 탭 선택
|
|
if (_showAllTab)
|
|
SelectTab(ALL_TAB_INDEX);
|
|
else if (_tabDataList.Count > 0)
|
|
SelectTab(0);
|
|
}
|
|
|
|
/// <summary>탭 데이터를 추가합니다.</summary>
|
|
public void AddTabData(TabPropertyData tabData)
|
|
{
|
|
_tabDataList.Add(tabData);
|
|
RebuildTabs();
|
|
}
|
|
|
|
/// <summary>특정 인덱스의 탭 데이터를 제거합니다.</summary>
|
|
public void RemoveTabData(int index)
|
|
{
|
|
if (index < 0 || index >= _tabDataList.Count) return;
|
|
|
|
_tabDataList.RemoveAt(index);
|
|
_tabSearchQueries.Remove(index);
|
|
RebuildTabs();
|
|
|
|
if (_selectedTabIndex == index)
|
|
SelectTab(_showAllTab ? ALL_TAB_INDEX : 0);
|
|
else if (_selectedTabIndex > index)
|
|
_selectedTabIndex--;
|
|
}
|
|
|
|
/// <summary>특정 인덱스의 탭 데이터를 반환합니다.</summary>
|
|
public TabPropertyData? GetTabData(int index)
|
|
{
|
|
if (index >= 0 && index < _tabDataList.Count)
|
|
return _tabDataList[index];
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 탭을 선택하고 해당 데이터를 PropertyList에 로드합니다.
|
|
/// </summary>
|
|
/// <param name="tabIndex">탭 인덱스 (-1: All 탭, 0+: 개별 탭)</param>
|
|
public void SelectTab(int tabIndex)
|
|
{
|
|
// 유효성 검사
|
|
if (tabIndex != ALL_TAB_INDEX && (tabIndex < 0 || tabIndex >= _tabDataList.Count))
|
|
return;
|
|
if (tabIndex == ALL_TAB_INDEX && !_showAllTab)
|
|
return;
|
|
|
|
// 1. 이전 탭의 검색어 저장
|
|
SaveCurrentSearchQuery();
|
|
|
|
// 2. 탭 인덱스 변경
|
|
_selectedTabIndex = tabIndex;
|
|
|
|
// 3. 탭 선택 스타일 업데이트
|
|
UpdateTabStyles();
|
|
|
|
// 4. 데이터 로드
|
|
LoadDataForTab(tabIndex);
|
|
|
|
// 5. 검색어 복원
|
|
RestoreSearchQuery(tabIndex);
|
|
|
|
// 6. 탭 변경 이벤트 발생
|
|
var tabData = tabIndex >= 0 && tabIndex < _tabDataList.Count
|
|
? _tabDataList[tabIndex]
|
|
: null;
|
|
OnTabChanged?.Invoke(tabIndex, tabData);
|
|
}
|
|
|
|
/// <summary>현재 선택된 탭의 데이터를 다시 로드합니다.</summary>
|
|
public void RefreshCurrentTab()
|
|
{
|
|
LoadDataForTab(_selectedTabIndex);
|
|
}
|
|
#endregion
|
|
|
|
#region Public Methods - Group Management (PropertyList 위임)
|
|
/// <summary>그룹을 추가합니다.</summary>
|
|
public void AddGroup(IUTKPropertyGroup group) => PropertyList.AddGroup(group);
|
|
|
|
/// <summary>지정한 ID의 그룹과 내부 아이템을 모두 제거합니다.</summary>
|
|
public void RemoveGroup(string groupId) => PropertyList.RemoveGroup(groupId);
|
|
|
|
/// <summary>지정한 ID의 그룹을 반환합니다.</summary>
|
|
public IUTKPropertyGroup? GetGroup(string groupId) => PropertyList.GetGroup(groupId);
|
|
|
|
/// <summary>그룹의 펼침/접힘 상태를 설정합니다.</summary>
|
|
public void SetGroupExpanded(string groupId, bool expanded) => PropertyList.SetGroupExpanded(groupId, expanded);
|
|
|
|
/// <summary>그룹의 펼침/접힘 상태를 토글합니다.</summary>
|
|
public void ToggleGroupExpanded(string groupId) => PropertyList.ToggleGroupExpanded(groupId);
|
|
#endregion
|
|
|
|
#region Public Methods - Property Management (PropertyList 위임)
|
|
/// <summary>최상위 속성 아이템을 추가합니다.</summary>
|
|
public void AddProperty(IUTKPropertyItem item) => PropertyList.AddProperty(item);
|
|
|
|
/// <summary>지정한 그룹에 속성 아이템을 추가합니다.</summary>
|
|
public void AddPropertyToGroup(string groupId, IUTKPropertyItem item) => PropertyList.AddPropertyToGroup(groupId, item);
|
|
|
|
/// <summary>지정한 ID의 속성 아이템을 제거합니다.</summary>
|
|
public void RemoveProperty(string itemId) => PropertyList.RemoveProperty(itemId);
|
|
|
|
/// <summary>지정한 ID의 속성 아이템을 반환합니다.</summary>
|
|
public IUTKPropertyItem? GetProperty(string itemId) => PropertyList.GetProperty(itemId);
|
|
#endregion
|
|
|
|
#region Public Methods - Value Management (PropertyList 위임)
|
|
/// <summary>속성 값을 변경합니다.</summary>
|
|
public void UpdatePropertyValue(string propertyId, object newValue, bool notify = false) => PropertyList.UpdatePropertyValue(propertyId, newValue, notify);
|
|
|
|
/// <summary>속성 값을 변경합니다. UpdatePropertyValue의 별칭입니다.</summary>
|
|
public void SetPropertyValue(string propertyId, object value, bool notify = false) => PropertyList.SetPropertyValue(propertyId, value, notify);
|
|
#endregion
|
|
|
|
#region Public Methods - Visibility & ReadOnly (PropertyList 위임)
|
|
/// <summary>속성 아이템의 가시성을 변경합니다.</summary>
|
|
public void SetPropertyVisibility(string propertyId, bool visible) => PropertyList.SetPropertyVisibility(propertyId, visible);
|
|
|
|
/// <summary>여러 속성의 가시성을 일괄 변경합니다.</summary>
|
|
public void SetPropertyVisibilityBatch(IEnumerable<(string propertyId, bool visible)> changes) => PropertyList.SetPropertyVisibilityBatch(changes);
|
|
|
|
/// <summary>그룹의 가시성을 변경합니다.</summary>
|
|
public void SetGroupVisibility(string groupId, bool visible) => PropertyList.SetGroupVisibility(groupId, visible);
|
|
|
|
/// <summary>속성 아이템의 읽기 전용 상태를 변경합니다.</summary>
|
|
public void SetPropertyReadOnly(string propertyId, bool isReadOnly) => PropertyList.SetPropertyReadOnly(propertyId, isReadOnly);
|
|
|
|
/// <summary>그룹 내 모든 아이템의 읽기 전용 상태를 일괄 변경합니다.</summary>
|
|
public void SetGroupReadOnly(string groupId, bool isReadOnly) => PropertyList.SetGroupReadOnly(groupId, isReadOnly);
|
|
#endregion
|
|
|
|
#region Public Methods - Utilities (PropertyList 위임)
|
|
/// <summary>모든 엔트리(그룹 + 아이템)를 제거하고 초기화합니다.</summary>
|
|
public new void Clear() => PropertyList.Clear();
|
|
|
|
/// <summary>현재 데이터를 기반으로 TreeView를 다시 빌드합니다.</summary>
|
|
public void Refresh() => PropertyList.Refresh();
|
|
#endregion
|
|
|
|
#region Public Methods - Window
|
|
/// <summary>윈도우를 표시합니다.</summary>
|
|
public void Show()
|
|
{
|
|
style.display = DisplayStyle.Flex;
|
|
}
|
|
|
|
/// <summary>윈도우를 숨깁니다.</summary>
|
|
public void Hide()
|
|
{
|
|
style.display = DisplayStyle.None;
|
|
}
|
|
|
|
/// <summary>윈도우의 위치를 설정합니다 (absolute 포지셔닝).</summary>
|
|
public void SetPosition(float x, float y)
|
|
{
|
|
style.left = x;
|
|
style.top = y;
|
|
}
|
|
|
|
/// <summary>윈도우의 크기를 설정합니다.</summary>
|
|
public void SetSize(float width, float height)
|
|
{
|
|
style.width = width;
|
|
style.height = height;
|
|
}
|
|
|
|
/// <summary>부모 요소 기준으로 윈도우를 중앙에 배치합니다.</summary>
|
|
public void CenterOnScreen()
|
|
{
|
|
schedule.Execute(() =>
|
|
{
|
|
var parent = this.parent;
|
|
if (parent == null) return;
|
|
|
|
float parentWidth = parent.resolvedStyle.width;
|
|
float parentHeight = parent.resolvedStyle.height;
|
|
float selfWidth = resolvedStyle.width;
|
|
float selfHeight = resolvedStyle.height;
|
|
|
|
style.left = (parentWidth - selfWidth) / 2;
|
|
style.top = (parentHeight - selfHeight) / 2;
|
|
});
|
|
}
|
|
#endregion
|
|
|
|
#region Private Methods - Tab Management
|
|
|
|
/// <summary>
|
|
/// 탭 버튼을 재구성합니다.
|
|
/// UTKComponentTabListWindow와 동일한 Button 기반 방식입니다.
|
|
/// </summary>
|
|
private void RebuildTabs()
|
|
{
|
|
if (_tabContainer == null) return;
|
|
|
|
// 기존 탭 버튼 제거
|
|
_tabContainer.Clear();
|
|
_tabButtons.Clear();
|
|
|
|
// "All" 탭 생성 (옵션)
|
|
if (_showAllTab)
|
|
{
|
|
var allTab = CreateTabButton("All", ALL_TAB_INDEX);
|
|
_tabButtons.Add(allTab);
|
|
_tabContainer.Add(allTab);
|
|
}
|
|
|
|
// 개별 탭 생성
|
|
for (int i = 0; i < _tabDataList.Count; i++)
|
|
{
|
|
var data = _tabDataList[i];
|
|
var tab = CreateTabButton(data.Name, i);
|
|
tab.SetEnabled(data.IsEnabled);
|
|
|
|
if (!string.IsNullOrEmpty(data.Tooltip))
|
|
tab.tooltip = data.Tooltip;
|
|
|
|
_tabButtons.Add(tab);
|
|
_tabContainer.Add(tab);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 탭 버튼을 생성합니다.
|
|
/// </summary>
|
|
private Button CreateTabButton(string label, int index)
|
|
{
|
|
var button = new Button(() => SelectTab(index))
|
|
{
|
|
text = label
|
|
};
|
|
button.AddToClassList("tab-button");
|
|
return button;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 탭 선택 스타일을 업데이트합니다.
|
|
/// </summary>
|
|
private void UpdateTabStyles()
|
|
{
|
|
for (int i = 0; i < _tabButtons.Count; i++)
|
|
{
|
|
var btn = _tabButtons[i];
|
|
int tabIndex = _showAllTab ? (i == 0 ? ALL_TAB_INDEX : i - 1) : i;
|
|
|
|
if (tabIndex == _selectedTabIndex)
|
|
btn.AddToClassList("tab-button-selected");
|
|
else
|
|
btn.RemoveFromClassList("tab-button-selected");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 탭에 해당하는 데이터를 UTKPropertyList에 로드합니다.
|
|
/// </summary>
|
|
private void LoadDataForTab(int tabIndex)
|
|
{
|
|
if (_propertyList == null) return;
|
|
|
|
if (tabIndex == ALL_TAB_INDEX)
|
|
LoadAllTabData();
|
|
else if (tabIndex >= 0 && tabIndex < _tabDataList.Count)
|
|
LoadSingleTabData(_tabDataList[tabIndex]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 단일 탭의 데이터를 UTKPropertyList에 로드합니다.
|
|
/// </summary>
|
|
private void LoadSingleTabData(TabPropertyData tabData)
|
|
{
|
|
if (_propertyList == null) return;
|
|
|
|
switch (tabData.DataType)
|
|
{
|
|
case TabPropertyDataType.Flat:
|
|
var flatItems = tabData.GetFlatData();
|
|
if (flatItems != null)
|
|
_propertyList.LoadProperties(flatItems);
|
|
else
|
|
_propertyList.Clear();
|
|
break;
|
|
|
|
case TabPropertyDataType.Grouped:
|
|
var groupedItems = tabData.GetGroupedData();
|
|
if (groupedItems != null)
|
|
_propertyList.LoadGroupedProperties(groupedItems);
|
|
else
|
|
_propertyList.Clear();
|
|
break;
|
|
|
|
case TabPropertyDataType.Mixed:
|
|
var mixedItems = tabData.GetMixedData();
|
|
if (mixedItems != null)
|
|
_propertyList.LoadMixedProperties(mixedItems);
|
|
else
|
|
_propertyList.Clear();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 탭의 데이터를 병합하여 UTKPropertyList에 로드합니다.
|
|
/// </summary>
|
|
private void LoadAllTabData()
|
|
{
|
|
if (_propertyList == null) return;
|
|
|
|
var allEntries = new List<IUTKPropertyEntry>();
|
|
|
|
foreach (var tabData in _tabDataList)
|
|
{
|
|
switch (tabData.DataType)
|
|
{
|
|
case TabPropertyDataType.Flat:
|
|
var flatItems = tabData.GetFlatData();
|
|
if (flatItems != null)
|
|
{
|
|
foreach (var item in flatItems)
|
|
allEntries.Add(item);
|
|
}
|
|
break;
|
|
|
|
case TabPropertyDataType.Grouped:
|
|
var groupedItems = tabData.GetGroupedData();
|
|
if (groupedItems != null)
|
|
{
|
|
foreach (var group in groupedItems)
|
|
allEntries.Add(group);
|
|
}
|
|
break;
|
|
|
|
case TabPropertyDataType.Mixed:
|
|
var mixedItems = tabData.GetMixedData();
|
|
if (mixedItems != null)
|
|
allEntries.AddRange(mixedItems);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (allEntries.Count > 0)
|
|
_propertyList.LoadMixedProperties(allEntries);
|
|
else
|
|
_propertyList.Clear();
|
|
}
|
|
#endregion
|
|
|
|
#region Private Methods - Search Persistence
|
|
|
|
/// <summary>현재 탭의 검색어를 저장합니다.</summary>
|
|
private void SaveCurrentSearchQuery()
|
|
{
|
|
if (_propertyList == null) return;
|
|
|
|
var currentQuery = _propertyList.SearchQuery;
|
|
if (!string.IsNullOrEmpty(currentQuery))
|
|
_tabSearchQueries[_selectedTabIndex] = currentQuery;
|
|
else
|
|
_tabSearchQueries.Remove(_selectedTabIndex);
|
|
}
|
|
|
|
/// <summary>지정된 탭의 저장된 검색어를 복원합니다.</summary>
|
|
private void RestoreSearchQuery(int tabIndex)
|
|
{
|
|
if (_propertyList == null) return;
|
|
|
|
if (_tabSearchQueries.TryGetValue(tabIndex, out var savedQuery) && !string.IsNullOrEmpty(savedQuery))
|
|
_propertyList.ApplySearch(savedQuery);
|
|
else
|
|
_propertyList.ApplySearch(string.Empty);
|
|
}
|
|
#endregion
|
|
|
|
#region Dragging
|
|
private void OnHeaderPointerDown(PointerDownEvent evt)
|
|
{
|
|
if (evt.button != 0) return;
|
|
|
|
_isDragging = true;
|
|
_dragStartPosition = new Vector2(resolvedStyle.left, resolvedStyle.top);
|
|
_dragStartMousePosition = evt.position;
|
|
_header?.CapturePointer(evt.pointerId);
|
|
}
|
|
|
|
private void OnHeaderPointerMove(PointerMoveEvent evt)
|
|
{
|
|
if (!_isDragging) return;
|
|
|
|
Vector2 delta = (Vector2)evt.position - _dragStartMousePosition;
|
|
style.left = _dragStartPosition.x + delta.x;
|
|
style.top = _dragStartPosition.y + delta.y;
|
|
}
|
|
|
|
private void OnHeaderPointerUp(PointerUpEvent evt)
|
|
{
|
|
if (!_isDragging) return;
|
|
|
|
_isDragging = false;
|
|
_header?.ReleasePointer(evt.pointerId);
|
|
}
|
|
#endregion
|
|
|
|
#region 테마 (Theme)
|
|
|
|
private void SubscribeToThemeChanges()
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
|
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
|
|
// 테마 변경 이벤트 해제
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
|
|
// 드래그 이벤트 해제
|
|
if (_header != null)
|
|
{
|
|
_header.UnregisterCallback<PointerDownEvent>(OnHeaderPointerDown);
|
|
_header.UnregisterCallback<PointerMoveEvent>(OnHeaderPointerMove);
|
|
_header.UnregisterCallback<PointerUpEvent>(OnHeaderPointerUp);
|
|
}
|
|
|
|
// PropertyList 정리
|
|
_propertyList?.Dispose();
|
|
_propertyList = null;
|
|
|
|
// 이벤트 정리
|
|
OnCloseClicked = null;
|
|
OnTabChanged = null;
|
|
|
|
// 데이터 정리
|
|
_tabDataList.Clear();
|
|
_tabSearchQueries.Clear();
|
|
_tabButtons.Clear();
|
|
|
|
// UI 참조 정리
|
|
_header = null;
|
|
_titleLabel = null;
|
|
_closeButton = null;
|
|
_tabContainer = null;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|