notifcation refactor for better readability

This commit is contained in:
choco
2025-10-11 21:24:39 +02:00
parent c545ccea52
commit a441bbfcc8
4 changed files with 474 additions and 389 deletions

View File

@@ -92,23 +92,17 @@ public class LightlessConfig : ILightlessConfiguration
public int NotificationOffsetX { get; set; } = 0; public int NotificationOffsetX { get; set; } = 0;
public float NotificationWidth { get; set; } = 350f; public float NotificationWidth { get; set; } = 350f;
public float NotificationSpacing { get; set; } = 8f; public float NotificationSpacing { get; set; } = 8f;
public bool NotificationStackUpwards { get; set; } = false;
// Animation & Effects // Animation & Effects
public float NotificationAnimationSpeed { get; set; } = 10f; public float NotificationAnimationSpeed { get; set; } = 10f;
public float NotificationAccentBarWidth { get; set; } = 3f; public float NotificationAccentBarWidth { get; set; } = 3f;
// Typography
public float NotificationFontScale { get; set; } = 1.0f;
// Duration per Type // Duration per Type
public int InfoNotificationDurationSeconds { get; set; } = 10; public int InfoNotificationDurationSeconds { get; set; } = 10;
public int WarningNotificationDurationSeconds { get; set; } = 15; public int WarningNotificationDurationSeconds { get; set; } = 15;
public int ErrorNotificationDurationSeconds { get; set; } = 20; public int ErrorNotificationDurationSeconds { get; set; } = 20;
public int PairRequestDurationSeconds { get; set; } = 180; public int PairRequestDurationSeconds { get; set; } = 180;
public int DownloadNotificationDurationSeconds { get; set; } = 300; public int DownloadNotificationDurationSeconds { get; set; } = 300;
// Sound Settings
public uint CustomInfoSoundId { get; set; } = 2; // Se2 public uint CustomInfoSoundId { get; set; } = 2; // Se2
public uint CustomWarningSoundId { get; set; } = 16; // Se15 public uint CustomWarningSoundId { get; set; } = 16; // Se15
public uint CustomErrorSoundId { get; set; } = 16; // Se15 public uint CustomErrorSoundId { get; set; } = 16; // Se15
@@ -118,7 +112,7 @@ public class LightlessConfig : ILightlessConfiguration
public bool DisableWarningSound { get; set; } = false; public bool DisableWarningSound { get; set; } = false;
public bool DisableErrorSound { get; set; } = false; public bool DisableErrorSound { get; set; } = false;
public bool DisablePairRequestSound { get; set; } = false; public bool DisablePairRequestSound { get; set; } = false;
public bool DisableDownloadSound { get; set; } = true; // Disabled by default public bool DisableDownloadSound { get; set; } = true; // Disabled by default (annoying)
public bool UseFocusTarget { get; set; } = false; public bool UseFocusTarget { get; set; } = false;
public bool overrideFriendColor { get; set; } = false; public bool overrideFriendColor { get; set; } = false;
public bool overridePartyColor { get; set; } = false; public bool overridePartyColor { get; set; } = false;

View File

