Files
XRLib/Assets/Scripts/UVC/UIToolkit/Modal/UTKModalBlocker.cs
2026-02-23 19:38:27 +09:00

177 lines
5.4 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 모달 뒤의 배경을 차단하고 클릭 시 닫힘 처리를 담당하는 컴포넌트
/// </summary>
[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
/// <summary>
/// 블로커가 클릭되었을 때 발생
/// </summary>
public event Action? OnBlockerClicked;
#endregion
#region Constructor
/// <summary>
/// UTKModalBlocker 생성자
/// </summary>
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<ClickEvent>(OnClick);
}
#endregion
#region Static Factory
/// <summary>
/// 모달 블로커를 생성하고 표시합니다.
/// parent의 panel.visualTree에 블로커를 추가합니다.
/// </summary>
/// <param name="parent">panel 접근용 부모 요소</param>
/// <param name="opacity">배경 투명도 (0~1)</param>
/// <param name="closeOnClick">클릭 시 자동 닫힘 여부</param>
/// <returns>생성된 UTKModalBlocker 인스턴스</returns>
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
/// <summary>
/// 블로커를 숨기고 제거합니다.
/// </summary>
public void Hide()
{
FadeOut(() =>
{
style.display = DisplayStyle.None;
RemoveFromHierarchy();
Dispose();
});
}
/// <summary>
/// 모달 콘텐츠를 블로커 앞에 추가합니다.
/// </summary>
/// <param name="modalContent">모달 콘텐츠 요소</param>
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);
}
}
/// <summary>
/// 블로커를 맨 위로 가져옵니다.
/// </summary>
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<TimeValue>(new List<TimeValue> { new TimeValue(FADE_DURATION_MS, TimeUnit.Millisecond) });
style.transitionProperty = new StyleList<StylePropertyName>(new List<StylePropertyName> { new StylePropertyName("background-color") });
style.backgroundColor = new Color(0, 0, 0, _targetOpacity);
});
}
private void FadeOut(Action? onComplete = null)
{
style.transitionDuration = new StyleList<TimeValue>(new List<TimeValue> { new TimeValue(FADE_DURATION_MS, TimeUnit.Millisecond) });
style.transitionProperty = new StyleList<StylePropertyName>(new List<StylePropertyName> { 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<ClickEvent>(OnClick);
OnBlockerClicked = null;
}
#endregion
}
}