351 lines
10 KiB
C#
351 lines
10 KiB
C#
#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
|
||
}
|
||
}
|