#nullable enable using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit.Property { /// /// UTKPropertyList를 감싸는 윈도우 래퍼 /// 헤더, 타이틀, 닫기 버튼 등 윈도우 프레임 제공 /// [UxmlElement] public partial class UTKPropertyWindow : VisualElement, IDisposable { #region Constants private const string USS_PATH = "UIToolkit/Property/UTKPropertyWindow"; #endregion #region Fields private bool _disposed; private UTKPropertyList? _propertyList; private VisualElement? _header; private Label? _titleLabel; private Button? _closeButton; private string _title = "Properties"; private bool _showCloseButton = true; private bool _isDragging; private Vector2 _dragStartPosition; private Vector2 _dragStartMousePosition; #endregion #region Properties /// 윈도우 타이틀 public string Title { get => _title; set { _title = value; if (_titleLabel != null) { _titleLabel.text = value; } } } /// 닫기 버튼 표시 여부 public bool ShowCloseButton { get => _showCloseButton; set { _showCloseButton = value; if (_closeButton != null) { _closeButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None; } } } /// 내부 PropertyList 접근 public UTKPropertyList PropertyList => _propertyList ??= new UTKPropertyList(); #endregion #region Events /// 닫기 버튼 클릭 이벤트 public event Action? OnCloseClicked; /// 속성 값 변경 이벤트 (PropertyList 위임) public event Action? OnPropertyValueChanged { add => PropertyList.OnPropertyValueChanged += value; remove => PropertyList.OnPropertyValueChanged -= value; } /// 그룹 펼침/접힘 이벤트 (PropertyList 위임) public event Action? OnGroupExpandedChanged { add => PropertyList.OnGroupExpandedChanged += value; remove => PropertyList.OnGroupExpandedChanged -= value; } /// 속성 클릭 이벤트 (PropertyList 위임) public event Action? OnPropertyClicked { add => PropertyList.OnPropertyClicked += value; remove => PropertyList.OnPropertyClicked -= value; } #endregion #region Constructor public UTKPropertyWindow() { UTKThemeManager.Instance.ApplyThemeToElement(this); var styleSheet = Resources.Load(USS_PATH); if (styleSheet != null) { styleSheets.Add(styleSheet); } CreateUI(); } public UTKPropertyWindow(string title) : this() { Title = title; } #endregion #region UI Creation private void CreateUI() { AddToClassList("utk-property-window"); // 헤더 _header = new VisualElement(); _header.name = "header"; _header.AddToClassList("utk-property-window__header"); _titleLabel = new Label(_title); _titleLabel.name = "title"; _titleLabel.AddToClassList("utk-property-window__title"); _header.Add(_titleLabel); _closeButton = new Button { text = "✕" }; _closeButton.name = "close-btn"; _closeButton.AddToClassList("utk-property-window__close-btn"); _closeButton.clicked += () => OnCloseClicked?.Invoke(); _header.Add(_closeButton); Add(_header); // 드래그 이벤트 _header.RegisterCallback(OnHeaderPointerDown); _header.RegisterCallback(OnHeaderPointerMove); _header.RegisterCallback(OnHeaderPointerUp); // PropertyList _propertyList = new UTKPropertyList(); _propertyList.AddToClassList("utk-property-window__content"); Add(_propertyList); } #endregion #region Public Methods - PropertyList 위임 public void LoadProperties(List items) => PropertyList.LoadProperties(items); public void LoadGroupedProperties(List groups) => PropertyList.LoadGroupedProperties(groups); public void LoadMixedProperties(List 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 IDisposable public void Dispose() { if (_disposed) return; _disposed = true; // 드래그 이벤트 해제 if (_header != null) { _header.UnregisterCallback(OnHeaderPointerDown); _header.UnregisterCallback(OnHeaderPointerMove); _header.UnregisterCallback(OnHeaderPointerUp); } // PropertyList 정리 _propertyList?.Dispose(); _propertyList = null; // 이벤트 정리 OnCloseClicked = null; // UI 참조 정리 _header = null; _titleLabel = null; _closeButton = null; } #endregion } }