@@ -39,14 +39,29 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
Mediator.Subscribe<NotificationMessage>(this, HandleNotificationMessage); Mediator.Subscribe<NotificationMessage>(this, HandleNotificationMessage);
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
{
return Task.CompletedTask;
}
public void ShowNotification(string title, string message, NotificationType type = NotificationType.Info, public void ShowNotification(string title, string message, NotificationType type = NotificationType.Info,
TimeSpan? duration = null, List<LightlessNotificationAction>? actions = null, uint? soundEffectId = null) TimeSpan? duration = null, List<LightlessNotificationAction>? actions = null, uint? soundEffectId = null)
{ {
var notification = new LightlessNotification var notification = CreateNotification(title, message, type, duration, actions, soundEffectId);
if (_configService.Current.AutoDismissOnAction && notification.Actions.Any())
{
WrapActionsWithAutoDismiss(notification);
}
if (notification.SoundEffectId.HasValue)
{
PlayNotificationSound(notification.SoundEffectId.Value);
}
Mediator.Publish(new LightlessNotificationMessage(notification));
}
private LightlessNotification CreateNotification(string title, string message, NotificationType type,
TimeSpan? duration, List<LightlessNotificationAction>? actions, uint? soundEffectId)
{
return new LightlessNotification
{ {
Title = title, Title = title,
Message = message, Message = message,
@@ -57,8 +72,9 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
ShowProgress = _configService.Current.ShowNotificationProgress, ShowProgress = _configService.Current.ShowNotificationProgress,
CreatedAt = DateTime.UtcNow CreatedAt = DateTime.UtcNow
}; };
}
if (_configService.Current.AutoDismissOnAction && notification.Actions.Any()) private void WrapActionsWithAutoDismiss(LightlessNotification notification)
{ {
foreach (var action in notification.Actions) foreach (var action in notification.Actions)
{ {
@@ -68,19 +84,16 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
originalOnClick(n); originalOnClick(n);
if (_configService.Current.AutoDismissOnAction) if (_configService.Current.AutoDismissOnAction)
{ {
n.IsDismissed = true; DismissNotification(n);
n.IsAnimatingOut = true;
} }
}; };
} }
} }
if (notification.SoundEffectId.HasValue) private void DismissNotification(LightlessNotification notification)
{ {
PlayNotificationSound(notification.SoundEffectId.Value); notification.IsDismissed = true;
} notification.IsAnimatingOut = true;
Mediator.Publish(new LightlessNotificationMessage(notification));
} }
public void ShowPairRequestNotification(string senderName, string senderId, Action onAccept, Action onDecline) public void ShowPairRequestNotification(string senderName, string senderId, Action onAccept, Action onDecline)
{ {
@@ -90,8 +103,24 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
Message = $"{senderName} wants to directly pair with you.", Message = $"{senderName} wants to directly pair with you.",
Type = NotificationType.PairRequest, Type = NotificationType.PairRequest,
Duration = TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds), Duration = TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds),
SoundEffectId = !_configService.Current.DisablePairRequestSound ? _configService.Current.PairRequestSoundId : null, SoundEffectId = GetPairRequestSoundId(),
Actions = new List<LightlessNotificationAction> Actions = CreatePairRequestActions(onAccept, onDecline)
};
if (notification.SoundEffectId.HasValue)
{
PlayNotificationSound(notification.SoundEffectId.Value);
}
Mediator.Publish(new LightlessNotificationMessage(notification));
}
private uint? GetPairRequestSoundId() =>
!_configService.Current.DisablePairRequestSound ? _configService.Current.PairRequestSoundId : null;
private List<LightlessNotificationAction> CreatePairRequestActions(Action onAccept, Action onDecline)
{
return new List<LightlessNotificationAction>
{ {
new() new()
{ {
@@ -104,8 +133,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
{ {
_logger.LogInformation("Pair request accepted"); _logger.LogInformation("Pair request accepted");
onAccept(); onAccept();
n.IsDismissed = true; DismissNotification(n);
n.IsAnimatingOut = true;
} }
}, },
new() new()
@@ -119,11 +147,21 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
{ {
_logger.LogInformation("Pair request declined"); _logger.LogInformation("Pair request declined");
onDecline(); onDecline();
n.IsDismissed = true; DismissNotification(n);
n.IsAnimatingOut = true;
} }
} }
};
} }
public void ShowDownloadCompleteNotification(string fileName, int fileCount, Action? onOpenFolder = null)
{
var notification = new LightlessNotification
{
Title = "Download Complete",
Message = FormatDownloadCompleteMessage(fileName, fileCount),
Type = NotificationType.Info,
Duration = TimeSpan.FromSeconds(8),
Actions = CreateDownloadCompleteActions(onOpenFolder),
SoundEffectId = NotificationSounds.DownloadComplete
}; };
if (notification.SoundEffectId.HasValue) if (notification.SoundEffectId.HasValue)
@@ -133,7 +171,13 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
Mediator.Publish(new LightlessNotificationMessage(notification)); Mediator.Publish(new LightlessNotificationMessage(notification));
} }
public void ShowDownloadCompleteNotification(string fileName, int fileCount, Action? onOpenFolder = null)
private string FormatDownloadCompleteMessage(string fileName, int fileCount) =>
fileCount > 1
? $"Downloaded {fileCount} files successfully."
: $"Downloaded {fileName} successfully.";
private List<LightlessNotificationAction> CreateDownloadCompleteActions(Action? onOpenFolder)
{ {
var actions = new List<LightlessNotificationAction>(); var actions = new List<LightlessNotificationAction>();
@@ -148,21 +192,23 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
OnClick = (n) => OnClick = (n) =>
{ {
onOpenFolder(); onOpenFolder();
n.IsDismissed = true; DismissNotification(n);
n.IsAnimatingOut = true;
} }
}); });
} }
return actions;
}
public void ShowErrorNotification(string title, string message, Exception? exception = null, Action? onRetry = null, Action? onViewLog = null)
{
var notification = new LightlessNotification var notification = new LightlessNotification
{ {
Title = "Download Complete", Title = title,
Message = fileCount > 1 ? Message = FormatErrorMessage(message, exception),
$"Downloaded {fileCount} files successfully." : Type = NotificationType.Error,
$"Downloaded {fileName} successfully.", Duration = TimeSpan.FromSeconds(15),
Type = NotificationType.Info, Actions = CreateErrorActions(onRetry, onViewLog),
Duration = TimeSpan.FromSeconds(8), SoundEffectId = NotificationSounds.Error
Actions = actions,
SoundEffectId = NotificationSounds.DownloadComplete
}; };
if (notification.SoundEffectId.HasValue) if (notification.SoundEffectId.HasValue)
@@ -172,9 +218,14 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
Mediator.Publish(new LightlessNotificationMessage(notification)); Mediator.Publish(new LightlessNotificationMessage(notification));
} }
public void ShowErrorNotification(string title, string message, Exception? exception = null, Action? onRetry = null, Action? onViewLog = null)
private string FormatErrorMessage(string message, Exception? exception) =>
exception != null ? $"{message}\n\nError: {exception.Message}" : message;
private List<LightlessNotificationAction> CreateErrorActions(Action? onRetry, Action? onViewLog)
{ {
var actions = new List<LightlessNotificationAction>(); var actions = new List<LightlessNotificationAction>();
if (onRetry != null) if (onRetry != null)
{ {
actions.Add(new LightlessNotificationAction actions.Add(new LightlessNotificationAction
@@ -186,11 +237,11 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
OnClick = (n) => OnClick = (n) =>
{ {
onRetry(); onRetry();
n.IsDismissed = true; DismissNotification(n);
n.IsAnimatingOut = true;
} }
}); });
} }
if (onViewLog != null) if (onViewLog != null)
{ {
actions.Add(new LightlessNotificationAction actions.Add(new LightlessNotificationAction
@@ -202,65 +253,14 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
OnClick = (n) => onViewLog() OnClick = (n) => onViewLog()
}); });
} }
var notification = new LightlessNotification
{
Title = title,
Message = exception != null ? $"{message}\n\nError: {exception.Message}" : message,
Type = NotificationType.Error,
Duration = TimeSpan.FromSeconds(15),
Actions = actions,
SoundEffectId = NotificationSounds.Error
};
if (notification.SoundEffectId.HasValue) return actions;
{
PlayNotificationSound(notification.SoundEffectId.Value);
}
Mediator.Publish(new LightlessNotificationMessage(notification));
} }
public void ShowPairDownloadNotification(List<(string playerName, float progress, string status)> downloadStatus, int queueWaiting = 0) public void ShowPairDownloadNotification(List<(string playerName, float progress, string status)> downloadStatus, int queueWaiting = 0)
{ {
var userDownloads = downloadStatus.Where(x => x.playerName != "Pair Queue").ToList(); var userDownloads = downloadStatus.Where(x => x.playerName != "Pair Queue").ToList();
var totalProgress = userDownloads.Count > 0 ? userDownloads.Average(x => x.progress) : 0f; var totalProgress = userDownloads.Count > 0 ? userDownloads.Average(x => x.progress) : 0f;
var completedCount = userDownloads.Count(x => x.progress >= 1.0f); var message = BuildPairDownloadMessage(userDownloads, queueWaiting);
var totalCount = userDownloads.Count;
var message = "";
if (queueWaiting > 0)
{
message = $"Queue: {queueWaiting} waiting";
}
if (totalCount > 0)
{
var progressMessage = $"Progress: {completedCount}/{totalCount} completed";
message = string.IsNullOrEmpty(message) ? progressMessage : $"{message}\n{progressMessage}";
}
if (userDownloads.Any(x => x.progress < 1.0f))
{
var maxNamesToShow = _configService.Current.MaxConcurrentPairApplications;
var activeDownloads = userDownloads.Where(x => x.progress < 1.0f).Take(maxNamesToShow);
var downloadLines = string.Join("\n", activeDownloads.Select(x =>
{
var statusText = x.status switch
{
"downloading" => $"{x.progress:P0}",
"decompressing" => "decompressing",
"queued" => "queued",
"waiting" => "waiting for slot",
_ => x.status
};
return $"• {x.playerName}: {statusText}";
}));
message += string.IsNullOrEmpty(message) ? downloadLines : $"\n{downloadLines}";
}
var allDownloadsCompleted = userDownloads.All(x => x.progress >= 1.0f) && userDownloads.Any();
var notification = new LightlessNotification var notification = new LightlessNotification
{ {
@@ -272,38 +272,83 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
ShowProgress = true, ShowProgress = true,
Progress = totalProgress Progress = totalProgress
}; };
Mediator.Publish(new LightlessNotificationMessage(notification)); Mediator.Publish(new LightlessNotificationMessage(notification));
if (allDownloadsCompleted)
if (AreAllDownloadsCompleted(userDownloads))
{ {
DismissPairDownloadNotification(); DismissPairDownloadNotification();
} }
} }
public void DismissPairDownloadNotification() private string BuildPairDownloadMessage(List<(string playerName, float progress, string status)> userDownloads, int queueWaiting)
{ {
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress")); var messageParts = new List<string>();
if (queueWaiting > 0)
{
messageParts.Add($"Queue: {queueWaiting} waiting");
} }
private TimeSpan GetDefaultDurationForType(NotificationType type) if (userDownloads.Count > 0)
{ {
return type switch var completedCount = userDownloads.Count(x => x.progress >= 1.0f);
messageParts.Add($"Progress: {completedCount}/{userDownloads.Count} completed");
}
var activeDownloadLines = BuildActiveDownloadLines(userDownloads);
if (!string.IsNullOrEmpty(activeDownloadLines))
{
messageParts.Add(activeDownloadLines);
}
return string.Join("\n", messageParts);
}
private string BuildActiveDownloadLines(List<(string playerName, float progress, string status)> userDownloads)
{
var activeDownloads = userDownloads
.Where(x => x.progress < 1.0f)
.Take(_configService.Current.MaxConcurrentPairApplications);
if (!activeDownloads.Any()) return string.Empty;
return string.Join("\n", activeDownloads.Select(x => $"• {x.playerName}: {FormatDownloadStatus(x)}"));
}
private string FormatDownloadStatus((string playerName, float progress, string status) download) => download.status switch
{
"downloading" => $"{download.progress:P0}",
"decompressing" => "decompressing",
"queued" => "queued",
"waiting" => "waiting for slot",
_ => download.status
};
private bool AreAllDownloadsCompleted(List<(string playerName, float progress, string status)> userDownloads) =>
userDownloads.Any() && userDownloads.All(x => x.progress >= 1.0f);
public void DismissPairDownloadNotification() =>
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress"));
private TimeSpan GetDefaultDurationForType(NotificationType type) => type switch
{ {
NotificationType.Info => TimeSpan.FromSeconds(_configService.Current.InfoNotificationDurationSeconds), NotificationType.Info => TimeSpan.FromSeconds(_configService.Current.InfoNotificationDurationSeconds),
NotificationType.Warning => TimeSpan.FromSeconds(_configService.Current.WarningNotificationDurationSeconds), NotificationType.Warning => TimeSpan.FromSeconds(_configService.Current.WarningNotificationDurationSeconds),
NotificationType.Error => TimeSpan.FromSeconds(_configService.Current.ErrorNotificationDurationSeconds), NotificationType.Error => TimeSpan.FromSeconds(_configService.Current.ErrorNotificationDurationSeconds),
NotificationType.PairRequest => TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds), NotificationType.PairRequest => TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds),
NotificationType.Download => TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds), NotificationType.Download => TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds),
_ => TimeSpan.FromSeconds(10) // Fallback for any unknown types _ => TimeSpan.FromSeconds(10)
}; };
}
private uint? GetSoundEffectId(NotificationType type, uint? overrideSoundId) private uint? GetSoundEffectId(NotificationType type, uint? overrideSoundId)
{ {
if (overrideSoundId.HasValue) if (overrideSoundId.HasValue) return overrideSoundId;
return overrideSoundId; if (IsSoundDisabledForType(type)) return null;
return GetConfiguredSoundForType(type);
}
// Check if this specific notification type is disabled private bool IsSoundDisabledForType(NotificationType type) => type switch
bool isDisabled = type switch
{ {
NotificationType.Info => _configService.Current.DisableInfoSound, NotificationType.Info => _configService.Current.DisableInfoSound,
NotificationType.Warning => _configService.Current.DisableWarningSound, NotificationType.Warning => _configService.Current.DisableWarningSound,
@@ -312,11 +357,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
_ => false _ => false
}; };
if (isDisabled) private uint GetConfiguredSoundForType(NotificationType type) => type switch
return null;
// Return the configured sound for this type
return type switch
{ {
NotificationType.Info => _configService.Current.CustomInfoSoundId, NotificationType.Info => _configService.Current.CustomInfoSoundId,
NotificationType.Warning => _configService.Current.CustomWarningSoundId, NotificationType.Warning => _configService.Current.CustomWarningSoundId,
@@ -324,7 +365,6 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
NotificationType.Download => _configService.Current.DownloadSoundId, NotificationType.Download => _configService.Current.DownloadSoundId,
_ => NotificationSounds.GetDefaultSound(type) _ => NotificationSounds.GetDefaultSound(type)
}; };
}
private void PlayNotificationSound(uint soundEffectId) private void PlayNotificationSound(uint soundEffectId)
{ {
@@ -342,12 +382,18 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
private void HandleNotificationMessage(NotificationMessage msg) private void HandleNotificationMessage(NotificationMessage msg)
{ {
_logger.LogInformation("{msg}", msg.ToString()); _logger.LogInformation("{msg}", msg.ToString());
if (!_dalamudUtilService.IsLoggedIn) return; if (!_dalamudUtilService.IsLoggedIn) return;
// Get notification location based on type and system preference var location = GetNotificationLocation(msg.Type);
var location = _configService.Current.UseLightlessNotifications ShowNotificationLocationBased(msg, location);
? msg.Type switch }
private NotificationLocation GetNotificationLocation(NotificationType type) =>
_configService.Current.UseLightlessNotifications
? GetLightlessNotificationLocation(type)
: GetClassicNotificationLocation(type);
private NotificationLocation GetLightlessNotificationLocation(NotificationType type) => type switch
{ {
NotificationType.Info => _configService.Current.LightlessInfoNotification, NotificationType.Info => _configService.Current.LightlessInfoNotification,
NotificationType.Warning => _configService.Current.LightlessWarningNotification, NotificationType.Warning => _configService.Current.LightlessWarningNotification,
@@ -355,8 +401,9 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
NotificationType.PairRequest => _configService.Current.LightlessPairRequestNotification, NotificationType.PairRequest => _configService.Current.LightlessPairRequestNotification,
NotificationType.Download => _configService.Current.LightlessDownloadNotification, NotificationType.Download => _configService.Current.LightlessDownloadNotification,
_ => NotificationLocation.LightlessUi _ => NotificationLocation.LightlessUi
} };
: msg.Type switch
private NotificationLocation GetClassicNotificationLocation(NotificationType type) => type switch
{ {
NotificationType.Info => _configService.Current.InfoNotification, NotificationType.Info => _configService.Current.InfoNotification,
NotificationType.Warning => _configService.Current.WarningNotification, NotificationType.Warning => _configService.Current.WarningNotification,
@@ -366,9 +413,6 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
_ => NotificationLocation.Nowhere _ => NotificationLocation.Nowhere
}; };
ShowNotificationLocationBased(msg, location);
}
private void ShowNotificationLocationBased(NotificationMessage msg, NotificationLocation location) private void ShowNotificationLocationBased(NotificationMessage msg, NotificationLocation location)
{ {
switch (location) switch (location)
@@ -403,18 +447,12 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
private void ShowLightlessNotification(NotificationMessage msg) private void ShowLightlessNotification(NotificationMessage msg)
{ {
var duration = msg.TimeShownOnScreen ?? GetDefaultDurationForType(msg.Type); var duration = msg.TimeShownOnScreen ?? GetDefaultDurationForType(msg.Type);
// GetSoundEffectId will handle checking if the sound is disabled
ShowNotification(msg.Title ?? "Lightless Sync", msg.Message ?? string.Empty, msg.Type, duration, null, null); ShowNotification(msg.Title ?? "Lightless Sync", msg.Message ?? string.Empty, msg.Type, duration, null, null);
} }
private void ShowToast(NotificationMessage msg) private void ShowToast(NotificationMessage msg)
{ {
Dalamud.Interface.ImGuiNotification.NotificationType dalamudType = msg.Type switch var dalamudType = ConvertToDalamudNotificationType(msg.Type);
{
NotificationType.Error => Dalamud.Interface.ImGuiNotification.NotificationType.Error,
NotificationType.Warning => Dalamud.Interface.ImGuiNotification.NotificationType.Warning,
_ => Dalamud.Interface.ImGuiNotification.NotificationType.Info
};
_notificationManager.AddNotification(new Notification() _notificationManager.AddNotification(new Notification()
{ {
@@ -426,6 +464,13 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
}); });
} }
private Dalamud.Interface.ImGuiNotification.NotificationType ConvertToDalamudNotificationType(NotificationType type) => type switch
{
NotificationType.Error => Dalamud.Interface.ImGuiNotification.NotificationType.Error,
NotificationType.Warning => Dalamud.Interface.ImGuiNotification.NotificationType.Warning,
_ => Dalamud.Interface.ImGuiNotification.NotificationType.Info
};
private void ShowChat(NotificationMessage msg) private void ShowChat(NotificationMessage msg)
{ {
switch (msg.Type) switch (msg.Type)

View File

@@ -17,13 +17,16 @@ namespace LightlessSync.UI;
public class LightlessNotificationUI : WindowMediatorSubscriberBase 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 readonly List<LightlessNotification> _notifications = new(); private readonly List<LightlessNotification> _notifications = new();
private readonly object _notificationLock = new(); private readonly object _notificationLock = new();
private readonly LightlessConfigService _configService; private readonly LightlessConfigService _configService;
private const float NotificationMinHeight = 60f;
private const float NotificationMaxHeight = 250f;
public LightlessNotificationUI(ILogger<LightlessNotificationUI> logger, LightlessMediator mediator, PerformanceCollectorService performanceCollector, LightlessConfigService configService) public LightlessNotificationUI(ILogger<LightlessNotificationUI> logger, LightlessMediator mediator, PerformanceCollectorService performanceCollector, LightlessConfigService configService)
: base(logger, mediator, "Lightless Notifications##LightlessNotifications", performanceCollector) : base(logger, mediator, "Lightless Notifications##LightlessNotifications", performanceCollector)
{ {
@@ -49,15 +52,11 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
Mediator.Subscribe<LightlessNotificationMessage>(this, HandleNotificationMessage); Mediator.Subscribe<LightlessNotificationMessage>(this, HandleNotificationMessage);
Mediator.Subscribe<LightlessNotificationDismissMessage>(this, HandleNotificationDismissMessage); Mediator.Subscribe<LightlessNotificationDismissMessage>(this, HandleNotificationDismissMessage);
} }
private void HandleNotificationMessage(LightlessNotificationMessage message) private void HandleNotificationMessage(LightlessNotificationMessage message) =>
{
AddNotification(message.Notification); AddNotification(message.Notification);
}
private void HandleNotificationDismissMessage(LightlessNotificationDismissMessage message) private void HandleNotificationDismissMessage(LightlessNotificationDismissMessage message) =>
{
RemoveNotification(message.NotificationId); RemoveNotification(message.NotificationId);
}
public void AddNotification(LightlessNotification notification) public void AddNotification(LightlessNotification notification)
{ {
@@ -66,12 +65,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
var existingNotification = _notifications.FirstOrDefault(n => n.Id == notification.Id); var existingNotification = _notifications.FirstOrDefault(n => n.Id == notification.Id);
if (existingNotification != null) if (existingNotification != null)
{ {
// Update existing notification without restarting animation UpdateExistingNotification(existingNotification, notification);
existingNotification.Message = notification.Message;
existingNotification.Progress = notification.Progress;
existingNotification.ShowProgress = notification.ShowProgress;
existingNotification.Title = notification.Title;
_logger.LogDebug("Updated existing notification: {Title}", notification.Title);
} }
else else
{ {
@@ -79,11 +73,17 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
_logger.LogDebug("Added new notification: {Title}", notification.Title); _logger.LogDebug("Added new notification: {Title}", notification.Title);
} }
if (!IsOpen) if (!IsOpen) IsOpen = true;
}
}
private void UpdateExistingNotification(LightlessNotification existing, LightlessNotification updated)
{ {
IsOpen = true; existing.Message = updated.Message;
} existing.Progress = updated.Progress;
} existing.ShowProgress = updated.ShowProgress;
existing.Title = updated.Title;
_logger.LogDebug("Updated existing notification: {Title}", updated.Title);
} }
public void RemoveNotification(string id) public void RemoveNotification(string id)
@@ -92,12 +92,17 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
{ {
var notification = _notifications.FirstOrDefault(n => n.Id == id); var notification = _notifications.FirstOrDefault(n => n.Id == id);
if (notification != null) if (notification != null)
{
StartOutAnimation(notification);
}
}
}
private void StartOutAnimation(LightlessNotification notification)
{ {
notification.IsAnimatingOut = true; notification.IsAnimatingOut = true;
notification.IsAnimatingIn = false; notification.IsAnimatingIn = false;
} }
}
}
protected override void DrawInternal() protected override void DrawInternal()
{ {
@@ -115,17 +120,25 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
} }
var viewport = ImGui.GetMainViewport(); var viewport = ImGui.GetMainViewport();
Position = CalculateWindowPosition(viewport);
DrawAllNotifications();
}
// Always position at top (choco doesnt know how to handle top positions how fitting) ImGui.PopStyleVar();
var baseX = viewport.WorkPos.X + viewport.WorkSize.X - _configService.Current.NotificationWidth - _configService.Current.NotificationOffsetX - 6f ; }
var baseY = viewport.WorkPos.Y;
// Apply Y offset private Vector2 CalculateWindowPosition(ImGuiViewportPtr viewport)
var finalY = baseY + _configService.Current.NotificationOffsetY; {
var x = viewport.WorkPos.X + viewport.WorkSize.X -
// Update position _configService.Current.NotificationWidth -
Position = new Vector2(baseX, finalY); _configService.Current.NotificationOffsetX -
WindowPaddingOffset;
var y = viewport.WorkPos.Y + _configService.Current.NotificationOffsetY;
return new Vector2(x, y);
}
private void DrawAllNotifications()
{
for (int i = 0; i < _notifications.Count; i++) for (int i = 0; i < _notifications.Count; i++)
{ {
DrawNotification(_notifications[i], i); DrawNotification(_notifications[i], i);
@@ -137,13 +150,15 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
} }
} }
ImGui.PopStyleVar();
}
private void UpdateNotifications() private void UpdateNotifications()
{ {
var deltaTime = ImGui.GetIO().DeltaTime; var deltaTime = ImGui.GetIO().DeltaTime;
EnforceMaxNotificationLimit();
UpdateAnimationsAndRemoveExpired(deltaTime);
}
private void EnforceMaxNotificationLimit()
{
var maxNotifications = _configService.Current.MaxSimultaneousNotifications; var maxNotifications = _configService.Current.MaxSimultaneousNotifications;
while (_notifications.Count(n => !n.IsAnimatingOut) > maxNotifications) while (_notifications.Count(n => !n.IsAnimatingOut) > maxNotifications)
{ {
@@ -151,60 +166,70 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
.Where(n => !n.IsAnimatingOut) .Where(n => !n.IsAnimatingOut)
.OrderBy(n => n.CreatedAt) .OrderBy(n => n.CreatedAt)
.FirstOrDefault(); .FirstOrDefault();
if (oldestNotification != null) if (oldestNotification != null)
{ {
oldestNotification.IsAnimatingOut = true; StartOutAnimation(oldestNotification);
oldestNotification.IsAnimatingIn = false;
} }
} }
}
private void UpdateAnimationsAndRemoveExpired(float deltaTime)
{
for (int i = _notifications.Count - 1; i >= 0; i--) for (int i = _notifications.Count - 1; i >= 0; i--)
{ {
var notification = _notifications[i]; var notification = _notifications[i];
UpdateNotificationAnimation(notification, deltaTime);
if (notification.IsAnimatingIn && notification.AnimationProgress < 1f) if (ShouldRemoveNotification(notification))
{
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 * 0.7f));
}
else if (!notification.IsAnimatingOut && !notification.IsDismissed)
{
notification.IsAnimatingIn = false;
if (notification.IsExpired && !notification.IsAnimatingOut)
{
notification.IsAnimatingOut = true;
notification.IsAnimatingIn = false;
}
}
if (notification.IsAnimatingOut && notification.AnimationProgress <= 0.01f)
{ {
_notifications.RemoveAt(i); _notifications.RemoveAt(i);
} }
} }
} }
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 bool ShouldRemoveNotification(LightlessNotification notification) =>
notification.IsAnimatingOut && notification.AnimationProgress <= 0.01f;
private void DrawNotification(LightlessNotification notification, int index) private void DrawNotification(LightlessNotification notification, int index)
{ {
var alpha = notification.AnimationProgress; var alpha = notification.AnimationProgress;
if (alpha <= 0f) return;
if (alpha <= 0f) var slideOffset = (1f - alpha) * SlideAnimationDistance;
return;
var slideOffset = (1f - alpha) * 100f; // Fixed slide distance
var originalCursorPos = ImGui.GetCursorPos(); var originalCursorPos = ImGui.GetCursorPos();
ImGui.SetCursorPosX(originalCursorPos.X + slideOffset); ImGui.SetCursorPosX(originalCursorPos.X + slideOffset);
var notificationHeight = CalculateNotificationHeight(notification); var notificationHeight = CalculateNotificationHeight(notification);
var notificationWidth = _configService.Current.NotificationWidth - slideOffset;
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
using var child = ImRaii.Child($"notification_{notification.Id}", using var child = ImRaii.Child($"notification_{notification.Id}",
new Vector2(_configService.Current.NotificationWidth - slideOffset, notificationHeight), new Vector2(notificationWidth, notificationHeight),
false, ImGuiWindowFlags.NoScrollbar); false, ImGuiWindowFlags.NoScrollbar);
if (child.Success) if (child.Success)
@@ -221,50 +246,69 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
var windowPos = ImGui.GetWindowPos(); var windowPos = ImGui.GetWindowPos();
var windowSize = ImGui.GetWindowSize(); 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);
DrawNotificationText(notification, alpha);
}
private Vector4 CalculateBackgroundColor(float alpha, bool isHovered)
{
var baseOpacity = _configService.Current.NotificationOpacity; var baseOpacity = _configService.Current.NotificationOpacity;
var finalOpacity = baseOpacity * alpha; var finalOpacity = baseOpacity * alpha;
var bgColor = new Vector4(30f/255f, 30f/255f, 30f/255f, finalOpacity); var bgColor = new Vector4(30f/255f, 30f/255f, 30f/255f, finalOpacity);
var accentColor = GetNotificationAccentColor(notification.Type);
var progressBarColor = UIColors.Get("LightlessBlue");
accentColor.W *= alpha; if (isHovered)
{
bgColor *= 1.1f;
bgColor.W = Math.Min(bgColor.W, 0.98f);
}
// Draw shadow with fixed intensity return bgColor;
}
private void DrawShadow(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, float alpha)
{
var shadowOffset = new Vector2(1f, 1f); var shadowOffset = new Vector2(1f, 1f);
var shadowAlpha = 0.4f * alpha; var shadowColor = new Vector4(0f, 0f, 0f, 0.4f * alpha);
var shadowColor = new Vector4(0f, 0f, 0f, shadowAlpha);
drawList.AddRectFilled( drawList.AddRectFilled(
windowPos + shadowOffset, windowPos + shadowOffset,
windowPos + windowSize + shadowOffset, windowPos + windowSize + shadowOffset,
ImGui.ColorConvertFloat4ToU32(shadowColor), ImGui.ColorConvertFloat4ToU32(shadowColor),
3f 3f
); );
}
var isHovered = ImGui.IsWindowHovered(); private void HandleClickToDismiss(LightlessNotification notification)
if (isHovered)
{ {
bgColor = bgColor * 1.1f; if (ImGui.IsWindowHovered() &&
bgColor.W = Math.Min(bgColor.W, 0.98f); _configService.Current.DismissNotificationOnClick &&
// Handle click-to-dismiss for notifications without actions
if (_configService.Current.DismissNotificationOnClick &&
!notification.Actions.Any() && !notification.Actions.Any() &&
ImGui.IsMouseClicked(ImGuiMouseButton.Left)) ImGui.IsMouseClicked(ImGuiMouseButton.Left))
{ {
notification.IsDismissed = true; notification.IsDismissed = true;
notification.IsAnimatingOut = true; StartOutAnimation(notification);
} }
} }
private void DrawBackground(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, Vector4 bgColor)
{
drawList.AddRectFilled( drawList.AddRectFilled(
windowPos, windowPos,
windowPos + windowSize, windowPos + windowSize,
ImGui.ColorConvertFloat4ToU32(bgColor), ImGui.ColorConvertFloat4ToU32(bgColor),
3f 3f
); );
}
// Draw accent bar on left side of the notif (only if width > 0) private void DrawAccentBar(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, Vector4 accentColor)
{
var accentWidth = _configService.Current.NotificationAccentBarWidth; var accentWidth = _configService.Current.NotificationAccentBarWidth;
if (accentWidth > 0f) if (accentWidth > 0f)
{ {
@@ -275,30 +319,37 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
3f 3f
); );
} }
DrawDurationProgressBar(notification, alpha, windowPos, windowSize, progressBarColor, drawList);
DrawNotificationText(notification, alpha);
} }
private void DrawDurationProgressBar(LightlessNotification notification, float alpha, Vector2 windowPos, Vector2 windowSize, Vector4 progressBarColor, ImDrawListPtr drawList) private void DrawDurationProgressBar(LightlessNotification notification, float alpha, Vector2 windowPos, Vector2 windowSize, ImDrawListPtr drawList)
{ {
// For download notifications, use download progress instead of duration var progress = CalculateProgress(notification);
float progress; var progressBarColor = UIColors.Get("LightlessBlue");
if (notification.Type == NotificationType.Download && notification.ShowProgress)
{
progress = Math.Clamp(notification.Progress, 0f, 1f);
}
else
{
var elapsed = DateTime.UtcNow - notification.CreatedAt;
progress = Math.Min(1.0f, (float)(elapsed.TotalSeconds / notification.Duration.TotalSeconds));
}
var progressHeight = 2f; var progressHeight = 2f;
var progressY = windowPos.Y + windowSize.Y - progressHeight; var progressY = windowPos.Y + windowSize.Y - progressHeight;
var progressWidth = windowSize.X * progress; 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 CalculateProgress(LightlessNotification notification)
{
if (notification.Type == NotificationType.Download && notification.ShowProgress)
{
return Math.Clamp(notification.Progress, 0f, 1f);
}
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); var bgProgressColor = new Vector4(progressBarColor.X * 0.3f, progressBarColor.Y * 0.3f, progressBarColor.Z * 0.3f, 0.5f * alpha);
drawList.AddRectFilled( drawList.AddRectFilled(
new Vector2(windowPos.X, progressY), new Vector2(windowPos.X, progressY),
@@ -306,8 +357,9 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
ImGui.ColorConvertFloat4ToU32(bgProgressColor), ImGui.ColorConvertFloat4ToU32(bgProgressColor),
0f 0f
); );
}
if (progress > 0) private void DrawProgressForeground(ImDrawListPtr drawList, Vector2 windowPos, float progressY, float progressHeight, float progressWidth, Vector4 progressBarColor, float alpha)
{ {
var progressColor = progressBarColor; var progressColor = progressBarColor;
progressColor.W *= alpha; progressColor.W *= alpha;
@@ -318,44 +370,51 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
0f 0f
); );
} }
}
private void DrawNotificationText(LightlessNotification notification, float alpha) private void DrawNotificationText(LightlessNotification notification, float alpha)
{ {
var padding = new Vector2(10f, 6f); var padding = new Vector2(10f, 6f);
var contentPos = new Vector2(padding.X, padding.Y); var contentPos = new Vector2(padding.X, padding.Y);
var windowSize = ImGui.GetWindowSize(); var windowSize = ImGui.GetWindowSize();
var contentSize = new Vector2(windowSize.X - padding.X, windowSize.Y - padding.Y * 2); var contentSize = new Vector2(windowSize.X - padding.X, windowSize.Y - padding.Y * 2);
ImGui.SetCursorPos(contentPos); ImGui.SetCursorPos(contentPos);
float titleHeight = 0f; var titleHeight = DrawTitle(notification, contentSize.X, alpha);
DrawMessage(notification, contentPos, contentSize.X, titleHeight, alpha);
if (notification.Actions.Count > 0)
{
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetStyle().ItemSpacing.Y);
ImGui.SetCursorPosX(contentPos.X);
DrawNotificationActions(notification, contentSize.X, alpha);
}
}
private float DrawTitle(LightlessNotification notification, float contentWidth, float alpha)
{
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 1f, 1f, alpha))) using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 1f, 1f, alpha)))
{ {
// Set text wrap position to prevent title overflow ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentWidth);
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentSize.X);
var titleStartY = ImGui.GetCursorPosY(); var titleStartY = ImGui.GetCursorPosY();
if (_configService.Current.ShowNotificationTimestamp) var titleText = _configService.Current.ShowNotificationTimestamp
{ ? $"[{notification.CreatedAt.ToLocalTime():HH:mm:ss}] {notification.Title}"
var timestamp = notification.CreatedAt.ToLocalTime().ToString("HH:mm:ss"); : notification.Title;
ImGui.TextWrapped($"[{timestamp}] {notification.Title}");
}
else
{
ImGui.TextWrapped(notification.Title);
}
titleHeight = ImGui.GetCursorPosY() - titleStartY; ImGui.TextWrapped(titleText);
var titleHeight = ImGui.GetCursorPosY() - titleStartY;
ImGui.PopTextWrapPos(); ImGui.PopTextWrapPos();
return titleHeight;
}
} }
if (!string.IsNullOrEmpty(notification.Message)) private void DrawMessage(LightlessNotification notification, Vector2 contentPos, float contentWidth, float titleHeight, float alpha)
{ {
if (string.IsNullOrEmpty(notification.Message)) return;
ImGui.SetCursorPos(contentPos + new Vector2(0f, titleHeight + 4f)); ImGui.SetCursorPos(contentPos + new Vector2(0f, titleHeight + 4f));
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentSize.X); ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentWidth);
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(0.9f, 0.9f, 0.9f, alpha))) using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(0.9f, 0.9f, 0.9f, alpha)))
{ {
ImGui.TextWrapped(notification.Message); ImGui.TextWrapped(notification.Message);
@@ -363,15 +422,6 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
ImGui.PopTextWrapPos(); ImGui.PopTextWrapPos();
} }
if (notification.Actions.Count > 0)
{
var spacingHeight = ImGui.GetStyle().ItemSpacing.Y;
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + spacingHeight);
ImGui.SetCursorPosX(contentPos.X);
DrawNotificationActions(notification, contentSize.X, alpha);
}
}
private void DrawNotificationActions(LightlessNotification notification, float availableWidth, float alpha) private void DrawNotificationActions(LightlessNotification notification, float availableWidth, float alpha)
{ {
var buttonSpacing = 8f; var buttonSpacing = 8f;
@@ -495,45 +545,44 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
private float CalculateNotificationHeight(LightlessNotification notification) private float CalculateNotificationHeight(LightlessNotification notification)
{ {
var contentWidth = _configService.Current.NotificationWidth - 35f; // Account for padding and accent bar var contentWidth = _configService.Current.NotificationWidth - 35f;
var height = 12f; // Base height for padding (top + bottom) var height = 12f;
var titleText = notification.Title; height += CalculateTitleHeight(notification, contentWidth);
if (_configService.Current.ShowNotificationTimestamp) height += CalculateMessageHeight(notification, contentWidth);
{
var timestamp = notification.CreatedAt.ToLocalTime().ToString("HH:mm:ss");
titleText = $"[{timestamp}] {titleText}";
}
var titleSize = ImGui.CalcTextSize(titleText, true, contentWidth);
height += titleSize.Y; // Title height
// Calculate message height
if (!string.IsNullOrEmpty(notification.Message))
{
height += 4f; // Spacing between title and message
var messageSize = ImGui.CalcTextSize(notification.Message, true, contentWidth); // This is cringe
height += messageSize.Y; // Message height
}
// Add height for progress bar
if (notification.ShowProgress) if (notification.ShowProgress)
{ {
height += 12f; height += 12f;
} }
// Add height for action buttons
if (notification.Actions.Count > 0) if (notification.Actions.Count > 0)
{ {
height += ImGui.GetStyle().ItemSpacing.Y; // Spacing before buttons height += ImGui.GetStyle().ItemSpacing.Y;
height += ImGui.GetFrameHeight(); // Button height height += ImGui.GetFrameHeight();
height += 12f; // Bottom padding for buttons height += 12f;
} }
// Allow notifications to grow taller but cap at maximum height
return Math.Clamp(height, NotificationMinHeight, NotificationMaxHeight); 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) private Vector4 GetNotificationAccentColor(NotificationType type)
{ {
return type switch return type switch

View File

@@ -15,12 +15,9 @@ public class LightlessNotification
public List<LightlessNotificationAction> Actions { get; set; } = new(); public List<LightlessNotificationAction> Actions { get; set; } = new();
public bool ShowProgress { get; set; } = false; public bool ShowProgress { get; set; } = false;
public float Progress { get; set; } = 0f; public float Progress { get; set; } = 0f;
public bool IsMinimized { get; set; } = false;
public float AnimationProgress { get; set; } = 0f; public float AnimationProgress { get; set; } = 0f;
public bool IsAnimatingIn { get; set; } = true; public bool IsAnimatingIn { get; set; } = true;
public bool IsAnimatingOut { get; set; } = false; public bool IsAnimatingOut { get; set; } = false;
// Sound properties
public uint? SoundEffectId { get; set; } = null; public uint? SoundEffectId { get; set; } = null;
} }
public class LightlessNotificationAction public class LightlessNotificationAction