372 lines
12 KiB
C#
372 lines
12 KiB
C#
#nullable enable
|
|
using System;
|
|
using Cysharp.Threading.Tasks;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace UVC.UIToolkit
|
|
{
|
|
/// <summary>
|
|
/// 토스트 알림 컴포넌트.
|
|
/// 화면 하단에 일시적으로 표시되는 비방해 알림입니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para><b>Toast(토스트)란?</b></para>
|
|
/// <para>
|
|
/// Toast는 화면 하단에 잠시 나타났다가 자동으로 사라지는 알림 UI입니다.
|
|
/// 이름은 토스터기에서 빵이 올라왔다가 내려가는 모습에서 유래했습니다.
|
|
/// 사용자의 작업을 방해하지 않으면서 정보를 전달할 때 사용합니다.
|
|
/// </para>
|
|
///
|
|
/// <para><b>Toast vs Alert 차이:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description><c>Toast</c> - 자동으로 사라짐, 비침투적 (작업 방해 안 함)</description></item>
|
|
/// <item><description><c>Alert</c> - 버튼 클릭 필요, 모달 (확인 필요한 중요 정보)</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>Toast 타입 (ToastType):</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description><c>Info</c> - 일반 정보 (회색/파란색)</description></item>
|
|
/// <item><description><c>Success</c> - 성공 메시지 (녹색)</description></item>
|
|
/// <item><description><c>Warning</c> - 경고 메시지 (주황색)</description></item>
|
|
/// <item><description><c>Error</c> - 오류 메시지 (빨간색)</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>주요 속성:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description><c>Message</c> - 표시할 메시지</description></item>
|
|
/// <item><description><c>Duration</c> - 표시 시간 (밀리초, 기본 1000ms = 1초)</description></item>
|
|
/// <item><description><c>ShowCloseButton</c> - 닫기 버튼 표시 여부</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>사용 전 초기화:</b></para>
|
|
/// <para>
|
|
/// Toast를 표시하기 전에 <c>UTKToast.SetRoot(rootVisualElement)</c>로 루트를 설정해야 합니다.
|
|
/// </para>
|
|
///
|
|
/// <para><b>실제 활용 예시:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description>"저장되었습니다" - 파일 저장 완료</description></item>
|
|
/// <item><description>"클립보드에 복사됨" - 복사 완료</description></item>
|
|
/// <item><description>"네트워크 연결됨/끊김" - 연결 상태 변경</description></item>
|
|
/// <item><description>"새 메시지가 도착했습니다" - 알림</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <para><b>C# 코드에서 사용:</b></para>
|
|
/// <code>
|
|
/// // 초기화 (root 설정 필요)
|
|
/// UTKToast.SetRoot(rootVisualElement);
|
|
///
|
|
/// // 기본 토스트
|
|
/// UTKToast.Show("저장되었습니다.");
|
|
///
|
|
/// // 타입별 토스트
|
|
/// UTKToast.ShowSuccess("성공적으로 완료되었습니다.");
|
|
/// UTKToast.ShowError("오류가 발생했습니다.");
|
|
/// UTKToast.ShowWarning("주의가 필요합니다.");
|
|
/// UTKToast.ShowInfo("정보 메시지");
|
|
///
|
|
/// // 지속시간 설정 (ms)
|
|
/// UTKToast.Show("잠시 표시", duration: 2000);
|
|
///
|
|
/// // 닫기 버튼 표시
|
|
/// UTKToast.Show("메시지", showCloseButton: true);
|
|
/// </code>
|
|
/// <para><b>UXML에서 사용:</b></para>
|
|
/// <code>
|
|
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
|
|
/// <!-- 토스트는 주로 C# 코드로 동적 생성합니다 -->
|
|
/// <utk:UTKToast Message="저장됨" Type="Success" />
|
|
/// </ui:UXML>
|
|
/// </code>
|
|
/// </example>
|
|
[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
|
|
/// <summary>닫힘 이벤트</summary>
|
|
public event Action? OnClosed;
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>메시지</summary>
|
|
[UxmlAttribute("message")]
|
|
public string Message
|
|
{
|
|
get => _message;
|
|
set
|
|
{
|
|
_message = value;
|
|
if (_messageLabel != null) _messageLabel.text = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>토스트 유형</summary>
|
|
[UxmlAttribute("type")]
|
|
public ToastType Type
|
|
{
|
|
get => _type;
|
|
set
|
|
{
|
|
_type = value;
|
|
UpdateType();
|
|
}
|
|
}
|
|
|
|
/// <summary>표시 시간 (밀리초)</summary>
|
|
[UxmlAttribute("duration")]
|
|
public int Duration
|
|
{
|
|
get => _duration;
|
|
set => _duration = value;
|
|
}
|
|
|
|
/// <summary>닫기 버튼 표시 여부</summary>
|
|
[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<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 Methods
|
|
/// <summary>
|
|
/// 기본 루트 요소 설정
|
|
/// </summary>
|
|
/// <param name="root">Toast를 표시할 기본 루트 요소</param>
|
|
public static void SetRoot(VisualElement root)
|
|
{
|
|
_root = root;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 기본 루트 요소 반환
|
|
/// </summary>
|
|
public static VisualElement? GetRoot() => _root;
|
|
|
|
private static void ValidateRoot()
|
|
{
|
|
if (_root == null)
|
|
{
|
|
throw new InvalidOperationException("UTKToast.SetRoot()를 먼저 호출하여 기본 루트 요소를 설정해야 합니다.");
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Static Factory
|
|
/// <summary>
|
|
/// Info 토스트 표시
|
|
/// </summary>
|
|
public static UTKToast ShowInfo(string message, int duration = DEFAULT_DURATION_MS)
|
|
{
|
|
return Show(message, ToastType.Info, duration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Success 토스트 표시
|
|
/// </summary>
|
|
public static UTKToast ShowSuccess(string message, int duration = DEFAULT_DURATION_MS)
|
|
{
|
|
return Show(message, ToastType.Success, duration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Warning 토스트 표시
|
|
/// </summary>
|
|
public static UTKToast ShowWarning(string message, int duration = DEFAULT_DURATION_MS)
|
|
{
|
|
return Show(message, ToastType.Warning, duration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Error 토스트 표시
|
|
/// </summary>
|
|
public static UTKToast ShowError(string message, int duration = DEFAULT_DURATION_MS)
|
|
{
|
|
return Show(message, ToastType.Error, duration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 토스트 표시
|
|
/// </summary>
|
|
public static UTKToast Show(string message, ToastType type = ToastType.Info, int duration = DEFAULT_DURATION_MS)
|
|
{
|
|
ValidateRoot();
|
|
|
|
var toast = new UTKToast(message, type);
|
|
toast._duration = duration;
|
|
|
|
// panel.visualTree에 직접 추가
|
|
var root = _root!.panel?.visualTree ?? _root!;
|
|
root.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<ClickEvent>(_ => Close());
|
|
Add(_closeButton);
|
|
|
|
UpdateType();
|
|
}
|
|
|
|
private void SubscribeToThemeChanges()
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
|
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
}
|
|
|
|
private void OnAttachToPanelForTheme(AttachToPanelEvent evt)
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
}
|
|
|
|
private void OnDetachFromPanelForTheme(DetachFromPanelEvent evt)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 토스트 닫기
|
|
/// </summary>
|
|
public void Close()
|
|
{
|
|
OnClosed?.Invoke();
|
|
RemoveFromHierarchy();
|
|
}
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
OnClosed = null;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|