#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] public string Title { get => _title; set { _title = value; if (_titleLabel != null) _titleLabel.text = value; } } /// 닫기 버튼 표시 여부 [UxmlAttribute] public bool ShowCloseButton { get => _showCloseButton; set { _showCloseButton = value; if (_closeButton != null) { _closeButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None; } } } /// 배경 클릭 시 닫기 여부 [UxmlAttribute] public bool CloseOnBackdropClick { get => _closeOnBackdropClick; set => _closeOnBackdropClick = value; } /// 모달 크기 [UxmlAttribute] 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(); } }); } 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 } }