#nullable enable
using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
///
/// 토스트 알림 컴포넌트.
/// 화면 하단에 일시적으로 표시되는 비방해 알림입니다.
///
///
/// Toast(토스트)란?
///
/// Toast는 화면 하단에 잠시 나타났다가 자동으로 사라지는 알림 UI입니다.
/// 이름은 토스터기에서 빵이 올라왔다가 내려가는 모습에서 유래했습니다.
/// 사용자의 작업을 방해하지 않으면서 정보를 전달할 때 사용합니다.
///
///
/// Toast vs Alert 차이:
///
/// - Toast - 자동으로 사라짐, 비침투적 (작업 방해 안 함)
/// - Alert - 버튼 클릭 필요, 모달 (확인 필요한 중요 정보)
///
///
/// Toast 타입 (ToastType):
///
/// - Info - 일반 정보 (회색/파란색)
/// - Success - 성공 메시지 (녹색)
/// - Warning - 경고 메시지 (주황색)
/// - Error - 오류 메시지 (빨간색)
///
///
/// 주요 속성:
///
/// - Message - 표시할 메시지
/// - Duration - 표시 시간 (밀리초, 기본 1000ms = 1초)
/// - ShowCloseButton - 닫기 버튼 표시 여부
///
///
/// 사용 전 초기화:
///
/// Toast를 표시하기 전에 UTKToast.SetRoot(rootVisualElement)로 루트를 설정해야 합니다.
/// 또는 Show(parent, ...)로 부모 요소를 직접 전달할 수 있습니다.
///
///
/// 실제 활용 예시:
///
/// - "저장되었습니다" - 파일 저장 완료
/// - "클립보드에 복사됨" - 복사 완료
/// - "네트워크 연결됨/끊김" - 연결 상태 변경
/// - "새 메시지가 도착했습니다" - 알림
///
///
///
/// C# 코드에서 사용:
///
/// // 초기화 (root 설정 필요)
/// UTKToast.Initialize(rootVisualElement);
///
/// // 기본 토스트
/// UTKToast.Show("저장되었습니다.");
///
/// // 타입별 토스트
/// UTKToast.ShowSuccess("성공적으로 완료되었습니다.");
/// UTKToast.ShowError("오류가 발생했습니다.");
/// UTKToast.ShowWarning("주의가 필요합니다.");
/// UTKToast.ShowInfo("정보 메시지");
///
/// // 지속시간 설정 (ms)
/// UTKToast.Show("잠시 표시", duration: 2000);
///
/// // 닫기 버튼 표시
/// UTKToast.Show("메시지", showCloseButton: true);
///
/// UXML에서 사용:
///
///
///
///
///
///
///
[UxmlElement]
public partial class UTKToast : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Modal/UTKToast";
private const int DEFAULT_DURATION_MS = 1000;
#endregion
#region Fields
private static VisualElement? _root;
private bool _disposed;
private Label? _messageLabel;
private Button? _closeButton;
private string _message = "";
private ToastType _type = ToastType.Info;
private int _duration = DEFAULT_DURATION_MS;
private bool _showCloseButton;
#endregion
#region Events
/// 닫힘 이벤트
public event Action? OnClosed;
#endregion
#region Properties
/// 메시지
[UxmlAttribute("message")]
public string Message
{
get => _message;
set
{
_message = value;
if (_messageLabel != null) _messageLabel.text = value;
}
}
/// 토스트 유형
[UxmlAttribute("type")]
public ToastType Type
{
get => _type;
set
{
_type = value;
UpdateType();
}
}
/// 표시 시간 (밀리초)
[UxmlAttribute("duration")]
public int Duration
{
get => _duration;
set => _duration = value;
}
/// 닫기 버튼 표시 여부
[UxmlAttribute("show-close-button")]
public bool ShowCloseButton
{
get => _showCloseButton;
set
{
_showCloseButton = value;
if (_closeButton != null)
{
_closeButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
}
}
}
#endregion
#region Enums
public enum ToastType
{
Info,
Success,
Warning,
Error
}
#endregion
#region Constructor
public UTKToast()
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
var uss = Resources.Load(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
CreateUI();
SubscribeToThemeChanges();
}
public UTKToast(string message, ToastType type = ToastType.Info) : this()
{
Message = message;
Type = type;
}
#endregion
#region Static Methods
///
/// 기본 루트 요소 설정
///
/// Toast를 표시할 기본 루트 요소
public static void SetRoot(VisualElement root)
{
_root = root;
}
///
/// 기본 루트 요소 반환
///
public static VisualElement? GetRoot() => _root;
private static void ValidateRoot()
{
if (_root == null)
{
throw new InvalidOperationException("UTKToast.SetRoot()를 먼저 호출하여 기본 루트 요소를 설정해야 합니다.");
}
}
#endregion
#region Static Factory (without parent)
///
/// Info 토스트 표시 (SetRoot로 설정된 루트 사용)
///
public static UTKToast ShowInfo(string message, int duration = DEFAULT_DURATION_MS)
{
ValidateRoot();
return Show(_root!, message, ToastType.Info, duration);
}
///
/// Success 토스트 표시 (SetRoot로 설정된 루트 사용)
///
public static UTKToast ShowSuccess(string message, int duration = DEFAULT_DURATION_MS)
{
ValidateRoot();
return Show(_root!, message, ToastType.Success, duration);
}
///
/// Warning 토스트 표시 (SetRoot로 설정된 루트 사용)
///
public static UTKToast ShowWarning(string message, int duration = DEFAULT_DURATION_MS)
{
ValidateRoot();
return Show(_root!, message, ToastType.Warning, duration);
}
///
/// Error 토스트 표시 (SetRoot로 설정된 루트 사용)
///
public static UTKToast ShowError(string message, int duration = DEFAULT_DURATION_MS)
{
ValidateRoot();
return Show(_root!, message, ToastType.Error, duration);
}
///
/// 토스트 표시 (SetRoot로 설정된 루트 사용)
///
public static UTKToast Show(string message, ToastType type = ToastType.Info, int duration = DEFAULT_DURATION_MS)
{
ValidateRoot();
return Show(_root!, message, type, duration);
}
#endregion
#region Static Factory (with parent)
///
/// Info 토스트 표시
///
public static UTKToast ShowInfo(VisualElement parent, string message, int duration = DEFAULT_DURATION_MS)
{
return Show(parent, message, ToastType.Info, duration);
}
///
/// Success 토스트 표시
///
public static UTKToast ShowSuccess(VisualElement parent, string message, int duration = DEFAULT_DURATION_MS)
{
return Show(parent, message, ToastType.Success, duration);
}
///
/// Warning 토스트 표시
///
public static UTKToast ShowWarning(VisualElement parent, string message, int duration = DEFAULT_DURATION_MS)
{
return Show(parent, message, ToastType.Warning, duration);
}
///
/// Error 토스트 표시
///
public static UTKToast ShowError(VisualElement parent, string message, int duration = DEFAULT_DURATION_MS)
{
return Show(parent, message, ToastType.Error, duration);
}
///
/// 토스트 표시
///
public static UTKToast Show(VisualElement parent, string message, ToastType type, int duration = DEFAULT_DURATION_MS)
{
var toast = new UTKToast(message, type);
toast._duration = duration;
parent.Add(toast);
// 하단 중앙 정렬
toast.style.position = Position.Absolute;
toast.style.left = Length.Percent(50);
toast.style.bottom = 20;
toast.style.translate = new Translate(Length.Percent(-50), 0);
// 자동 닫기
if (duration > 0)
{
toast.AutoCloseAsync(duration).Forget();
}
return toast;
}
#endregion
#region UI Creation
private void CreateUI()
{
AddToClassList("utk-toast");
_messageLabel = new Label { name = "message" };
_messageLabel.AddToClassList("utk-toast__message");
Add(_messageLabel);
_closeButton = new Button { name = "close-btn", text = "✕" };
_closeButton.AddToClassList("utk-toast__close-btn");
_closeButton.style.display = DisplayStyle.None;
_closeButton.RegisterCallback(_ => Close());
Add(_closeButton);
UpdateType();
}
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 UpdateType()
{
RemoveFromClassList("utk-toast--info");
RemoveFromClassList("utk-toast--success");
RemoveFromClassList("utk-toast--warning");
RemoveFromClassList("utk-toast--error");
var typeClass = _type switch
{
ToastType.Success => "utk-toast--success",
ToastType.Warning => "utk-toast--warning",
ToastType.Error => "utk-toast--error",
_ => "utk-toast--info"
};
AddToClassList(typeClass);
}
private async UniTaskVoid AutoCloseAsync(int delayMs)
{
await UniTask.Delay(delayMs);
if (!_disposed)
{
Close();
}
}
///
/// 토스트 닫기
///
public void Close()
{
OnClosed?.Invoke();
RemoveFromHierarchy();
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
OnClosed = null;
}
#endregion
}
}