#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
///
/// 패널 컨테이너 컴포넌트.
/// 콘텐츠를 그룹화하고 시각적으로 구분하는 섹션 컨테이너입니다.
///
///
/// Panel(패널)이란?
///
/// Panel은 관련 UI 요소들을 묶어 하나의 섹션으로 표시하는 컨테이너입니다.
/// 설정 페이지, 폼, 대시보드 등에서 내용을 구분하고 정리하는 데 사용됩니다.
/// 선택적으로 접기/펼치기 기능을 제공합니다.
///
///
/// Panel vs Card 차이:
///
/// - Panel - 기능적 그룹화, 접기/펼치기, 넓은 영역
/// - Card - 시각적 그룹화, 독립적 아이템 단위, 클릭 가능
///
///
/// 패널 스타일 (PanelVariant):
///
/// - Default - 기본 스타일
/// - Elevated - 그림자가 있는 떠 있는 스타일
/// - Outlined - 테두리만 있는 스타일
/// - Flat - 배경/테두리 없는 평면 스타일
///
///
/// 패널 구성 요소:
///
/// - 헤더 - 제목과 헤더 액션 버튼
/// - 콘텐츠 - 본문 내용 (UXML 자식 또는 Add()로 추가)
/// - 푸터 - 하단 버튼/정보 영역 (ShowFooter로 표시)
///
///
/// 접기/펼치기 기능:
///
/// IsCollapsible = true로 설정하면 헤더 클릭으로 패널을 접거나 펼 수 있습니다.
/// IsCollapsed 속성으로 현재 접힘 상태를 확인/변경할 수 있습니다.
///
///
/// 실제 활용 예시:
///
/// - 설정 페이지 - "일반", "고급", "보안" 등 설정 그룹
/// - 인스펙터 - Transform, Renderer 등 컴포넌트 섹션
/// - 대시보드 - 통계, 차트, 목록 등 위젯 영역
/// - 폼 - 개인정보, 결제정보 등 입력 그룹
///
///
///
/// C# 코드에서 사용:
///
/// // 기본 패널
/// 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;
///
/// UXML에서 사용:
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
[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
/// 접힘 상태 변경 이벤트
public event Action? OnCollapsedChanged;
#endregion
#region Properties
/// 패널 제목
[UxmlAttribute("title")]
public string Title
{
get => _title;
set
{
_title = value;
if (_titleLabel != null) _titleLabel.text = value;
}
}
/// 헤더 표시 여부
[UxmlAttribute("show-header")]
public bool ShowHeader
{
get => _showHeader;
set
{
_showHeader = value;
if (_header != null)
{
_header.style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
}
}
}
/// 푸터 표시 여부
[UxmlAttribute("show-footer")]
public bool ShowFooter
{
get => _showFooter;
set
{
_showFooter = value;
if (_footer != null)
{
_footer.style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
}
}
}
/// 패널 스타일
[UxmlAttribute("variant")]
public PanelVariant Variant
{
get => _variant;
set
{
_variant = value;
UpdateVariant();
}
}
/// 접기 가능 여부
[UxmlAttribute("is-collapsible")]
public bool IsCollapsible
{
get => _isCollapsible;
set
{
_isCollapsible = value;
EnableInClassList("utk-panel--collapsible", value);
}
}
/// 접힘 상태
[UxmlAttribute("is-collapsed")]
public bool IsCollapsed
{
get => _isCollapsed;
set => SetCollapsed(value, true);
}
///
/// UXML에서 자식 요소가 추가될 컨테이너.
/// 이 속성을 오버라이드하여 자식 요소들이 _content 영역에 추가되도록 합니다.
///
public override VisualElement contentContainer => _content ?? this;
/// 푸터 영역
public VisualElement? FooterContainer => _footer;
/// 헤더 액션 영역
public VisualElement? HeaderActionsContainer => _headerActions;
#endregion
#region Enums
public enum PanelVariant
{
Default,
Elevated,
Outlined,
Flat
}
#endregion
#region Constructor
public UTKPanel()
{
// 1. 먼저 테마 스타일시트 적용 (변수 정의)
UTKThemeManager.Instance.ApplyThemeToElement(this);
// 2. 컴포넌트 USS 적용 (변수 사용)
var uss = Resources.Load(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
CreateUI();
SetupEvents();
SubscribeToThemeChanges();
// UXML에서 로드될 때 속성이 설정된 후 UI 갱신
RegisterCallback(OnAttachToPanel);
}
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");
_content.RegisterCallback(OnContentGeometryChanged);
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 OnContentGeometryChanged(GeometryChangedEvent evt)
{
// Content 영역 내 Label 요소에 스타일 클래스 추가
ApplyContentLabelStyles();
}
private void ApplyContentLabelStyles()
{
if (_content == null) return;
foreach (var child in _content.Children())
{
if (child is Label label && !label.ClassListContains("utk-panel__content-label"))
{
label.AddToClassList("utk-panel__content-label");
}
}
}
private void SetupEvents()
{
_header?.RegisterCallback(OnHeaderClick);
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback(OnAttachToPanelForTheme);
RegisterCallback(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 Event Handlers
private void OnAttachToPanel(AttachToPanelEvent evt)
{
// UXML 속성이 설정된 후 한 번만 UI 갱신
UnregisterCallback(OnAttachToPanel);
// Title 적용
if (_titleLabel != null)
{
_titleLabel.text = _title;
}
// Header 표시 상태 적용
if (_header != null)
{
_header.style.display = _showHeader ? DisplayStyle.Flex : DisplayStyle.None;
}
// Footer 표시 상태 적용
if (_footer != null)
{
_footer.style.display = _showFooter ? DisplayStyle.Flex : DisplayStyle.None;
}
// Variant 적용
UpdateVariant();
// Collapsible 상태 적용
EnableInClassList("utk-panel--collapsible", _isCollapsible);
// Collapsed 상태 적용
if (_isCollapsed)
{
EnableInClassList("utk-panel--collapsed", true);
if (_content != null)
{
_content.style.display = DisplayStyle.None;
}
}
// UXML 자식 요소 스타일 적용 (지연 실행)
schedule.Execute(ApplyContentLabelStyles);
}
private void OnHeaderClick(ClickEvent evt)
{
if (!_isCollapsible) return;
SetCollapsed(!_isCollapsed, true);
evt.StopPropagation();
}
#endregion
#region Methods
///
/// 접힘 상태 설정
///
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);
}
///
/// 헤더 액션 추가
///
public void AddHeaderAction(VisualElement element)
{
_headerActions?.Add(element);
}
///
/// 푸터에 요소 추가
///
public void AddToFooter(VisualElement element)
{
_footer?.Add(element);
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
UnregisterCallback(OnAttachToPanelForTheme);
UnregisterCallback(OnDetachFromPanelForTheme);
OnCollapsedChanged = null;
}
#endregion
}
}