754 lines
28 KiB
C#
754 lines
28 KiB
C#
using Dalamud.Interface;
|
|
using Dalamud.Interface.Colors;
|
|
using Dalamud.Interface.Utility;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Interface.Windowing;
|
|
using LightlessSync.LightlessConfiguration;
|
|
using LightlessSync.LightlessConfiguration.Models;
|
|
using LightlessSync.Services;
|
|
using LightlessSync.Services.Mediator;
|
|
using LightlessSync.UI.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
using System.Numerics;
|
|
using Dalamud.Bindings.ImGui;
|
|
|
|
namespace LightlessSync.UI;
|
|
|
|
public class LightlessNotificationUi : WindowMediatorSubscriberBase
|
|
{
|
|
private const float _notificationMinHeight = 60f;
|
|
private const float _notificationMaxHeight = 250f;
|
|
private const float _windowPaddingOffset = 6f;
|
|
private const float _slideAnimationDistance = 100f;
|
|
private const float _outAnimationSpeedMultiplier = 0.7f;
|
|
private const float _contentPaddingX = 10f;
|
|
private const float _contentPaddingY = 6f;
|
|
private const float _titleMessageSpacing = 4f;
|
|
private const float _actionButtonSpacing = 8f;
|
|
|
|
private readonly List<LightlessNotification> _notifications = new();
|
|
private readonly object _notificationLock = new();
|
|
private readonly LightlessConfigService _configService;
|
|
private readonly Dictionary<string, float> _notificationYOffsets = new();
|
|
private readonly Dictionary<string, float> _notificationTargetYOffsets = new();
|
|
|
|
public LightlessNotificationUi(ILogger<LightlessNotificationUi> logger, LightlessMediator mediator, PerformanceCollectorService performanceCollector, LightlessConfigService configService)
|
|
: base(logger, mediator, "Lightless Notifications##LightlessNotifications", performanceCollector)
|
|
{
|
|
_configService = configService;
|
|
Flags = ImGuiWindowFlags.NoDecoration |
|
|
ImGuiWindowFlags.NoMove |
|
|
ImGuiWindowFlags.NoResize |
|
|
ImGuiWindowFlags.NoSavedSettings |
|
|
ImGuiWindowFlags.NoFocusOnAppearing |
|
|
ImGuiWindowFlags.NoNav |
|
|
ImGuiWindowFlags.NoBackground |
|
|
ImGuiWindowFlags.NoCollapse |
|
|
ImGuiWindowFlags.NoInputs |
|
|
ImGuiWindowFlags.NoTitleBar |
|
|
ImGuiWindowFlags.NoScrollbar |
|
|
ImGuiWindowFlags.AlwaysAutoResize;
|
|
|
|
PositionCondition = ImGuiCond.Always;
|
|
SizeCondition = ImGuiCond.FirstUseEver;
|
|
IsOpen = false;
|
|
RespectCloseHotkey = false;
|
|
DisableWindowSounds = true;
|
|
|
|
Mediator.Subscribe<LightlessNotificationMessage>(this, HandleNotificationMessage);
|
|
Mediator.Subscribe<LightlessNotificationDismissMessage>(this, HandleNotificationDismissMessage);
|
|
Mediator.Subscribe<ClearAllNotificationsMessage>(this, HandleClearAllNotifications);
|
|
}
|
|
private void HandleNotificationMessage(LightlessNotificationMessage message) => AddNotification(message.Notification);
|
|
private void HandleNotificationDismissMessage(LightlessNotificationDismissMessage message) => RemoveNotification(message.NotificationId);
|
|
private void HandleClearAllNotifications(ClearAllNotificationsMessage message) => ClearAllNotifications();
|
|
|
|
public void AddNotification(LightlessNotification notification)
|
|
{
|
|
lock (_notificationLock)
|
|
{
|
|
var existingNotification = _notifications.FirstOrDefault(n => n.Id == notification.Id);
|
|
if (existingNotification != null)
|
|
{
|
|
UpdateExistingNotification(existingNotification, notification);
|
|
}
|
|
else
|
|
{
|
|
_notifications.Add(notification);
|
|
_logger.LogDebug("Added new notification: {Title}", notification.Title);
|
|
}
|
|
|
|
if (!IsOpen) IsOpen = true;
|
|
}
|
|
}
|
|
|
|
private void UpdateExistingNotification(LightlessNotification existing, LightlessNotification updated)
|
|
{
|
|
existing.Message = updated.Message;
|
|
existing.Progress = updated.Progress;
|
|
existing.ShowProgress = updated.ShowProgress;
|
|
existing.Title = updated.Title;
|
|
|
|
// Reset the duration timer on every update for download notifications
|
|
if (updated.Type == NotificationType.Download)
|
|
{
|
|
existing.CreatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
_logger.LogDebug("Updated existing notification: {Title}", updated.Title);
|
|
}
|
|
|
|
public void RemoveNotification(string id)
|
|
{
|
|
lock (_notificationLock)
|
|
{
|
|
var notification = _notifications.FirstOrDefault(n => n.Id == id);
|
|
if (notification != null)
|
|
{
|
|
StartOutAnimation(notification);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ClearAllNotifications()
|
|
{
|
|
lock (_notificationLock)
|
|
{
|
|
foreach (var notification in _notifications)
|
|
{
|
|
StartOutAnimation(notification);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void StartOutAnimation(LightlessNotification notification)
|
|
{
|
|
notification.IsAnimatingOut = true;
|
|
notification.IsAnimatingIn = false;
|
|
}
|
|
|
|
private bool ShouldRemoveNotification(LightlessNotification notification) =>
|
|
notification.IsAnimatingOut && notification.AnimationProgress <= 0.01f;
|
|
|
|
protected override void DrawInternal()
|
|
{
|
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
|
|
|
lock (_notificationLock)
|
|
{
|
|
UpdateNotifications();
|
|
|
|
if (_notifications.Count == 0)
|
|
{
|
|
ImGui.PopStyleVar();
|
|
IsOpen = false;
|
|
return;
|
|
}
|
|
|
|
var viewport = ImGui.GetMainViewport();
|
|
|
|
// Window auto-resizes based on content (AlwaysAutoResize flag)
|
|
Position = CalculateWindowPosition(viewport);
|
|
PositionCondition = ImGuiCond.Always;
|
|
|
|
DrawAllNotifications();
|
|
}
|
|
|
|
ImGui.PopStyleVar();
|
|
}
|
|
|
|
private Vector2 CalculateWindowPosition(ImGuiViewportPtr viewport)
|
|
{
|
|
var corner = _configService.Current.NotificationCorner;
|
|
var offsetX = _configService.Current.NotificationOffsetX;
|
|
var width = _configService.Current.NotificationWidth;
|
|
|
|
float posX = corner == NotificationCorner.Left
|
|
? viewport.WorkPos.X + offsetX - _windowPaddingOffset
|
|
: viewport.WorkPos.X + viewport.WorkSize.X - width - offsetX - _windowPaddingOffset;
|
|
|
|
return new Vector2(posX, viewport.WorkPos.Y);
|
|
}
|
|
|
|
private void DrawAllNotifications()
|
|
{
|
|
var offsetY = _configService.Current.NotificationOffsetY;
|
|
var startY = ImGui.GetCursorPosY() + offsetY;
|
|
|
|
for (int i = 0; i < _notifications.Count; i++)
|
|
{
|
|
var notification = _notifications[i];
|
|
|
|
if (_notificationYOffsets.TryGetValue(notification.Id, out var yOffset))
|
|
{
|
|
ImGui.SetCursorPosY(startY + yOffset);
|
|
}
|
|
|
|
DrawNotification(notification, i);
|
|
}
|
|
}
|
|
|
|
private void UpdateNotifications()
|
|
{
|
|
var deltaTime = ImGui.GetIO().DeltaTime;
|
|
EnforceMaxNotificationLimit();
|
|
UpdateAnimationsAndRemoveExpired(deltaTime);
|
|
}
|
|
|
|
private void EnforceMaxNotificationLimit()
|
|
{
|
|
var maxNotifications = _configService.Current.MaxSimultaneousNotifications;
|
|
while (_notifications.Count(n => !n.IsAnimatingOut) > maxNotifications)
|
|
{
|
|
var oldestNotification = _notifications
|
|
.Where(n => !n.IsAnimatingOut)
|
|
.OrderBy(n => n.CreatedAt)
|
|
.FirstOrDefault();
|
|
|
|
if (oldestNotification != null)
|
|
{
|
|
StartOutAnimation(oldestNotification);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateAnimationsAndRemoveExpired(float deltaTime)
|
|
{
|
|
UpdateTargetYPositions();
|
|
|
|
for (int i = _notifications.Count - 1; i >= 0; i--)
|
|
{
|
|
var notification = _notifications[i];
|
|
UpdateNotificationAnimation(notification, deltaTime);
|
|
UpdateNotificationYOffset(notification, deltaTime);
|
|
|
|
if (ShouldRemoveNotification(notification))
|
|
{
|
|
_notifications.RemoveAt(i);
|
|
_notificationYOffsets.Remove(notification.Id);
|
|
_notificationTargetYOffsets.Remove(notification.Id);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateTargetYPositions()
|
|
{
|
|
float currentY = 0f;
|
|
|
|
for (int i = 0; i < _notifications.Count; i++)
|
|
{
|
|
var notification = _notifications[i];
|
|
|
|
if (!_notificationTargetYOffsets.ContainsKey(notification.Id))
|
|
{
|
|
_notificationTargetYOffsets[notification.Id] = currentY;
|
|
_notificationYOffsets[notification.Id] = currentY;
|
|
}
|
|
else
|
|
{
|
|
_notificationTargetYOffsets[notification.Id] = currentY;
|
|
}
|
|
|
|
currentY += CalculateNotificationHeight(notification) + _configService.Current.NotificationSpacing;
|
|
}
|
|
}
|
|
|
|
private void UpdateNotificationYOffset(LightlessNotification notification, float deltaTime)
|
|
{
|
|
if (!_notificationYOffsets.ContainsKey(notification.Id) || !_notificationTargetYOffsets.ContainsKey(notification.Id))
|
|
return;
|
|
|
|
var current = _notificationYOffsets[notification.Id];
|
|
var target = _notificationTargetYOffsets[notification.Id];
|
|
var diff = target - current;
|
|
|
|
if (Math.Abs(diff) < 0.5f)
|
|
{
|
|
_notificationYOffsets[notification.Id] = target;
|
|
}
|
|
else
|
|
{
|
|
var speed = _configService.Current.NotificationSlideSpeed;
|
|
_notificationYOffsets[notification.Id] = current + (diff * deltaTime * speed);
|
|
}
|
|
}
|
|
|
|
private void UpdateNotificationAnimation(LightlessNotification notification, float deltaTime)
|
|
{
|
|
if (notification.IsAnimatingIn && notification.AnimationProgress < 1f)
|
|
{
|
|
notification.AnimationProgress = Math.Min(1f,
|
|
notification.AnimationProgress + deltaTime * _configService.Current.NotificationAnimationSpeed);
|
|
}
|
|
else if (notification.IsAnimatingOut && notification.AnimationProgress > 0f)
|
|
{
|
|
notification.AnimationProgress = Math.Max(0f,
|
|
notification.AnimationProgress - deltaTime * _configService.Current.NotificationAnimationSpeed * _outAnimationSpeedMultiplier);
|
|
}
|
|
else if (!notification.IsAnimatingOut && !notification.IsDismissed)
|
|
{
|
|
notification.IsAnimatingIn = false;
|
|
|
|
if (notification.IsExpired)
|
|
{
|
|
StartOutAnimation(notification);
|
|
}
|
|
}
|
|
}
|
|
|
|
private Vector2 CalculateSlideOffset(float alpha)
|
|
{
|
|
var distance = (1f - alpha) * _slideAnimationDistance;
|
|
var corner = _configService.Current.NotificationCorner;
|
|
return corner == NotificationCorner.Left ? new Vector2(-distance, 0) : new Vector2(distance, 0);
|
|
}
|
|
|
|
private void DrawNotification(LightlessNotification notification, int index)
|
|
{
|
|
var alpha = notification.AnimationProgress;
|
|
if (alpha <= 0f) return;
|
|
|
|
var slideOffset = CalculateSlideOffset(alpha);
|
|
var originalCursorPos = ImGui.GetCursorPos();
|
|
ImGui.SetCursorPos(originalCursorPos + slideOffset);
|
|
|
|
var notificationHeight = CalculateNotificationHeight(notification);
|
|
var notificationWidth = _configService.Current.NotificationWidth;
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
|
|
|
using var child = ImRaii.Child($"notification_{notification.Id}",
|
|
new Vector2(notificationWidth, notificationHeight),
|
|
false, ImGuiWindowFlags.NoScrollbar);
|
|
|
|
if (child.Success)
|
|
{
|
|
DrawNotificationContent(notification, alpha);
|
|
}
|
|
|
|
ImGui.PopStyleVar();
|
|
}
|
|
|
|
private void DrawNotificationContent(LightlessNotification notification, float alpha)
|
|
{
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
var windowPos = ImGui.GetWindowPos();
|
|
var windowSize = ImGui.GetWindowSize();
|
|
|
|
var bgColor = CalculateBackgroundColor(alpha, ImGui.IsWindowHovered());
|
|
var accentColor = GetNotificationAccentColor(notification.Type);
|
|
accentColor.W *= alpha;
|
|
|
|
DrawShadow(drawList, windowPos, windowSize, alpha);
|
|
HandleClickToDismiss(notification);
|
|
DrawBackground(drawList, windowPos, windowSize, bgColor);
|
|
DrawAccentBar(drawList, windowPos, windowSize, accentColor);
|
|
DrawDurationProgressBar(notification, alpha, windowPos, windowSize, drawList);
|
|
|
|
// Draw download progress bar above duration bar for download notifications
|
|
if (notification.Type == NotificationType.Download && notification.ShowProgress)
|
|
{
|
|
DrawDownloadProgressBar(notification, alpha, windowPos, windowSize, drawList);
|
|
}
|
|
|
|
DrawNotificationText(notification, alpha);
|
|
}
|
|
|
|
private Vector4 CalculateBackgroundColor(float alpha, bool isHovered)
|
|
{
|
|
var baseOpacity = _configService.Current.NotificationOpacity;
|
|
var finalOpacity = baseOpacity * alpha;
|
|
var bgColor = new Vector4(30f/255f, 30f/255f, 30f/255f, finalOpacity);
|
|
|
|
if (isHovered)
|
|
{
|
|
bgColor *= 1.1f;
|
|
bgColor.W = Math.Min(bgColor.W, 0.98f);
|
|
}
|
|
|
|
return bgColor;
|
|
}
|
|
|
|
private void DrawShadow(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, float alpha)
|
|
{
|
|
var shadowOffset = new Vector2(1f, 1f);
|
|
var shadowColor = new Vector4(0f, 0f, 0f, 0.4f * alpha);
|
|
drawList.AddRectFilled(
|
|
windowPos + shadowOffset,
|
|
windowPos + windowSize + shadowOffset,
|
|
ImGui.ColorConvertFloat4ToU32(shadowColor),
|
|
3f
|
|
);
|
|
}
|
|
|
|
private void HandleClickToDismiss(LightlessNotification notification)
|
|
{
|
|
if (ImGui.IsWindowHovered() &&
|
|
_configService.Current.DismissNotificationOnClick &&
|
|
!notification.Actions.Any() &&
|
|
ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
|
{
|
|
notification.IsDismissed = true;
|
|
StartOutAnimation(notification);
|
|
}
|
|
}
|
|
|
|
private void DrawBackground(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, Vector4 bgColor)
|
|
{
|
|
drawList.AddRectFilled(
|
|
windowPos,
|
|
windowPos + windowSize,
|
|
ImGui.ColorConvertFloat4ToU32(bgColor),
|
|
3f
|
|
);
|
|
}
|
|
|
|
private void DrawAccentBar(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, Vector4 accentColor)
|
|
{
|
|
var accentWidth = _configService.Current.NotificationAccentBarWidth;
|
|
if (accentWidth <= 0f) return;
|
|
|
|
var corner = _configService.Current.NotificationCorner;
|
|
Vector2 accentStart, accentEnd;
|
|
|
|
if (corner == NotificationCorner.Left)
|
|
{
|
|
accentStart = windowPos + new Vector2(windowSize.X - accentWidth, 0);
|
|
accentEnd = windowPos + windowSize;
|
|
}
|
|
else
|
|
{
|
|
accentStart = windowPos;
|
|
accentEnd = windowPos + new Vector2(accentWidth, windowSize.Y);
|
|
}
|
|
|
|
drawList.AddRectFilled(
|
|
accentStart,
|
|
accentEnd,
|
|
ImGui.ColorConvertFloat4ToU32(accentColor),
|
|
3f
|
|
);
|
|
}
|
|
|
|
private void DrawDurationProgressBar(LightlessNotification notification, float alpha, Vector2 windowPos, Vector2 windowSize, ImDrawListPtr drawList)
|
|
{
|
|
var progress = CalculateDurationProgress(notification);
|
|
var progressBarColor = UIColors.Get("LightlessBlue");
|
|
var progressHeight = 2f;
|
|
var progressY = windowPos.Y + windowSize.Y - progressHeight;
|
|
var progressWidth = windowSize.X * progress;
|
|
|
|
DrawProgressBackground(drawList, windowPos, windowSize, progressY, progressHeight, progressBarColor, alpha);
|
|
|
|
if (progress > 0)
|
|
{
|
|
DrawProgressForeground(drawList, windowPos, progressY, progressHeight, progressWidth, progressBarColor, alpha);
|
|
}
|
|
}
|
|
|
|
private void DrawDownloadProgressBar(LightlessNotification notification, float alpha, Vector2 windowPos, Vector2 windowSize, ImDrawListPtr drawList)
|
|
{
|
|
var progress = Math.Clamp(notification.Progress, 0f, 1f);
|
|
var progressBarColor = UIColors.Get("LightlessGreen");
|
|
var progressHeight = 3f;
|
|
// Position above the duration bar (2px duration bar + 1px spacing)
|
|
var progressY = windowPos.Y + windowSize.Y - progressHeight - 3f;
|
|
var progressWidth = windowSize.X * progress;
|
|
|
|
DrawProgressBackground(drawList, windowPos, windowSize, progressY, progressHeight, progressBarColor, alpha);
|
|
|
|
if (progress > 0)
|
|
{
|
|
DrawProgressForeground(drawList, windowPos, progressY, progressHeight, progressWidth, progressBarColor, alpha);
|
|
}
|
|
}
|
|
|
|
private float CalculateDurationProgress(LightlessNotification notification)
|
|
{
|
|
// Calculate duration timer progress
|
|
var elapsed = DateTime.UtcNow - notification.CreatedAt;
|
|
return Math.Min(1.0f, (float)(elapsed.TotalSeconds / notification.Duration.TotalSeconds));
|
|
}
|
|
|
|
private void DrawProgressBackground(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, float progressY, float progressHeight, Vector4 progressBarColor, float alpha)
|
|
{
|
|
var bgProgressColor = new Vector4(progressBarColor.X * 0.3f, progressBarColor.Y * 0.3f, progressBarColor.Z * 0.3f, 0.5f * alpha);
|
|
drawList.AddRectFilled(
|
|
new Vector2(windowPos.X, progressY),
|
|
new Vector2(windowPos.X + windowSize.X, progressY + progressHeight),
|
|
ImGui.ColorConvertFloat4ToU32(bgProgressColor),
|
|
0f
|
|
);
|
|
}
|
|
|
|
private void DrawProgressForeground(ImDrawListPtr drawList, Vector2 windowPos, float progressY, float progressHeight, float progressWidth, Vector4 progressBarColor, float alpha)
|
|
{
|
|
var progressColor = progressBarColor;
|
|
progressColor.W *= alpha;
|
|
drawList.AddRectFilled(
|
|
new Vector2(windowPos.X, progressY),
|
|
new Vector2(windowPos.X + progressWidth, progressY + progressHeight),
|
|
ImGui.ColorConvertFloat4ToU32(progressColor),
|
|
0f
|
|
);
|
|
}
|
|
|
|
private void DrawNotificationText(LightlessNotification notification, float alpha)
|
|
{
|
|
var contentPos = new Vector2(_contentPaddingX, _contentPaddingY);
|
|
var windowSize = ImGui.GetWindowSize();
|
|
var contentWidth = CalculateContentWidth(windowSize.X);
|
|
|
|
ImGui.SetCursorPos(contentPos);
|
|
|
|
var titleHeight = DrawTitle(notification, contentWidth, alpha);
|
|
DrawMessage(notification, contentPos, contentWidth, titleHeight, alpha);
|
|
|
|
if (HasActions(notification))
|
|
{
|
|
PositionActionsAtBottom(windowSize.Y);
|
|
DrawNotificationActions(notification, contentWidth, alpha);
|
|
}
|
|
}
|
|
|
|
private float CalculateContentWidth(float windowWidth) =>
|
|
windowWidth - (_contentPaddingX * 2);
|
|
|
|
private bool HasActions(LightlessNotification notification) =>
|
|
notification.Actions.Count > 0;
|
|
|
|
private void PositionActionsAtBottom(float windowHeight)
|
|
{
|
|
var actionHeight = ImGui.GetFrameHeight();
|
|
var bottomY = windowHeight - _contentPaddingY - actionHeight;
|
|
ImGui.SetCursorPosY(bottomY);
|
|
ImGui.SetCursorPosX(_contentPaddingX);
|
|
}
|
|
|
|
private float DrawTitle(LightlessNotification notification, float contentWidth, float alpha)
|
|
{
|
|
var titleColor = new Vector4(1f, 1f, 1f, alpha);
|
|
var titleText = FormatTitleText(notification);
|
|
|
|
using (ImRaii.PushColor(ImGuiCol.Text, titleColor))
|
|
{
|
|
return DrawWrappedText(titleText, contentWidth);
|
|
}
|
|
}
|
|
|
|
private string FormatTitleText(LightlessNotification notification)
|
|
{
|
|
if (!_configService.Current.ShowNotificationTimestamp)
|
|
return notification.Title;
|
|
|
|
var timestamp = notification.CreatedAt.ToLocalTime().ToString("HH:mm:ss");
|
|
return $"[{timestamp}] {notification.Title}";
|
|
}
|
|
|
|
private float DrawWrappedText(string text, float wrapWidth)
|
|
{
|
|
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + wrapWidth);
|
|
var startY = ImGui.GetCursorPosY();
|
|
ImGui.TextWrapped(text);
|
|
var height = ImGui.GetCursorPosY() - startY;
|
|
ImGui.PopTextWrapPos();
|
|
return height;
|
|
}
|
|
|
|
private void DrawMessage(LightlessNotification notification, Vector2 contentPos, float contentWidth, float titleHeight, float alpha)
|
|
{
|
|
if (string.IsNullOrEmpty(notification.Message)) return;
|
|
|
|
var messagePos = contentPos + new Vector2(0f, titleHeight + _titleMessageSpacing);
|
|
var messageColor = new Vector4(0.9f, 0.9f, 0.9f, alpha);
|
|
|
|
ImGui.SetCursorPos(messagePos);
|
|
|
|
using (ImRaii.PushColor(ImGuiCol.Text, messageColor))
|
|
{
|
|
DrawWrappedText(notification.Message, contentWidth);
|
|
}
|
|
}
|
|
|
|
private void DrawNotificationActions(LightlessNotification notification, float availableWidth, float alpha)
|
|
{
|
|
var buttonWidth = CalculateActionButtonWidth(notification.Actions.Count, availableWidth);
|
|
|
|
_logger.LogDebug("Drawing {ActionCount} notification actions, buttonWidth: {ButtonWidth}, availableWidth: {AvailableWidth}",
|
|
notification.Actions.Count, buttonWidth, availableWidth);
|
|
|
|
var startX = ImGui.GetCursorPosX();
|
|
|
|
for (int i = 0; i < notification.Actions.Count; i++)
|
|
{
|
|
if (i > 0)
|
|
{
|
|
ImGui.SameLine();
|
|
PositionActionButton(i, startX, buttonWidth);
|
|
}
|
|
DrawActionButton(notification.Actions[i], notification, alpha, buttonWidth);
|
|
}
|
|
}
|
|
|
|
private float CalculateActionButtonWidth(int actionCount, float availableWidth)
|
|
{
|
|
var totalSpacing = (actionCount - 1) * _actionButtonSpacing;
|
|
return (availableWidth - totalSpacing) / actionCount;
|
|
}
|
|
|
|
private void PositionActionButton(int index, float startX, float buttonWidth)
|
|
{
|
|
var xPosition = startX + index * (buttonWidth + _actionButtonSpacing);
|
|
ImGui.SetCursorPosX(xPosition);
|
|
}
|
|
|
|
private void DrawActionButton(LightlessNotificationAction action, LightlessNotification notification, float alpha, float buttonWidth)
|
|
{
|
|
_logger.LogDebug("Drawing action button: {ActionId} - {ActionLabel}, width: {ButtonWidth}", action.Id, action.Label, buttonWidth);
|
|
|
|
var buttonColor = action.Color;
|
|
buttonColor.W *= alpha;
|
|
|
|
var hoveredColor = buttonColor * 1.1f;
|
|
hoveredColor.W = buttonColor.W;
|
|
|
|
var activeColor = buttonColor * 0.9f;
|
|
activeColor.W = buttonColor.W;
|
|
|
|
using (ImRaii.PushColor(ImGuiCol.Button, buttonColor))
|
|
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, hoveredColor))
|
|
using (ImRaii.PushColor(ImGuiCol.ButtonActive, activeColor))
|
|
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 1f, 1f, alpha)))
|
|
{
|
|
var buttonPressed = false;
|
|
|
|
if (action.Icon != FontAwesomeIcon.None)
|
|
{
|
|
buttonPressed = DrawIconTextButton(action.Icon, action.Label, buttonWidth, alpha);
|
|
}
|
|
else
|
|
{
|
|
buttonPressed = ImGui.Button(action.Label, new Vector2(buttonWidth, 0));
|
|
}
|
|
|
|
_logger.LogDebug("Button {ActionId} pressed: {ButtonPressed}", action.Id, buttonPressed);
|
|
|
|
if (buttonPressed)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogDebug("Executing action: {ActionId}", action.Id);
|
|
action.OnClick(notification);
|
|
_logger.LogDebug("Action executed successfully: {ActionId}", action.Id);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error executing notification action: {ActionId}", action.Id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool DrawIconTextButton(FontAwesomeIcon icon, string text, float width, float alpha)
|
|
{
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
var cursorPos = ImGui.GetCursorScreenPos();
|
|
var frameHeight = ImGui.GetFrameHeight();
|
|
|
|
Vector2 iconSize;
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
iconSize = ImGui.CalcTextSize(icon.ToIconString());
|
|
}
|
|
|
|
var textSize = ImGui.CalcTextSize(text);
|
|
var spacing = 3f * ImGuiHelpers.GlobalScale;
|
|
var totalTextWidth = iconSize.X + spacing + textSize.X;
|
|
|
|
var buttonPressed = ImGui.InvisibleButton($"btn_{icon}_{text}", new Vector2(width, frameHeight));
|
|
|
|
var buttonMin = ImGui.GetItemRectMin();
|
|
var buttonMax = ImGui.GetItemRectMax();
|
|
var buttonSize = buttonMax - buttonMin;
|
|
|
|
var buttonColor = ImGui.GetColorU32(ImGuiCol.Button);
|
|
if (ImGui.IsItemHovered())
|
|
buttonColor = ImGui.GetColorU32(ImGuiCol.ButtonHovered);
|
|
if (ImGui.IsItemActive())
|
|
buttonColor = ImGui.GetColorU32(ImGuiCol.ButtonActive);
|
|
|
|
drawList.AddRectFilled(buttonMin, buttonMax, buttonColor, 3f);
|
|
|
|
var iconPos = buttonMin + new Vector2((buttonSize.X - totalTextWidth) / 2f, (buttonSize.Y - iconSize.Y) / 2f);
|
|
var textPos = iconPos + new Vector2(iconSize.X + spacing, (iconSize.Y - textSize.Y) / 2f);
|
|
|
|
var textColor = ImGui.GetColorU32(ImGuiCol.Text);
|
|
|
|
// Draw icon
|
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
|
{
|
|
drawList.AddText(iconPos, textColor, icon.ToIconString());
|
|
}
|
|
|
|
// Draw text
|
|
drawList.AddText(textPos, textColor, text);
|
|
|
|
return buttonPressed;
|
|
}
|
|
|
|
private float CalculateNotificationHeight(LightlessNotification notification)
|
|
{
|
|
var contentWidth = CalculateContentWidth(_configService.Current.NotificationWidth);
|
|
var height = 12f;
|
|
|
|
height += CalculateTitleHeight(notification, contentWidth);
|
|
height += CalculateMessageHeight(notification, contentWidth);
|
|
|
|
if (notification.ShowProgress)
|
|
{
|
|
height += 12f;
|
|
}
|
|
|
|
if (notification.Actions.Count > 0)
|
|
{
|
|
height += ImGui.GetStyle().ItemSpacing.Y;
|
|
height += ImGui.GetFrameHeight();
|
|
height += 12f;
|
|
}
|
|
|
|
return Math.Clamp(height, _notificationMinHeight, _notificationMaxHeight);
|
|
}
|
|
|
|
private float CalculateTitleHeight(LightlessNotification notification, float contentWidth)
|
|
{
|
|
var titleText = _configService.Current.ShowNotificationTimestamp
|
|
? $"[{notification.CreatedAt.ToLocalTime():HH:mm:ss}] {notification.Title}"
|
|
: notification.Title;
|
|
|
|
return ImGui.CalcTextSize(titleText, true, contentWidth).Y;
|
|
}
|
|
|
|
private float CalculateMessageHeight(LightlessNotification notification, float contentWidth)
|
|
{
|
|
if (string.IsNullOrEmpty(notification.Message)) return 0f;
|
|
|
|
var messageHeight = ImGui.CalcTextSize(notification.Message, true, contentWidth).Y;
|
|
return 4f + messageHeight;
|
|
}
|
|
|
|
private Vector4 GetNotificationAccentColor(NotificationType type)
|
|
{
|
|
return type switch
|
|
{
|
|
NotificationType.Info => UIColors.Get("LightlessPurple"),
|
|
NotificationType.Warning => UIColors.Get("LightlessYellow"),
|
|
NotificationType.Error => UIColors.Get("DimRed"),
|
|
NotificationType.PairRequest => UIColors.Get("LightlessBlue"),
|
|
NotificationType.Download => UIColors.Get("LightlessGreen"),
|
|
NotificationType.Performance => UIColors.Get("LightlessOrange"),
|
|
|
|
_ => UIColors.Get("LightlessPurple")
|
|
};
|
|
}
|
|
} |