#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
}
}