#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
///
/// 모달 창 컴포넌트.
/// 사용자 정의 콘텐츠를 포함할 수 있는 모달 대화상자입니다.
///
///
/// C# 코드에서 사용:
///
/// // 모달 생성 및 표시
/// var modal = new UTKModal();
/// modal.Title = "설정";
/// modal.Size = UTKModal.ModalSize.Medium;
/// modal.OnClosed += () => Debug.Log("모달 닫힘");
///
/// // 콘텐츠 추가
/// var content = new Label("모달 내용");
/// modal.AddContent(content);
///
/// // 푸터 버튼 추가
/// modal.AddFooterButton("확인", UTKButton.ButtonVariant.Primary, () => modal.Close());
/// modal.AddFooterButton("취소", UTKButton.ButtonVariant.Normal, () => modal.Close());
///
/// // 표시
/// modal.Show(rootElement);
///
/// UXML에서 사용:
///
///
///
///
///
///
///
///
[UxmlElement]
public partial class UTKModal : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Modal/UTKModal";
#endregion
#region Fields
private bool _disposed;
private UTKModalBlocker? _blocker;
private VisualElement? _header;
private Label? _titleLabel;
private Button? _closeButton;
private VisualElement? _content;
private VisualElement? _footer;
private string _title = "Modal";
private bool _showCloseButton = true;
private bool _closeOnBackdropClick = true;
private ModalSize _size = ModalSize.Medium;
#endregion
#region Events
/// 닫힘 이벤트
public event Action? OnClosed;
#endregion
#region Properties
/// 모달 제목
[UxmlAttribute("title")]
public string Title
{
get => _title;
set
{
_title = value;
if (_titleLabel != null) _titleLabel.text = value;
}
}
/// 닫기 버튼 표시 여부
[UxmlAttribute("show-close-button")]
public bool ShowCloseButton
{
get => _showCloseButton;
set
{
_showCloseButton = value;
if (_closeButton != null)
{
_closeButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
}
}
}
/// 배경 클릭 시 닫기 여부
[UxmlAttribute("close-on-backdrop-click")]
public bool CloseOnBackdropClick
{
get => _closeOnBackdropClick;
set => _closeOnBackdropClick = value;
}
/// 모달 크기
[UxmlAttribute("size")]
public ModalSize Size
{
get => _size;
set
{
_size = value;
UpdateSize();
}
}
/// 콘텐츠 컨테이너
public VisualElement? ContentContainer => _content;
/// 푸터 컨테이너
public VisualElement? FooterContainer => _footer;
#endregion
#region Enums
public enum ModalSize
{
Small,
Medium,
Large,
FullScreen
}
#endregion
#region Constructor
public UTKModal()
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
var uss = Resources.Load(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
CreateUI();
SetupEvents();
SubscribeToThemeChanges();
}
public UTKModal(string title, ModalSize size = ModalSize.Medium) : this()
{
Title = title;
Size = size;
}
#endregion
#region Static Factory
///
/// 모달 표시
///
public static UTKModal Show(VisualElement parent, string title, ModalSize size = ModalSize.Medium)
{
var modal = new UTKModal(title, size);
modal._blocker = UTKModalBlocker.Show(parent, 0.5f, false);
if (modal._closeOnBackdropClick)
{
modal._blocker.OnBlockerClicked += modal.Close;
}
modal._blocker.Add(modal);
// 중앙 정렬
modal.style.position = Position.Absolute;
modal.style.left = Length.Percent(50);
modal.style.top = Length.Percent(50);
modal.style.translate = new Translate(Length.Percent(-50), Length.Percent(-50));
return modal;
}
#endregion
#region UI Creation
private void CreateUI()
{
AddToClassList("utk-modal");
// Header
_header = new VisualElement { name = "header" };
_header.AddToClassList("utk-modal__header");
hierarchy.Add(_header);
_titleLabel = new Label { name = "title", text = _title };
_titleLabel.AddToClassList("utk-modal__title");
_header.Add(_titleLabel);
_closeButton = new Button { name = "close-btn", text = "✕" };
_closeButton.AddToClassList("utk-modal__close-btn");
_header.Add(_closeButton);
// Content
_content = new VisualElement { name = "content" };
_content.AddToClassList("utk-modal__content");
hierarchy.Add(_content);
// Footer
_footer = new VisualElement { name = "footer" };
_footer.AddToClassList("utk-modal__footer");
hierarchy.Add(_footer);
UpdateSize();
}
private void SetupEvents()
{
_closeButton?.RegisterCallback(_ => Close());
RegisterCallback(evt =>
{
if (evt.keyCode == KeyCode.Escape)
{
Close();
evt.StopPropagation();
}
}, TrickleDown.TrickleDown);
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region Methods
private void UpdateSize()
{
RemoveFromClassList("utk-modal--small");
RemoveFromClassList("utk-modal--medium");
RemoveFromClassList("utk-modal--large");
RemoveFromClassList("utk-modal--fullscreen");
var sizeClass = _size switch
{
ModalSize.Small => "utk-modal--small",
ModalSize.Large => "utk-modal--large",
ModalSize.FullScreen => "utk-modal--fullscreen",
_ => "utk-modal--medium"
};
AddToClassList(sizeClass);
}
///
/// 콘텐츠 추가
///
public new void Add(VisualElement element)
{
_content?.Add(element);
}
///
/// 푸터에 요소 추가
///
public void AddToFooter(VisualElement element)
{
_footer?.Add(element);
}
///
/// 푸터 표시/숨김
///
public void SetFooterVisible(bool visible)
{
if (_footer != null)
{
_footer.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
}
}
///
/// 모달 닫기
///
public void Close()
{
OnClosed?.Invoke();
if (_blocker != null)
{
_blocker.OnBlockerClicked -= Close;
_blocker.Hide();
}
_blocker = null;
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
OnClosed = null;
_blocker = null;
}
#endregion
}
}