Files
XRLib/Assets/Scripts/UVC/UIToolkit/Modal/UTKNotification.cs
2026-01-08 20:15:57 +09:00

351 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#nullable enable
using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 알림 창 컴포넌트.
/// 화면 모서리에 표시되는 알림 메시지입니다.
/// </summary>
[UxmlElement]
public partial class UTKNotification : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Modal/UTKNotification";
private const int DEFAULT_DURATION_MS = 5000;
#endregion
#region Fields
private bool _disposed;
private VisualElement? _header;
private Label? _iconLabel;
private Label? _titleLabel;
private Button? _closeButton;
private Label? _messageLabel;
private VisualElement? _actions;
private string _title = "";
private string _message = "";
private NotificationType _type = NotificationType.Info;
private int _duration = DEFAULT_DURATION_MS;
private NotificationPosition _position = NotificationPosition.TopRight;
#endregion
#region Events
/// <summary>닫힘 이벤트</summary>
public event Action? OnClosed;
/// <summary>액션 클릭 이벤트</summary>
public event Action<string>? OnActionClicked;
#endregion
#region Properties
/// <summary>제목</summary>
[UxmlAttribute]
public string Title
{
get => _title;
set
{
_title = value;
if (_titleLabel != null) _titleLabel.text = value;
}
}
/// <summary>메시지</summary>
[UxmlAttribute]
public string Message
{
get => _message;
set
{
_message = value;
if (_messageLabel != null) _messageLabel.text = value;
}
}
/// <summary>알림 유형</summary>
[UxmlAttribute]
public NotificationType Type
{
get => _type;
set
{
_type = value;
UpdateType();
}
}
/// <summary>표시 시간 (밀리초, 0이면 수동 닫기)</summary>
[UxmlAttribute]
public int Duration
{
get => _duration;
set => _duration = value;
}
/// <summary>표시 위치</summary>
[UxmlAttribute]
public NotificationPosition Position
{
get => _position;
set => _position = value;
}
/// <summary>액션 컨테이너</summary>
public VisualElement? ActionsContainer => _actions;
#endregion
#region Enums
public enum NotificationType
{
Info,
Success,
Warning,
Error
}
public enum NotificationPosition
{
TopLeft,
TopRight,
BottomLeft,
BottomRight
}
#endregion
#region Constructor
public UTKNotification()
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
CreateUI();
SubscribeToThemeChanges();
}
public UTKNotification(string title, string message, NotificationType type = NotificationType.Info) : this()
{
Title = title;
Message = message;
Type = type;
}
#endregion
#region Static Factory
/// <summary>
/// Info 알림 표시
/// </summary>
public static UTKNotification ShowInfo(VisualElement parent, string title, string message, int duration = DEFAULT_DURATION_MS)
{
return Show(parent, title, message, NotificationType.Info, NotificationPosition.TopRight, duration);
}
/// <summary>
/// Success 알림 표시
/// </summary>
public static UTKNotification ShowSuccess(VisualElement parent, string title, string message, int duration = DEFAULT_DURATION_MS)
{
return Show(parent, title, message, NotificationType.Success, NotificationPosition.TopRight, duration);
}
/// <summary>
/// Warning 알림 표시
/// </summary>
public static UTKNotification ShowWarning(VisualElement parent, string title, string message, int duration = DEFAULT_DURATION_MS)
{
return Show(parent, title, message, NotificationType.Warning, NotificationPosition.TopRight, duration);
}
/// <summary>
/// Error 알림 표시
/// </summary>
public static UTKNotification ShowError(VisualElement parent, string title, string message, int duration = DEFAULT_DURATION_MS)
{
return Show(parent, title, message, NotificationType.Error, NotificationPosition.TopRight, duration);
}
/// <summary>
/// 알림 표시
/// </summary>
public static UTKNotification Show(VisualElement parent, string title, string message,
NotificationType type, NotificationPosition position, int duration = DEFAULT_DURATION_MS)
{
var notification = new UTKNotification(title, message, type);
notification._duration = duration;
notification._position = position;
parent.Add(notification);
// 위치 설정
notification.style.position = UnityEngine.UIElements.Position.Absolute;
notification.ApplyPosition(position);
// 자동 닫기
if (duration > 0)
{
notification.AutoCloseAsync(duration).Forget();
}
return notification;
}
#endregion
#region UI Creation
private void CreateUI()
{
AddToClassList("utk-notification");
// Header
_header = new VisualElement { name = "header" };
_header.AddToClassList("utk-notification__header");
Add(_header);
_iconLabel = new Label { name = "icon" };
_iconLabel.AddToClassList("utk-notification__icon");
_header.Add(_iconLabel);
_titleLabel = new Label { name = "title" };
_titleLabel.AddToClassList("utk-notification__title");
_header.Add(_titleLabel);
_closeButton = new Button { name = "close-btn", text = "✕" };
_closeButton.AddToClassList("utk-notification__close-btn");
_closeButton.RegisterCallback<ClickEvent>(_ => Close());
_header.Add(_closeButton);
// Message
_messageLabel = new Label { name = "message" };
_messageLabel.AddToClassList("utk-notification__message");
Add(_messageLabel);
// Actions
_actions = new VisualElement { name = "actions" };
_actions.AddToClassList("utk-notification__actions");
Add(_actions);
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-notification--info");
RemoveFromClassList("utk-notification--success");
RemoveFromClassList("utk-notification--warning");
RemoveFromClassList("utk-notification--error");
var typeClass = _type switch
{
NotificationType.Success => "utk-notification--success",
NotificationType.Warning => "utk-notification--warning",
NotificationType.Error => "utk-notification--error",
_ => "utk-notification--info"
};
AddToClassList(typeClass);
if (_iconLabel != null)
{
_iconLabel.text = _type switch
{
NotificationType.Success => "✓",
NotificationType.Warning => "⚠",
NotificationType.Error => "✕",
_ => ""
};
}
}
private void ApplyPosition(NotificationPosition position)
{
const int offset = 16;
switch (position)
{
case NotificationPosition.TopLeft:
style.top = offset;
style.left = offset;
break;
case NotificationPosition.TopRight:
style.top = offset;
style.right = offset;
break;
case NotificationPosition.BottomLeft:
style.bottom = offset;
style.left = offset;
break;
case NotificationPosition.BottomRight:
style.bottom = offset;
style.right = offset;
break;
}
}
private async UniTaskVoid AutoCloseAsync(int delayMs)
{
await UniTask.Delay(delayMs);
if (!_disposed)
{
Close();
}
}
/// <summary>
/// 액션 버튼 추가
/// </summary>
public void AddAction(string label, string actionId)
{
if (_actions == null) return;
var btn = new UTKButton(label, "", UTKButton.ButtonVariant.Ghost);
btn.Size = UTKButton.ButtonSize.Small;
btn.OnClicked += () => OnActionClicked?.Invoke(actionId);
_actions.Add(btn);
}
/// <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;
OnActionClicked = null;
}
#endregion
}
}