275 lines
7.6 KiB
C#
275 lines
7.6 KiB
C#
#nullable enable
|
||
using System;
|
||
using Cysharp.Threading.Tasks;
|
||
using UnityEngine;
|
||
using UnityEngine.UIElements;
|
||
|
||
namespace UVC.UIToolkit
|
||
{
|
||
/// <summary>
|
||
/// 토스트 알림 컴포넌트.
|
||
/// 화면 하단에 일시적으로 표시되는 알림입니다.
|
||
/// </summary>
|
||
[UxmlElement]
|
||
public partial class UTKToast : VisualElement, IDisposable
|
||
{
|
||
#region Constants
|
||
private const string USS_PATH = "UIToolkit/Modal/UTKToast";
|
||
private const int DEFAULT_DURATION_MS = 3000;
|
||
#endregion
|
||
|
||
#region Fields
|
||
private bool _disposed;
|
||
private Label? _iconLabel;
|
||
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
|
||
/// <summary>닫힘 이벤트</summary>
|
||
public event Action? OnClosed;
|
||
#endregion
|
||
|
||
#region Properties
|
||
/// <summary>메시지</summary>
|
||
[UxmlAttribute]
|
||
public string Message
|
||
{
|
||
get => _message;
|
||
set
|
||
{
|
||
_message = value;
|
||
if (_messageLabel != null) _messageLabel.text = value;
|
||
}
|
||
}
|
||
|
||
/// <summary>토스트 유형</summary>
|
||
[UxmlAttribute]
|
||
public ToastType Type
|
||
{
|
||
get => _type;
|
||
set
|
||
{
|
||
_type = value;
|
||
UpdateType();
|
||
}
|
||
}
|
||
|
||
/// <summary>표시 시간 (밀리초)</summary>
|
||
[UxmlAttribute]
|
||
public int Duration
|
||
{
|
||
get => _duration;
|
||
set => _duration = value;
|
||
}
|
||
|
||
/// <summary>닫기 버튼 표시 여부</summary>
|
||
[UxmlAttribute]
|
||
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<StyleSheet>(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 Factory
|
||
/// <summary>
|
||
/// Info 토스트 표시
|
||
/// </summary>
|
||
public static UTKToast ShowInfo(VisualElement parent, string message, int duration = DEFAULT_DURATION_MS)
|
||
{
|
||
return Show(parent, message, ToastType.Info, duration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Success 토스트 표시
|
||
/// </summary>
|
||
public static UTKToast ShowSuccess(VisualElement parent, string message, int duration = DEFAULT_DURATION_MS)
|
||
{
|
||
return Show(parent, message, ToastType.Success, duration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Warning 토스트 표시
|
||
/// </summary>
|
||
public static UTKToast ShowWarning(VisualElement parent, string message, int duration = DEFAULT_DURATION_MS)
|
||
{
|
||
return Show(parent, message, ToastType.Warning, duration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Error 토스트 표시
|
||
/// </summary>
|
||
public static UTKToast ShowError(VisualElement parent, string message, int duration = DEFAULT_DURATION_MS)
|
||
{
|
||
return Show(parent, message, ToastType.Error, duration);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 토스트 표시
|
||
/// </summary>
|
||
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");
|
||
|
||
_iconLabel = new Label { name = "icon" };
|
||
_iconLabel.AddToClassList("utk-toast__icon");
|
||
Add(_iconLabel);
|
||
|
||
_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<ClickEvent>(_ => Close());
|
||
Add(_closeButton);
|
||
|
||
UpdateType();
|
||
}
|
||
|
||
private void SubscribeToThemeChanges()
|
||
{
|
||
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
||
RegisterCallback<DetachFromPanelEvent>(_ =>
|
||
{
|
||
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);
|
||
|
||
if (_iconLabel != null)
|
||
{
|
||
_iconLabel.text = _type switch
|
||
{
|
||
ToastType.Success => "✓",
|
||
ToastType.Warning => "⚠",
|
||
ToastType.Error => "✕",
|
||
_ => "ℹ"
|
||
};
|
||
}
|
||
}
|
||
|
||
private async UniTaskVoid AutoCloseAsync(int delayMs)
|
||
{
|
||
await UniTask.Delay(delayMs);
|
||
if (!_disposed)
|
||
{
|
||
Close();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 토스트 닫기
|
||
/// </summary>
|
||
public void Close()
|
||
{
|
||
OnClosed?.Invoke();
|
||
RemoveFromHierarchy();
|
||
}
|
||
#endregion
|
||
|
||
#region IDisposable
|
||
public void Dispose()
|
||
{
|
||
if (_disposed) return;
|
||
_disposed = true;
|
||
|
||
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
||
OnClosed = null;
|
||
}
|
||
#endregion
|
||
}
|
||
}
|