UTKPropertyItem 개선
This commit is contained in:
365
Assets/Scripts/UVC/UIToolkit/Window/UTKPropertyListWindow.cs
Normal file
365
Assets/Scripts/UVC/UIToolkit/Window/UTKPropertyListWindow.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// UTKPropertyList를 감싸는 윈도우 래퍼
|
||||
/// 헤더, 타이틀, 닫기 버튼 등 윈도우 프레임 제공
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKPropertyListWindow : VisualElement, IDisposable
|
||||
{
|
||||
#region Constants
|
||||
private const string UXML_PATH = "UIToolkit/Window/UTKPropertyListWindow";
|
||||
private const string USS_PATH = "UIToolkit/Window/UTKPropertyListWindowUss";
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
private bool _disposed;
|
||||
private UTKPropertyList? _propertyList;
|
||||
private VisualElement? _header;
|
||||
private UTKLabel? _titleLabel;
|
||||
private UTKButton? _closeButton;
|
||||
private string _title = "Properties";
|
||||
private bool _showCloseButton = false;
|
||||
private bool _isDragging;
|
||||
private Vector2 _dragStartPosition;
|
||||
private Vector2 _dragStartMousePosition;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>윈도우 타이틀</summary>
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set
|
||||
{
|
||||
_title = value;
|
||||
if (_titleLabel != null)
|
||||
{
|
||||
_titleLabel.Text = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>닫기 버튼 표시 여부</summary>
|
||||
public bool ShowCloseButton
|
||||
{
|
||||
get => _showCloseButton;
|
||||
set
|
||||
{
|
||||
_showCloseButton = value;
|
||||
if (_closeButton != null)
|
||||
{
|
||||
_closeButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>내부 PropertyList 접근</summary>
|
||||
public UTKPropertyList PropertyList => _propertyList ??= new UTKPropertyList();
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
/// <summary>닫기 버튼 클릭 이벤트</summary>
|
||||
public event Action? OnCloseClicked;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public UTKPropertyListWindow()
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
SubscribeToThemeChanges();
|
||||
|
||||
var styleSheet = Resources.Load<StyleSheet>(USS_PATH);
|
||||
if (styleSheet != null)
|
||||
{
|
||||
styleSheets.Add(styleSheet);
|
||||
}
|
||||
|
||||
CreateUI();
|
||||
}
|
||||
|
||||
public UTKPropertyListWindow(string title) : this()
|
||||
{
|
||||
Title = title;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region UI Creation
|
||||
private void CreateUI()
|
||||
{
|
||||
AddToClassList("utk-property-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 windowRoot = root.Q<VisualElement>("window-root");
|
||||
|
||||
if (windowRoot != null)
|
||||
{
|
||||
// windowRoot의 자식들을 현재 요소로 이동
|
||||
foreach (var child in windowRoot.Children().ToArray())
|
||||
{
|
||||
Add(child);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// window-root가 없으면 전체 추가
|
||||
Add(root);
|
||||
}
|
||||
|
||||
// 요소 참조 가져오기
|
||||
_header = this.Q<VisualElement>("header");
|
||||
_titleLabel = this.Q<UTKLabel>("title");
|
||||
_closeButton = this.Q<UTKButton>("close-btn");
|
||||
_propertyList = this.Q<UTKPropertyList>("content");
|
||||
|
||||
// 타이틀 설정
|
||||
if (_titleLabel != null)
|
||||
{
|
||||
_titleLabel.Text = _title;
|
||||
_titleLabel.Size = UTKLabel.LabelSize.Label3;
|
||||
}
|
||||
|
||||
// 닫기 버튼 설정
|
||||
if (_closeButton != null)
|
||||
{
|
||||
_closeButton.SetMaterialIcon(UTKMaterialIcons.Close, 14);
|
||||
_closeButton.IconOnly = true;
|
||||
_closeButton.OnClicked += () => OnCloseClicked?.Invoke();
|
||||
_closeButton.style.display = _showCloseButton ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
// PropertyList가 없으면 생성
|
||||
if (_propertyList == null)
|
||||
{
|
||||
_propertyList = new UTKPropertyList();
|
||||
_propertyList.name = "content";
|
||||
_propertyList.AddToClassList("utk-property-window__content");
|
||||
Add(_propertyList);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateUIFallback()
|
||||
{
|
||||
// 헤더
|
||||
_header = new VisualElement();
|
||||
_header.name = "header";
|
||||
_header.AddToClassList("utk-property-window__header");
|
||||
|
||||
_titleLabel = new UTKLabel(_title, UTKLabel.LabelSize.Label3);
|
||||
_titleLabel.name = "title";
|
||||
_titleLabel.AddToClassList("utk-property-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-window__close-btn");
|
||||
_closeButton.OnClicked += () => OnCloseClicked?.Invoke();
|
||||
_header.Add(_closeButton);
|
||||
|
||||
Add(_header);
|
||||
|
||||
// PropertyList
|
||||
_propertyList = new UTKPropertyList();
|
||||
_propertyList.AddToClassList("utk-property-window__content");
|
||||
Add(_propertyList);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods - PropertyList 위임
|
||||
public void LoadProperties(List<IUTKPropertyItem> items) => PropertyList.LoadProperties(items);
|
||||
public void LoadGroupedProperties(List<IUTKPropertyGroup> groups) => PropertyList.LoadGroupedProperties(groups);
|
||||
public void LoadMixedProperties(List<IUTKPropertyEntry> entries) => PropertyList.LoadMixedProperties(entries);
|
||||
|
||||
public void AddGroup(IUTKPropertyGroup group) => PropertyList.AddGroup(group);
|
||||
public void RemoveGroup(string groupId) => PropertyList.RemoveGroup(groupId);
|
||||
public IUTKPropertyGroup? GetGroup(string groupId) => PropertyList.GetGroup(groupId);
|
||||
public void SetGroupExpanded(string groupId, bool expanded) => PropertyList.SetGroupExpanded(groupId, expanded);
|
||||
public void ToggleGroupExpanded(string groupId) => PropertyList.ToggleGroupExpanded(groupId);
|
||||
|
||||
public void AddProperty(IUTKPropertyItem item) => PropertyList.AddProperty(item);
|
||||
public void AddPropertyToGroup(string groupId, IUTKPropertyItem item) => PropertyList.AddPropertyToGroup(groupId, item);
|
||||
public void RemoveProperty(string itemId) => PropertyList.RemoveProperty(itemId);
|
||||
public IUTKPropertyItem? GetProperty(string itemId) => PropertyList.GetProperty(itemId);
|
||||
|
||||
public void UpdatePropertyValue(string propertyId, object newValue) => PropertyList.UpdatePropertyValue(propertyId, newValue);
|
||||
public void SetPropertyValue(string propertyId, object value) => PropertyList.SetPropertyValue(propertyId, value);
|
||||
|
||||
public void SetPropertyVisibility(string propertyId, bool visible) => PropertyList.SetPropertyVisibility(propertyId, visible);
|
||||
public void SetGroupVisibility(string groupId, bool visible) => PropertyList.SetGroupVisibility(groupId, visible);
|
||||
public void SetPropertyReadOnly(string propertyId, bool isReadOnly) => PropertyList.SetPropertyReadOnly(propertyId, isReadOnly);
|
||||
public void SetGroupReadOnly(string groupId, bool isReadOnly) => PropertyList.SetGroupReadOnly(groupId, isReadOnly);
|
||||
|
||||
public void Clear() => PropertyList.Clear();
|
||||
public void Refresh() => PropertyList.Refresh();
|
||||
#endregion
|
||||
|
||||
#region Public Methods - Window
|
||||
public void Show()
|
||||
{
|
||||
style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
public void SetPosition(float x, float y)
|
||||
{
|
||||
style.left = x;
|
||||
style.top = y;
|
||||
}
|
||||
|
||||
public void SetSize(float width, float height)
|
||||
{
|
||||
style.width = width;
|
||||
style.height = height;
|
||||
}
|
||||
|
||||
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 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<DetachFromPanelEvent>(_ =>
|
||||
{
|
||||
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;
|
||||
|
||||
// 드래그 이벤트 해제
|
||||
if (_header != null)
|
||||
{
|
||||
_header.UnregisterCallback<PointerDownEvent>(OnHeaderPointerDown);
|
||||
_header.UnregisterCallback<PointerMoveEvent>(OnHeaderPointerMove);
|
||||
_header.UnregisterCallback<PointerUpEvent>(OnHeaderPointerUp);
|
||||
}
|
||||
|
||||
// PropertyList 정리
|
||||
_propertyList?.Dispose();
|
||||
_propertyList = null;
|
||||
|
||||
// 이벤트 정리
|
||||
OnCloseClicked = null;
|
||||
|
||||
// UI 참조 정리
|
||||
_header = null;
|
||||
_titleLabel = null;
|
||||
_closeButton = null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94e88e474e6d28b46ab4b5aca56ff78f
|
||||
@@ -162,7 +162,7 @@ namespace UVC.UIToolkit
|
||||
|
||||
#region UI 컴포넌트 참조 (UI Component References)
|
||||
/// <summary>검색어 입력 필드</summary>
|
||||
private TextField? _searchField;
|
||||
private UTKInputField? _searchField;
|
||||
|
||||
/// <summary>Unity UI Toolkit의 TreeView 컴포넌트</summary>
|
||||
private TreeView? _treeView;
|
||||
@@ -303,7 +303,7 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
|
||||
// 3. 자식 요소 참조 획득 (UXML의 name 속성으로 찾음)
|
||||
_searchField = this.Q<TextField>("search-field");
|
||||
_searchField = this.Q<UTKInputField>("search-field");
|
||||
_treeView = this.Q<TreeView>("main-tree-view");
|
||||
_titleLabel = this.Q<Label>("title");
|
||||
_closeButton = this.Q<UTKButton>("close-btn");
|
||||
@@ -335,8 +335,7 @@ namespace UVC.UIToolkit
|
||||
// 검색창 이벤트: Enter 키를 눌렀을 때 또는 포커스를 잃었을 때 필터링 실행
|
||||
if (_searchField != null)
|
||||
{
|
||||
_searchField.RegisterCallback<KeyDownEvent>(OnSearchFieldKeyDown);
|
||||
_searchField.RegisterCallback<FocusOutEvent>(OnSearchFieldFocusOut);
|
||||
_searchField.OnSubmit += OnSearch;
|
||||
}
|
||||
|
||||
// TreeView 설정
|
||||
@@ -349,7 +348,7 @@ namespace UVC.UIToolkit
|
||||
_treeView.selectionChanged += OnTreeViewSelectionChanged;
|
||||
_treeView.itemsChosen += OnTreeViewItemsChosen;
|
||||
_treeView.itemExpandedChanged += OnTreeViewItemExpandedChanged;
|
||||
_treeView.RegisterCallback<KeyDownEvent>(OnTreeViewKeyDown);
|
||||
_treeView.RegisterCallback<KeyDownEvent>(OnTreeViewKeyDown, TrickleDown.TrickleDown);
|
||||
}
|
||||
|
||||
// 닫기 버튼: 트리 리스트를 숨기고 이벤트 발생
|
||||
@@ -1248,28 +1247,7 @@ namespace UVC.UIToolkit
|
||||
#endregion
|
||||
|
||||
#region 검색 기능 (Search Functionality)
|
||||
/// <summary>
|
||||
/// 검색 필드에서 Enter 키를 눌렀을 때 검색을 실행합니다.
|
||||
/// </summary>
|
||||
/// <param name="evt">키 입력 이벤트</param>
|
||||
private void OnSearchFieldKeyDown(KeyDownEvent evt)
|
||||
{
|
||||
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
|
||||
{
|
||||
OnSearch(_searchField?.value ?? string.Empty);
|
||||
evt.StopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 검색 필드가 포커스를 잃었을 때 검색을 실행합니다.
|
||||
/// </summary>
|
||||
/// <param name="evt">포커스 아웃 이벤트</param>
|
||||
private void OnSearchFieldFocusOut(FocusOutEvent evt)
|
||||
{
|
||||
OnSearch(_searchField?.value ?? string.Empty);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 검색어에 따라 트리를 필터링합니다.
|
||||
/// 검색어가 비어있으면 원본 데이터로 복원됩니다.
|
||||
@@ -1428,8 +1406,7 @@ namespace UVC.UIToolkit
|
||||
// 검색 필드 이벤트 해제
|
||||
if (_searchField != null)
|
||||
{
|
||||
_searchField.UnregisterCallback<KeyDownEvent>(OnSearchFieldKeyDown);
|
||||
_searchField.UnregisterCallback<FocusOutEvent>(OnSearchFieldFocusOut);
|
||||
_searchField.OnSubmit -= OnSearch;
|
||||
}
|
||||
|
||||
// TreeView 이벤트 핸들러 해제
|
||||
@@ -1438,7 +1415,7 @@ namespace UVC.UIToolkit
|
||||
_treeView.selectionChanged -= OnTreeViewSelectionChanged;
|
||||
_treeView.itemsChosen -= OnTreeViewItemsChosen;
|
||||
_treeView.itemExpandedChanged -= OnTreeViewItemExpandedChanged;
|
||||
_treeView.UnregisterCallback<KeyDownEvent>(OnTreeViewKeyDown);
|
||||
_treeView.UnregisterCallback<KeyDownEvent>(OnTreeViewKeyDown, TrickleDown.TrickleDown);
|
||||
_treeView.bindItem = null;
|
||||
_treeView.makeItem = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user