Files
XRLib/Assets/Scripts/UVC/UIToolkit/Modal/UTKPanel.cs

338 lines
9.4 KiB
C#
Raw Normal View History

2026-01-08 20:15:57 +09:00
#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 패널 컨테이너 컴포넌트.
/// 콘텐츠를 그룹화하고 시각적으로 구분합니다.
/// </summary>
2026-01-13 20:39:45 +09:00
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 패널
/// var panel = new UTKPanel();
/// panel.Title = "설정";
/// panel.AddContent(new Label("패널 내용"));
///
/// // 접을 수 있는 패널
/// panel.IsCollapsible = true;
/// panel.IsCollapsed = false;
///
/// // 헤더 액션 버튼 추가
/// panel.AddHeaderAction(UTKMaterialIcons.Settings, () => Debug.Log("설정"));
///
/// // 푸터 표시
/// panel.ShowFooter = true;
/// panel.AddFooterContent(new Label("푸터 내용"));
///
/// // 변형 스타일
/// panel.Variant = UTKPanel.PanelVariant.Elevated;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 패널 -->
/// <utk:UTKPanel Title="제목">
/// <ui:Label text="패널 내용" />
/// </utk:UTKPanel>
///
/// <!-- 접을 수 있는 패널 -->
/// <utk:UTKPanel Title="고급 설정" IsCollapsible="true">
/// <ui:Label text="내용" />
/// </utk:UTKPanel>
///
/// <!-- 외곽선 스타일 -->
/// <utk:UTKPanel Title="외곽선" Variant="Outlined" />
/// </ui:UXML>
/// </code>
/// </example>
2026-01-08 20:15:57 +09:00
[UxmlElement]
public partial class UTKPanel : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Modal/UTKPanel";
#endregion
#region Fields
private bool _disposed;
private VisualElement? _header;
private Label? _titleLabel;
private VisualElement? _headerActions;
private VisualElement? _content;
private VisualElement? _footer;
private string _title = "";
private bool _showHeader = true;
private bool _showFooter;
private PanelVariant _variant = PanelVariant.Default;
private bool _isCollapsible;
private bool _isCollapsed;
#endregion
#region Events
/// <summary>접힘 상태 변경 이벤트</summary>
public event Action<bool>? OnCollapsedChanged;
#endregion
#region Properties
/// <summary>패널 제목</summary>
[UxmlAttribute]
public string Title
{
get => _title;
set
{
_title = value;
if (_titleLabel != null) _titleLabel.text = value;
}
}
/// <summary>헤더 표시 여부</summary>
[UxmlAttribute]
public bool ShowHeader
{
get => _showHeader;
set
{
_showHeader = value;
if (_header != null)
{
_header.style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
}
}
}
/// <summary>푸터 표시 여부</summary>
[UxmlAttribute]
public bool ShowFooter
{
get => _showFooter;
set
{
_showFooter = value;
if (_footer != null)
{
_footer.style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
}
}
}
/// <summary>패널 스타일</summary>
[UxmlAttribute]
public PanelVariant Variant
{
get => _variant;
set
{
_variant = value;
UpdateVariant();
}
}
/// <summary>접기 가능 여부</summary>
[UxmlAttribute]
public bool IsCollapsible
{
get => _isCollapsible;
set
{
_isCollapsible = value;
EnableInClassList("utk-panel--collapsible", value);
}
}
/// <summary>접힘 상태</summary>
[UxmlAttribute]
public bool IsCollapsed
{
get => _isCollapsed;
set => SetCollapsed(value, true);
}
/// <summary>콘텐츠 영역</summary>
public VisualElement? ContentContainer => _content;
/// <summary>푸터 영역</summary>
public VisualElement? FooterContainer => _footer;
/// <summary>헤더 액션 영역</summary>
public VisualElement? HeaderActionsContainer => _headerActions;
#endregion
#region Enums
public enum PanelVariant
{
Default,
Elevated,
Outlined,
Flat
}
#endregion
#region Constructor
public UTKPanel()
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
CreateUI();
SetupEvents();
SubscribeToThemeChanges();
}
public UTKPanel(string title) : this()
{
Title = title;
}
#endregion
#region UI Creation
private void CreateUI()
{
AddToClassList("utk-panel");
// Header
_header = new VisualElement { name = "header" };
_header.AddToClassList("utk-panel__header");
hierarchy.Add(_header);
_titleLabel = new Label { name = "title" };
_titleLabel.AddToClassList("utk-panel__title");
_header.Add(_titleLabel);
_headerActions = new VisualElement { name = "header-actions" };
_headerActions.AddToClassList("utk-panel__header-actions");
_header.Add(_headerActions);
// Content
_content = new VisualElement { name = "content" };
_content.AddToClassList("utk-panel__content");
hierarchy.Add(_content);
// Footer
_footer = new VisualElement { name = "footer" };
_footer.AddToClassList("utk-panel__footer");
_footer.style.display = DisplayStyle.None;
hierarchy.Add(_footer);
UpdateVariant();
}
private void SetupEvents()
{
_header?.RegisterCallback<ClickEvent>(OnHeaderClick);
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region Event Handlers
private void OnHeaderClick(ClickEvent evt)
{
if (!_isCollapsible) return;
SetCollapsed(!_isCollapsed, true);
evt.StopPropagation();
}
#endregion
#region Methods
/// <summary>
/// 접힘 상태 설정
/// </summary>
public void SetCollapsed(bool collapsed, bool notify)
{
if (_isCollapsed == collapsed) return;
_isCollapsed = collapsed;
EnableInClassList("utk-panel--collapsed", collapsed);
if (_content != null)
{
_content.style.display = collapsed ? DisplayStyle.None : DisplayStyle.Flex;
}
if (_footer != null && _showFooter)
{
_footer.style.display = collapsed ? DisplayStyle.None : DisplayStyle.Flex;
}
if (notify)
{
OnCollapsedChanged?.Invoke(collapsed);
}
}
private void UpdateVariant()
{
RemoveFromClassList("utk-panel--default");
RemoveFromClassList("utk-panel--elevated");
RemoveFromClassList("utk-panel--outlined");
RemoveFromClassList("utk-panel--flat");
var variantClass = _variant switch
{
PanelVariant.Elevated => "utk-panel--elevated",
PanelVariant.Outlined => "utk-panel--outlined",
PanelVariant.Flat => "utk-panel--flat",
_ => "utk-panel--default"
};
AddToClassList(variantClass);
}
/// <summary>
/// 콘텐츠 추가
/// </summary>
public new void Add(VisualElement element)
{
_content?.Add(element);
}
/// <summary>
/// 헤더 액션 추가
/// </summary>
public void AddHeaderAction(VisualElement element)
{
_headerActions?.Add(element);
}
/// <summary>
/// 푸터에 요소 추가
/// </summary>
public void AddToFooter(VisualElement element)
{
_footer?.Add(element);
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
OnCollapsedChanged = null;
}
#endregion
}
}