#nullable enable using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// 모달 뒤의 배경을 차단하고 클릭 시 닫힘 처리를 담당하는 컴포넌트 /// [UxmlElement] public partial class UTKModalBlocker : VisualElement, IDisposable { #region Constants private const string USS_CLASS = "utk-modal-blocker"; private const float DEFAULT_OPACITY = 0.7f; private const int FADE_DURATION_MS = 200; #endregion #region Fields private bool _disposed; private bool _closeOnClick; private float _targetOpacity; #endregion #region Events /// /// 블로커가 클릭되었을 때 발생 /// public event Action? OnBlockerClicked; #endregion #region Constructor /// /// UTKModalBlocker 생성자 /// public UTKModalBlocker() { AddToClassList(USS_CLASS); // 기본 스타일 설정 style.position = Position.Absolute; style.left = 0; style.top = 0; style.right = 0; style.bottom = 0; style.backgroundColor = new Color(0, 0, 0, 0); // 클릭 이벤트 등록 RegisterCallback(OnClick); } #endregion #region Static Factory /// /// 모달 블로커를 생성하고 표시합니다. /// parent의 panel.visualTree에 블로커를 추가합니다. /// /// panel 접근용 부모 요소 /// 배경 투명도 (0~1) /// 클릭 시 자동 닫힘 여부 /// 생성된 UTKModalBlocker 인스턴스 public static UTKModalBlocker Show(VisualElement parent, float opacity = DEFAULT_OPACITY, bool closeOnClick = false) { var blocker = new UTKModalBlocker { _closeOnClick = closeOnClick, _targetOpacity = Mathf.Clamp01(opacity) }; var root = parent.panel?.visualTree ?? parent; root.Add(blocker); blocker.FadeIn(); return blocker; } #endregion #region Public Methods /// /// 블로커를 숨기고 제거합니다. /// public void Hide() { FadeOut(() => { style.display = DisplayStyle.None; RemoveFromHierarchy(); Dispose(); }); } /// /// 모달 콘텐츠를 블로커 앞에 추가합니다. /// /// 모달 콘텐츠 요소 public void ShowModal(VisualElement modalContent) { if (modalContent == null) return; // 모달 콘텐츠를 블로커 다음에 추가 var parent = this.parent; if (parent != null) { int blockerIndex = parent.IndexOf(this); parent.Insert(blockerIndex + 1, modalContent); } } /// /// 블로커를 맨 위로 가져옵니다. /// public void BringToFront() { this.BringToFront(); } #endregion #region Private Methods private void OnClick(ClickEvent evt) { // 블로커 자체가 클릭된 경우에만 처리 (버블링된 이벤트 무시) if (evt.target != this) return; OnBlockerClicked?.Invoke(); if (_closeOnClick) { Hide(); } evt.StopPropagation(); } private void FadeIn() { // 시작 투명도 style.backgroundColor = new Color(0, 0, 0, 0); // 애니메이션 schedule.Execute(() => { style.transitionDuration = new StyleList(new List { new TimeValue(FADE_DURATION_MS, TimeUnit.Millisecond) }); style.transitionProperty = new StyleList(new List { new StylePropertyName("background-color") }); style.backgroundColor = new Color(0, 0, 0, _targetOpacity); }); } private void FadeOut(Action? onComplete = null) { style.transitionDuration = new StyleList(new List { new TimeValue(FADE_DURATION_MS, TimeUnit.Millisecond) }); style.transitionProperty = new StyleList(new List { new StylePropertyName("background-color") }); style.backgroundColor = new Color(0, 0, 0, 0); // 페이드 아웃 완료 후 콜백 schedule.Execute(() => onComplete?.Invoke()).ExecuteLater(FADE_DURATION_MS); } #endregion #region IDisposable public void Dispose() { if (_disposed) return; _disposed = true; UnregisterCallback(OnClick); OnBlockerClicked = null; } #endregion } }