#nullable enable using System; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// 토스트 알림 컴포넌트. /// 화면 하단에 일시적으로 표시되는 알림입니다. /// [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] public string Message { get => _message; set { _message = value; if (_messageLabel != null) _messageLabel.text = value; } } /// 토스트 유형 [UxmlAttribute] public ToastType Type { get => _type; set { _type = value; UpdateType(); } } /// 표시 시간 (밀리초) [UxmlAttribute] public int Duration { get => _duration; set => _duration = value; } /// 닫기 버튼 표시 여부 [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(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 } }