From bcb524df52f9b519943ae95f86e2dc22779a8cfd Mon Sep 17 00:00:00 2001 From: choco Date: Mon, 13 Oct 2025 00:13:21 +0200 Subject: [PATCH 1/5] text auto flexing on dismiss removed --- LightlessSync/UI/LightlessNotificationUI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LightlessSync/UI/LightlessNotificationUI.cs b/LightlessSync/UI/LightlessNotificationUI.cs index 7ced889..cb62b51 100644 --- a/LightlessSync/UI/LightlessNotificationUI.cs +++ b/LightlessSync/UI/LightlessNotificationUI.cs @@ -300,7 +300,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase ImGui.SetCursorPos(originalCursorPos + slideOffset); var notificationHeight = CalculateNotificationHeight(notification); - var notificationWidth = _configService.Current.NotificationWidth - Math.Abs(slideOffset.X); + var notificationWidth = _configService.Current.NotificationWidth; ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); -- 2.49.1 From 3f2e4d6640a8919289c2a1bf792850e465b3860c Mon Sep 17 00:00:00 2001 From: choco Date: Tue, 14 Oct 2025 09:36:10 +0200 Subject: [PATCH 2/5] small service cleanup --- LightlessSync/UI/DownloadUi.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/LightlessSync/UI/DownloadUi.cs b/LightlessSync/UI/DownloadUi.cs index 93cc623..1b1ec16 100644 --- a/LightlessSync/UI/DownloadUi.cs +++ b/LightlessSync/UI/DownloadUi.cs @@ -66,7 +66,7 @@ public class DownloadUi : WindowMediatorSubscriberBase _currentDownloads.TryRemove(msg.DownloadId, out _); if (!_currentDownloads.Any()) { - _notificationService.DismissPairDownloadNotification(); + Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress")); } }); Mediator.Subscribe(this, (_) => IsOpen = false); @@ -117,7 +117,7 @@ public class DownloadUi : WindowMediatorSubscriberBase } catch { - // ignore errors thrown from UI + _logger.LogDebug("Error drawing upload progress"); } try @@ -129,7 +129,7 @@ public class DownloadUi : WindowMediatorSubscriberBase if (useNotifications) { - // Use notification system - only update when data actually changes + // Use notification system if (_currentDownloads.Any()) { UpdateDownloadNotificationIfChanged(limiterSnapshot); @@ -137,13 +137,14 @@ public class DownloadUi : WindowMediatorSubscriberBase } else if (!_notificationDismissed) { - _notificationService.DismissPairDownloadNotification(); + Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress")); _notificationDismissed = true; _lastDownloadStateHash = 0; } } else { + // Use text overlay if (limiterSnapshot.IsEnabled) { var queueColor = limiterSnapshot.Waiting > 0 ? ImGuiColors.DalamudYellow : ImGuiColors.DalamudGrey; @@ -185,7 +186,7 @@ public class DownloadUi : WindowMediatorSubscriberBase } catch { - // ignore errors thrown from UI + _logger.LogDebug("Error drawing download progress"); } } @@ -257,7 +258,7 @@ public class DownloadUi : WindowMediatorSubscriberBase } catch { - // ignore errors thrown on UI + _logger.LogDebug("Error drawing upload progress"); } } } -- 2.49.1 From f202818b55456ef2d0726dbd747a4febd8e79b6f Mon Sep 17 00:00:00 2001 From: choco Date: Tue, 14 Oct 2025 11:46:14 +0200 Subject: [PATCH 3/5] performance notifcation addition, with some regular bugfixes regarding the flexing of the notifications --- .../Configurations/LightlessConfig.cs | 7 +- .../Models/NotificationLocation.cs | 3 +- LightlessSync/Services/Mediator/Messages.cs | 2 + LightlessSync/Services/NotificationService.cs | 221 ++++++++++++++++-- .../Services/PlayerPerformanceService.cs | 34 ++- LightlessSync/UI/LightlessNotificationUI.cs | 113 ++++++--- LightlessSync/UI/SettingsUi.cs | 73 +++++- LightlessSync/UI/SyncshellFinderUI.cs | 2 +- LightlessSync/UI/UIColors.cs | 6 +- 9 files changed, 379 insertions(+), 82 deletions(-) diff --git a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs index bee94fd..9102d90 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs @@ -86,6 +86,7 @@ public class LightlessConfig : ILightlessConfiguration public NotificationLocation LightlessErrorNotification { get; set; } = NotificationLocation.ChatAndLightlessUi; public NotificationLocation LightlessPairRequestNotification { get; set; } = NotificationLocation.LightlessUi; public NotificationLocation LightlessDownloadNotification { get; set; } = NotificationLocation.TextOverlay; + public NotificationLocation LightlessPerformanceNotification { get; set; } = NotificationLocation.LightlessUi; // Basic Settings public float NotificationOpacity { get; set; } = 0.95f; @@ -112,16 +113,18 @@ public class LightlessConfig : ILightlessConfiguration public int ErrorNotificationDurationSeconds { get; set; } = 20; public int PairRequestDurationSeconds { get; set; } = 180; public int DownloadNotificationDurationSeconds { get; set; } = 300; + public int PerformanceNotificationDurationSeconds { get; set; } = 20; public uint CustomInfoSoundId { get; set; } = 2; // Se2 public uint CustomWarningSoundId { get; set; } = 16; // Se15 public uint CustomErrorSoundId { get; set; } = 16; // Se15 public uint PairRequestSoundId { get; set; } = 5; // Se5 - public uint DownloadSoundId { get; set; } = 15; // Se14 + public uint PerformanceSoundId { get; set; } = 16; // Se15 public bool DisableInfoSound { get; set; } = true; public bool DisableWarningSound { get; set; } = true; public bool DisableErrorSound { get; set; } = true; public bool DisablePairRequestSound { get; set; } = true; - public bool DisableDownloadSound { get; set; } = true; + public bool DisablePerformanceSound { get; set; } = true; + public bool ShowPerformanceNotificationActions { get; set; } = true; public bool UseFocusTarget { get; set; } = false; public bool overrideFriendColor { get; set; } = false; public bool overridePartyColor { get; set; } = false; diff --git a/LightlessSync/LightlessConfiguration/Models/NotificationLocation.cs b/LightlessSync/LightlessConfiguration/Models/NotificationLocation.cs index 73637ee..c0609c6 100644 --- a/LightlessSync/LightlessConfiguration/Models/NotificationLocation.cs +++ b/LightlessSync/LightlessConfiguration/Models/NotificationLocation.cs @@ -17,7 +17,8 @@ public enum NotificationType Warning, Error, PairRequest, - Download + Download, + Performance } public enum NotificationCorner diff --git a/LightlessSync/Services/Mediator/Messages.cs b/LightlessSync/Services/Mediator/Messages.cs index 3396027..8d45ed6 100644 --- a/LightlessSync/Services/Mediator/Messages.cs +++ b/LightlessSync/Services/Mediator/Messages.cs @@ -50,6 +50,8 @@ public record TransientResourceChangedMessage(IntPtr Address) : MessageBase; public record HaltScanMessage(string Source) : MessageBase; public record NotificationMessage (string Title, string Message, NotificationType Type, TimeSpan? TimeShownOnScreen = null) : MessageBase; +public record PerformanceNotificationMessage + (string Title, string Message, UserData UserData, bool IsPaused, string PlayerName) : MessageBase; public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage; public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage; public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage; diff --git a/LightlessSync/Services/NotificationService.cs b/LightlessSync/Services/NotificationService.cs index 3f3fdfb..c2f5ab6 100644 --- a/LightlessSync/Services/NotificationService.cs +++ b/LightlessSync/Services/NotificationService.cs @@ -10,9 +10,11 @@ using LightlessSync.UI.Models; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using FFXIVClientStructs.FFXIV.Client.UI; +using LightlessSync.API.Data; using NotificationType = LightlessSync.LightlessConfiguration.Models.NotificationType; namespace LightlessSync.Services; + public class NotificationService : DisposableMediatorSubscriberBase, IHostedService { private readonly ILogger _logger; @@ -44,6 +46,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ { Mediator.Subscribe(this, HandleNotificationMessage); Mediator.Subscribe(this, HandlePairRequestsUpdated); + Mediator.Subscribe(this, HandlePerformanceNotification); return Task.CompletedTask; } @@ -107,23 +110,35 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ public void ShowPairRequestNotification(string senderName, string senderId, Action onAccept, Action onDecline) { - var notification = new LightlessNotification + var location = GetNotificationLocation(NotificationType.PairRequest); + + // Show in chat if configured + if (location == NotificationLocation.Chat || location == NotificationLocation.ChatAndLightlessUi) { - Id = $"pair_request_{senderId}", - Title = "Pair Request Received", - Message = $"{senderName} wants to directly pair with you.", - Type = NotificationType.PairRequest, - Duration = TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds), - SoundEffectId = GetPairRequestSoundId(), - Actions = CreatePairRequestActions(onAccept, onDecline) - }; - - if (notification.SoundEffectId.HasValue) - { - PlayNotificationSound(notification.SoundEffectId.Value); + ShowChat(new NotificationMessage("Pair Request Received", $"{senderName} wants to directly pair with you.", NotificationType.PairRequest)); } + + // Show Lightless notification if configured + if (location == NotificationLocation.LightlessUi || location == NotificationLocation.ChatAndLightlessUi) + { + var notification = new LightlessNotification + { + Id = $"pair_request_{senderId}", + Title = "Pair Request Received", + Message = $"{senderName} wants to directly pair with you.", + Type = NotificationType.PairRequest, + Duration = TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds), + SoundEffectId = GetPairRequestSoundId(), + Actions = CreatePairRequestActions(onAccept, onDecline) + }; - Mediator.Publish(new LightlessNotificationMessage(notification)); + if (notification.SoundEffectId.HasValue) + { + PlayNotificationSound(notification.SoundEffectId.Value); + } + + Mediator.Publish(new LightlessNotificationMessage(notification)); + } } private uint? GetPairRequestSoundId() => @@ -356,6 +371,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ NotificationType.Error => TimeSpan.FromSeconds(_configService.Current.ErrorNotificationDurationSeconds), NotificationType.PairRequest => TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds), NotificationType.Download => TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds), + NotificationType.Performance => TimeSpan.FromSeconds(_configService.Current.PerformanceNotificationDurationSeconds), _ => TimeSpan.FromSeconds(10) }; @@ -371,7 +387,8 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ NotificationType.Info => _configService.Current.DisableInfoSound, NotificationType.Warning => _configService.Current.DisableWarningSound, NotificationType.Error => _configService.Current.DisableErrorSound, - NotificationType.Download => _configService.Current.DisableDownloadSound, + NotificationType.Performance => _configService.Current.DisablePerformanceSound, + NotificationType.Download => true, // Download sounds always disabled _ => false }; @@ -380,7 +397,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ NotificationType.Info => _configService.Current.CustomInfoSoundId, NotificationType.Warning => _configService.Current.CustomWarningSoundId, NotificationType.Error => _configService.Current.CustomErrorSoundId, - NotificationType.Download => _configService.Current.DownloadSoundId, + NotificationType.Performance => _configService.Current.PerformanceSoundId, _ => NotificationSounds.GetDefaultSound(type) }; @@ -418,6 +435,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ NotificationType.Error => _configService.Current.LightlessErrorNotification, NotificationType.PairRequest => _configService.Current.LightlessPairRequestNotification, NotificationType.Download => _configService.Current.LightlessDownloadNotification, + NotificationType.Performance => _configService.Current.LightlessPerformanceNotification, _ => NotificationLocation.LightlessUi }; @@ -505,6 +523,18 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ case NotificationType.Error: PrintErrorChat(msg.Message); break; + + case NotificationType.PairRequest: + PrintPairRequestChat(msg.Title, msg.Message); + break; + + case NotificationType.Performance: + PrintPerformanceChat(msg.Title, msg.Message); + break; + + // Download notifications don't support chat output, will be a giga spam otherwise + case NotificationType.Download: + break; } } @@ -528,6 +558,22 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ _chatGui.Print(se.BuiltString); } + private void PrintPairRequestChat(string? title, string? message) + { + SeStringBuilder se = new SeStringBuilder().AddText("[Lightless Sync] ") + .AddUiForeground("Pair Request: ", 541).AddUiForegroundOff() + .AddText(title ?? message ?? string.Empty); + _chatGui.Print(se.BuiltString); + } + + private void PrintPerformanceChat(string? title, string? message) + { + SeStringBuilder se = new SeStringBuilder().AddText("[Lightless Sync] ") + .AddUiForeground("Performance: ", 508).AddUiForegroundOff() + .AddText(title ?? message ?? string.Empty); + _chatGui.Print(se.BuiltString); + } + private void HandlePairRequestsUpdated(PairRequestsUpdatedMessage _) { var activeRequests = _pairRequestService.GetActiveRequests(); @@ -557,5 +603,144 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ ); } } -} - \ No newline at end of file + + private void HandlePerformanceNotification(PerformanceNotificationMessage msg) + { + var location = GetNotificationLocation(NotificationType.Performance); + + // Show in chat if configured + if (location == NotificationLocation.Chat || location == NotificationLocation.ChatAndLightlessUi) + { + ShowChat(new NotificationMessage(msg.Title, msg.Message, NotificationType.Performance)); + } + + // Show Lightless notification if configured and action buttons are enabled + if ((location == NotificationLocation.LightlessUi || location == NotificationLocation.ChatAndLightlessUi) + && _configService.Current.UseLightlessNotifications + && _configService.Current.ShowPerformanceNotificationActions) + { + var actions = CreatePerformanceActions(msg.UserData, msg.IsPaused, msg.PlayerName); + var notification = new LightlessNotification + { + Title = msg.Title, + Message = msg.Message, + Type = NotificationType.Performance, + Duration = TimeSpan.FromSeconds(_configService.Current.PerformanceNotificationDurationSeconds), + Actions = actions, + SoundEffectId = GetSoundEffectId(NotificationType.Performance, null) + }; + + if (notification.SoundEffectId.HasValue) + { + PlayNotificationSound(notification.SoundEffectId.Value); + } + + Mediator.Publish(new LightlessNotificationMessage(notification)); + } + else if (location != NotificationLocation.Nowhere && location != NotificationLocation.Chat) + { + // Fall back to regular notification without action buttons + HandleNotificationMessage(new NotificationMessage(msg.Title, msg.Message, NotificationType.Performance)); + } + } + + private List CreatePerformanceActions(UserData userData, bool isPaused, string playerName) + { + var actions = new List(); + + if (isPaused) + { + actions.Add(new LightlessNotificationAction + { + Label = "Unpause", + Icon = FontAwesomeIcon.Play, + Color = UIColors.Get("LightlessGreen"), + IsPrimary = true, + OnClick = (notification) => + { + try + { + Mediator.Publish(new CyclePauseMessage(userData)); + DismissNotification(notification); + + var displayName = GetUserDisplayName(userData, playerName); + ShowNotification( + "Player Unpaused", + $"Successfully unpaused {displayName}", + NotificationType.Info, + TimeSpan.FromSeconds(3)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to unpause player {uid}", userData.UID); + var displayName = GetUserDisplayName(userData, playerName); + ShowNotification( + "Unpause Failed", + $"Failed to unpause {displayName}", + NotificationType.Error, + TimeSpan.FromSeconds(5)); + } + } + }); + } + else + { + actions.Add(new LightlessNotificationAction + { + Label = "Pause", + Icon = FontAwesomeIcon.Pause, + Color = UIColors.Get("LightlessOrange"), + IsPrimary = true, + OnClick = (notification) => + { + try + { + Mediator.Publish(new PauseMessage(userData)); + DismissNotification(notification); + + var displayName = GetUserDisplayName(userData, playerName); + ShowNotification( + "Player Paused", + $"Successfully paused {displayName}", + NotificationType.Info, + TimeSpan.FromSeconds(3)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to pause player {uid}", userData.UID); + var displayName = GetUserDisplayName(userData, playerName); + ShowNotification( + "Pause Failed", + $"Failed to pause {displayName}", + NotificationType.Error, + TimeSpan.FromSeconds(5)); + } + } + }); + } + + // Add dismiss button + actions.Add(new LightlessNotificationAction + { + Label = "Dismiss", + Icon = FontAwesomeIcon.Times, + Color = UIColors.Get("DimRed"), + IsPrimary = false, + OnClick = (notification) => + { + DismissNotification(notification); + } + }); + + return actions; + } + + private string GetUserDisplayName(UserData userData, string playerName) + { + if (!string.IsNullOrEmpty(userData.Alias) && !string.Equals(userData.Alias, userData.UID, StringComparison.Ordinal)) + { + return $"{playerName} ({userData.Alias})"; + } + return $"{playerName} ({userData.UID})"; + } +} \ No newline at end of file diff --git a/LightlessSync/Services/PlayerPerformanceService.cs b/LightlessSync/Services/PlayerPerformanceService.cs index ef43849..0cf7a72 100644 --- a/LightlessSync/Services/PlayerPerformanceService.cs +++ b/LightlessSync/Services/PlayerPerformanceService.cs @@ -93,8 +93,12 @@ public class PlayerPerformanceService $"triangle warning threshold ({triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles)."; } - _mediator.Publish(new NotificationMessage($"{pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds performance threshold(s)", - warningText, LightlessConfiguration.Models.NotificationType.Warning)); + _mediator.Publish(new PerformanceNotificationMessage( + $"{pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds performance threshold(s)", + warningText, + pairHandler.Pair.UserData, + pairHandler.Pair.IsPaused, + pairHandler.Pair.PlayerName)); } return true; @@ -138,11 +142,16 @@ public class PlayerPerformanceService if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.TrisAutoPauseThresholdThousands * 1000, triUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm)) { - _mediator.Publish(new NotificationMessage($"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused", - $"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured triangle auto pause threshold (" + + var message = $"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured triangle auto pause threshold (" + $"{triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)" + - $" and has been automatically paused.", - LightlessConfiguration.Models.NotificationType.Warning)); + $" and has been automatically paused."; + + _mediator.Publish(new PerformanceNotificationMessage( + $"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused", + message, + pair.UserData, + true, + pair.PlayerName)); _mediator.Publish(new EventMessage(new Event(pair.PlayerName, pair.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning, $"Exceeds triangle threshold: automatically paused ({triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)"))); @@ -214,11 +223,16 @@ public class PlayerPerformanceService if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.VRAMSizeAutoPauseThresholdMiB * 1024 * 1024, vramUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm)) { - _mediator.Publish(new NotificationMessage($"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused", - $"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured VRAM auto pause threshold (" + + var message = $"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured VRAM auto pause threshold (" + $"{UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB}MiB)" + - $" and has been automatically paused.", - LightlessConfiguration.Models.NotificationType.Warning)); + $" and has been automatically paused."; + + _mediator.Publish(new PerformanceNotificationMessage( + $"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused", + message, + pair.UserData, + true, + pair.PlayerName)); _mediator.Publish(new PauseMessage(pair.UserData)); diff --git a/LightlessSync/UI/LightlessNotificationUI.cs b/LightlessSync/UI/LightlessNotificationUI.cs index cb62b51..2ac26b7 100644 --- a/LightlessSync/UI/LightlessNotificationUI.cs +++ b/LightlessSync/UI/LightlessNotificationUI.cs @@ -22,6 +22,10 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase 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 _notifications = new(); private readonly object _notificationLock = new(); @@ -462,81 +466,112 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase private void DrawNotificationText(LightlessNotification notification, float alpha) { - var padding = new Vector2(10f, 6f); - var contentPos = new Vector2(padding.X, padding.Y); + var contentPos = new Vector2(ContentPaddingX, ContentPaddingY); var windowSize = ImGui.GetWindowSize(); - var contentSize = new Vector2(windowSize.X - padding.X, windowSize.Y - padding.Y * 2); + var contentWidth = CalculateContentWidth(windowSize.X); ImGui.SetCursorPos(contentPos); - var titleHeight = DrawTitle(notification, contentSize.X, alpha); - DrawMessage(notification, contentPos, contentSize.X, titleHeight, alpha); + var titleHeight = DrawTitle(notification, contentWidth, alpha); + DrawMessage(notification, contentPos, contentWidth, titleHeight, alpha); - if (notification.Actions.Count > 0) + if (HasActions(notification)) { - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetStyle().ItemSpacing.Y); - ImGui.SetCursorPosX(contentPos.X); - DrawNotificationActions(notification, contentSize.X, alpha); + 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) { - using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 1f, 1f, alpha))) + var titleColor = new Vector4(1f, 1f, 1f, alpha); + var titleText = FormatTitleText(notification); + + using (ImRaii.PushColor(ImGuiCol.Text, titleColor)) { - ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentWidth); - var titleStartY = ImGui.GetCursorPosY(); - - var titleText = _configService.Current.ShowNotificationTimestamp - ? $"[{notification.CreatedAt.ToLocalTime():HH:mm:ss}] {notification.Title}" - : notification.Title; - - ImGui.TextWrapped(titleText); - var titleHeight = ImGui.GetCursorPosY() - titleStartY; - ImGui.PopTextWrapPos(); - return titleHeight; + 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; - ImGui.SetCursorPos(contentPos + new Vector2(0f, titleHeight + 4f)); - ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentWidth); - using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(0.9f, 0.9f, 0.9f, alpha))) + 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)) { - ImGui.TextWrapped(notification.Message); + DrawWrappedText(notification.Message, contentWidth); } - ImGui.PopTextWrapPos(); } private void DrawNotificationActions(LightlessNotification notification, float availableWidth, float alpha) { - var buttonSpacing = 8f; - var rightPadding = 10f; - var usableWidth = availableWidth - rightPadding; - var totalSpacing = (notification.Actions.Count - 1) * buttonSpacing; - var buttonWidth = (usableWidth - totalSpacing) / notification.Actions.Count; + var buttonWidth = CalculateActionButtonWidth(notification.Actions.Count, availableWidth); _logger.LogDebug("Drawing {ActionCount} notification actions, buttonWidth: {ButtonWidth}, availableWidth: {AvailableWidth}", notification.Actions.Count, buttonWidth, availableWidth); - var startCursorPos = ImGui.GetCursorPos(); + var startX = ImGui.GetCursorPosX(); for (int i = 0; i < notification.Actions.Count; i++) { - var action = notification.Actions[i]; - if (i > 0) { ImGui.SameLine(); - var currentX = startCursorPos.X + i * (buttonWidth + buttonSpacing); - ImGui.SetCursorPosX(currentX); + PositionActionButton(i, startX, buttonWidth); } - DrawActionButton(action, notification, alpha, 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) { @@ -634,7 +669,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase private float CalculateNotificationHeight(LightlessNotification notification) { - var contentWidth = _configService.Current.NotificationWidth - 35f; + var contentWidth = CalculateContentWidth(_configService.Current.NotificationWidth); var height = 12f; height += CalculateTitleHeight(notification, contentWidth); @@ -681,6 +716,8 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase NotificationType.Error => UIColors.Get("DimRed"), NotificationType.PairRequest => UIColors.Get("LightlessBlue"), NotificationType.Download => UIColors.Get("LightlessGreen"), + NotificationType.Performance => UIColors.Get("LightlessOrange"), + _ => UIColors.Get("LightlessPurple") }; } diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 87df967..1272742 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -1990,7 +1990,7 @@ public class SettingsUi : WindowMediatorSubscriberBase ("LightlessBlue", "Secondary Blue", "Secondary title colors, visable pairs"), ("LightlessGreen", "Success Green", "Join buttons and success messages"), ("LightlessYellow", "Warning Yellow", "Warning colors"), - ("LightlessYellow2", "Warning Yellow (Alt)", "Warning colors"), + ("LightlessOrange", "Performance Orange", "Performance notifications and warnings"), ("PairBlue", "Syncshell Blue", "Syncshell headers, toggle highlights, and moderator actions"), ("DimRed", "Error Red", "Error and offline colors") }; @@ -3640,6 +3640,36 @@ public class SettingsUi : WindowMediatorSubscriberBase } UiSharedService.AttachToolTip("Test download progress notification"); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Performance Notifications"); + ImGui.TableSetColumnIndex(1); + ImGui.SetNextItemWidth(-1); + _uiShared.DrawCombo("###enhanced_performance", lightlessLocations, GetNotificationLocationLabel, + (location) => + { + _configService.Current.LightlessPerformanceNotification = location; + _configService.Save(); + }, _configService.Current.LightlessPerformanceNotification); + ImGui.TableSetColumnIndex(2); + availableWidth = ImGui.GetContentRegionAvail().X; + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_performance", new Vector2(availableWidth, 0))) + { + var testUserData = new UserData("TEST123", "TestUser", false, false, false, null, null); + Mediator.Publish(new PerformanceNotificationMessage( + "Test Player (TestUser) exceeds performance threshold(s)", + "Player Test Player (TestUser) exceeds your configured VRAM warning threshold (500 MB/300 MB).", + testUserData, + false, + "Test Player" + )); + } + } + UiSharedService.AttachToolTip("Test performance notification"); + ImGui.EndTable(); } @@ -3974,6 +4004,20 @@ public class SettingsUi : WindowMediatorSubscriberBase if (ImGui.IsItemHovered()) ImGui.SetTooltip("Right click to reset to default (300)."); + int performanceDuration = _configService.Current.PerformanceNotificationDurationSeconds; + if (ImGui.SliderInt("Performance Duration (seconds)", ref performanceDuration, 5, 60)) + { + _configService.Current.PerformanceNotificationDurationSeconds = Math.Clamp(performanceDuration, 5, 60); + _configService.Save(); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.PerformanceNotificationDurationSeconds = 20; + _configService.Save(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (20)."); + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); ImGui.TreePop(); } @@ -4041,6 +4085,22 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.TreePop(); } + if (_uiShared.MediumTreeNode("Performance Notifications", UIColors.Get("LightlessOrange"))) + { + var showPerformanceActions = _configService.Current.ShowPerformanceNotificationActions; + if (ImGui.Checkbox("Show action buttons on performance warnings", ref showPerformanceActions)) + { + _configService.Current.ShowPerformanceNotificationActions = showPerformanceActions; + _configService.Save(); + } + + _uiShared.DrawHelpText( + "When a player exceeds performance thresholds or is auto-paused, show Pause/Unpause buttons in the notification."); + + _uiShared.ColoredSeparator(UIColors.Get("LightlessOrange"), 1.5f); + ImGui.TreePop(); + } + if (_uiShared.MediumTreeNode("System Notifications", UIColors.Get("LightlessYellow"))) { var disableOptionalPluginWarnings = _configService.Current.DisableOptionalPluginWarnings; @@ -4074,8 +4134,7 @@ public class SettingsUi : WindowMediatorSubscriberBase { return new[] { - NotificationLocation.LightlessUi, NotificationLocation.ChatAndLightlessUi, - NotificationLocation.TextOverlay, NotificationLocation.Nowhere + NotificationLocation.LightlessUi, NotificationLocation.TextOverlay, NotificationLocation.Nowhere }; } @@ -4138,7 +4197,7 @@ public class SettingsUi : WindowMediatorSubscriberBase ("Warning", 1, _configService.Current.CustomWarningSoundId, _configService.Current.DisableWarningSound, 16u), ("Error", 2, _configService.Current.CustomErrorSoundId, _configService.Current.DisableErrorSound, 16u), ("Pair Request", 3, _configService.Current.PairRequestSoundId, _configService.Current.DisablePairRequestSound, 5u), - ("Download", 4, _configService.Current.DownloadSoundId, _configService.Current.DisableDownloadSound, 15u) + ("Performance", 4, _configService.Current.PerformanceSoundId, _configService.Current.DisablePerformanceSound, 16u) }; foreach (var (typeName, typeIndex, currentSoundId, isDisabled, defaultSoundId) in soundTypes) @@ -4168,7 +4227,7 @@ public class SettingsUi : WindowMediatorSubscriberBase case 1: _configService.Current.CustomWarningSoundId = newSoundId; break; case 2: _configService.Current.CustomErrorSoundId = newSoundId; break; case 3: _configService.Current.PairRequestSoundId = newSoundId; break; - case 4: _configService.Current.DownloadSoundId = newSoundId; break; + case 4: _configService.Current.PerformanceSoundId = newSoundId; break; } _configService.Save(); @@ -4222,7 +4281,7 @@ public class SettingsUi : WindowMediatorSubscriberBase case 1: _configService.Current.DisableWarningSound = newDisabled; break; case 2: _configService.Current.DisableErrorSound = newDisabled; break; case 3: _configService.Current.DisablePairRequestSound = newDisabled; break; - case 4: _configService.Current.DisableDownloadSound = newDisabled; break; + case 4: _configService.Current.DisablePerformanceSound = newDisabled; break; } _configService.Save(); } @@ -4248,7 +4307,7 @@ public class SettingsUi : WindowMediatorSubscriberBase case 1: _configService.Current.CustomWarningSoundId = defaultSoundId; break; case 2: _configService.Current.CustomErrorSoundId = defaultSoundId; break; case 3: _configService.Current.PairRequestSoundId = defaultSoundId; break; - case 4: _configService.Current.DownloadSoundId = defaultSoundId; break; + case 4: _configService.Current.PerformanceSoundId = defaultSoundId; break; } _configService.Save(); } diff --git a/LightlessSync/UI/SyncshellFinderUI.cs b/LightlessSync/UI/SyncshellFinderUI.cs index 971d40c..6cd6935 100644 --- a/LightlessSync/UI/SyncshellFinderUI.cs +++ b/LightlessSync/UI/SyncshellFinderUI.cs @@ -88,7 +88,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase ImGuiHelpers.ScaledDummy(0.5f); ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 10.0f); - ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("LightlessYellow2")); + ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("PairBlue")); if (ImGui.Button("Open Lightfinder", new Vector2(200 * ImGuiHelpers.GlobalScale, 0))) { diff --git a/LightlessSync/UI/UIColors.cs b/LightlessSync/UI/UIColors.cs index 993573d..3c1eabd 100644 --- a/LightlessSync/UI/UIColors.cs +++ b/LightlessSync/UI/UIColors.cs @@ -11,21 +11,17 @@ namespace LightlessSync.UI { "LightlessPurple", "#ad8af5" }, { "LightlessPurpleActive", "#be9eff" }, { "LightlessPurpleDefault", "#9375d1" }, - { "ButtonDefault", "#323232" }, { "FullBlack", "#000000" }, - { "LightlessBlue", "#a6c2ff" }, { "LightlessYellow", "#ffe97a" }, - { "LightlessYellow2", "#cfbd63" }, { "LightlessGreen", "#7cd68a" }, + { "LightlessOrange", "#ffb366" }, { "PairBlue", "#88a2db" }, { "DimRed", "#d44444" }, - { "LightlessAdminText", "#ffd663" }, { "LightlessAdminGlow", "#b09343" }, { "LightlessModeratorText", "#94ffda" }, - { "LightlessModeratorGlow", "#599c84" }, { "Lightfinder", "#ad8af5" }, { "LightfinderEdge", "#000000" }, -- 2.49.1 From d6a4595bb8a0d72f61cdf4793dbb2f8438e1b9eb Mon Sep 17 00:00:00 2001 From: choco Date: Tue, 14 Oct 2025 14:54:30 +0200 Subject: [PATCH 4/5] old debug line removals, better alignment performance notifs --- .../Services/PlayerPerformanceService.cs | 23 +++--- LightlessSync/UI/SettingsUi.cs | 2 +- LightlessSync/UI/TopTabMenu.cs | 76 ------------------- 3 files changed, 11 insertions(+), 90 deletions(-) diff --git a/LightlessSync/Services/PlayerPerformanceService.cs b/LightlessSync/Services/PlayerPerformanceService.cs index 0cf7a72..7db92e1 100644 --- a/LightlessSync/Services/PlayerPerformanceService.cs +++ b/LightlessSync/Services/PlayerPerformanceService.cs @@ -78,19 +78,18 @@ public class PlayerPerformanceService string warningText = string.Empty; if (exceedsTris && !exceedsVram) { - warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds your configured triangle warning threshold (" + - $"{triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles)."; + warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds your configured triangle warning threshold\n" + + $"{triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles"; } else if (!exceedsTris) { - warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds your configured VRAM warning threshold (" + - $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB)."; + warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds your configured VRAM warning threshold\n" + + $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB"; } else { - warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds both VRAM warning threshold (" + - $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB) and " + - $"triangle warning threshold ({triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles)."; + warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds both VRAM warning threshold and triangle warning threshold\n" + + $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB and {triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles"; } _mediator.Publish(new PerformanceNotificationMessage( @@ -142,9 +141,8 @@ public class PlayerPerformanceService if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.TrisAutoPauseThresholdThousands * 1000, triUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm)) { - var message = $"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured triangle auto pause threshold (" + - $"{triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)" + - $" and has been automatically paused."; + var message = $"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured triangle auto pause threshold and has been automatically paused\n" + + $"{triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles"; _mediator.Publish(new PerformanceNotificationMessage( $"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused", @@ -223,9 +221,8 @@ public class PlayerPerformanceService if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.VRAMSizeAutoPauseThresholdMiB * 1024 * 1024, vramUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm)) { - var message = $"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured VRAM auto pause threshold (" + - $"{UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB}MiB)" + - $" and has been automatically paused."; + var message = $"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured VRAM auto pause threshold and has been automatically paused\n" + + $"{UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB}MiB"; _mediator.Publish(new PerformanceNotificationMessage( $"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused", diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 1272742..d5520e0 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -3661,7 +3661,7 @@ public class SettingsUi : WindowMediatorSubscriberBase var testUserData = new UserData("TEST123", "TestUser", false, false, false, null, null); Mediator.Publish(new PerformanceNotificationMessage( "Test Player (TestUser) exceeds performance threshold(s)", - "Player Test Player (TestUser) exceeds your configured VRAM warning threshold (500 MB/300 MB).", + "Player Test Player (TestUser) exceeds your configured VRAM warning threshold\n500 MB/300 MB", testUserData, false, "Test Player" diff --git a/LightlessSync/UI/TopTabMenu.cs b/LightlessSync/UI/TopTabMenu.cs index 2a6a236..b4327c0 100644 --- a/LightlessSync/UI/TopTabMenu.cs +++ b/LightlessSync/UI/TopTabMenu.cs @@ -196,82 +196,6 @@ public class TopTabMenu if (TabSelection != SelectedTab.None) ImGuiHelpers.ScaledDummy(3f); - #if DEBUG - if (ImGui.Button("Test Pair Request")) - { - _lightlessNotificationService.ShowPairRequestNotification( - "Debug User", - "debug-user-id", - onAccept: () => - { - _lightlessMediator.Publish(new NotificationMessage( - "Pair Accepted", - "Debug pair request was accepted!", - NotificationType.Info, - TimeSpan.FromSeconds(3))); - }, - onDecline: () => - { - _lightlessMediator.Publish(new NotificationMessage( - "Pair Declined", - "Debug pair request was declined.", - NotificationType.Warning, - TimeSpan.FromSeconds(3))); - } - ); - } - - ImGui.SameLine(); - if (ImGui.Button("Test Info")) - { - _lightlessMediator.Publish(new NotificationMessage( - "Information", - "This is a test ifno notification with some longer text to see how it wraps. This is a test ifno notification with some longer text to see how it wraps. This is a test ifno notification with some longer text to see how it wraps. This is a test ifno notification with some longer text to see how it wraps.", - NotificationType.Info, - TimeSpan.FromSeconds(5))); - } - - ImGui.SameLine(); - if (ImGui.Button("Test Warning")) - { - _lightlessMediator.Publish(new NotificationMessage( - "Warning", - "This is a test warning notification.", - NotificationType.Warning, - TimeSpan.FromSeconds(7))); - } - - ImGui.SameLine(); - if (ImGui.Button("Test Error")) - { - _lightlessMediator.Publish(new NotificationMessage( - "Error", - "This is a test error notification erp police", - NotificationType.Error, - TimeSpan.FromSeconds(10))); - } - - if (ImGui.Button("Test Download Progress")) - { - var downloadStatus = new List<(string playerName, float progress, string status)> - { - ("Mauwmauw Nekochan", 0.85f, "downloading"), - ("Raelynn Kitsune", 0.34f, "downloading"), - ("Jaina Elraeth", 0.67f, "downloading"), - ("Vaelstra Bloodthorn", 0.19f, "downloading"), - ("Lydia Hera Moondrop", 0.86f, "downloading"), - ("C'liina Star", 1.0f, "completed") - }; - - _lightlessNotificationService.ShowPairDownloadNotification(downloadStatus); - } - ImGui.SameLine(); - if (ImGui.Button("Dismiss Download")) - { - _lightlessNotificationService.DismissPairDownloadNotification(); - } - #endif - DrawIncomingPairRequests(availableWidth); ImGui.Separator(); -- 2.49.1 From cf27a6729657fdccb54f71b14cf6b67081191dca Mon Sep 17 00:00:00 2001 From: choco Date: Tue, 14 Oct 2025 15:07:46 +0200 Subject: [PATCH 5/5] optional action button toggle for pair request (default on) --- .../Configurations/LightlessConfig.cs | 1 + LightlessSync/Services/NotificationService.cs | 11 +++++++++-- LightlessSync/UI/SettingsUi.cs | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs index 9102d90..bdf8542 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs @@ -125,6 +125,7 @@ public class LightlessConfig : ILightlessConfiguration public bool DisablePairRequestSound { get; set; } = true; public bool DisablePerformanceSound { get; set; } = true; public bool ShowPerformanceNotificationActions { get; set; } = true; + public bool ShowPairRequestNotificationActions { get; set; } = true; public bool UseFocusTarget { get; set; } = false; public bool overrideFriendColor { get; set; } = false; public bool overridePartyColor { get; set; } = false; diff --git a/LightlessSync/Services/NotificationService.cs b/LightlessSync/Services/NotificationService.cs index c2f5ab6..755e756 100644 --- a/LightlessSync/Services/NotificationService.cs +++ b/LightlessSync/Services/NotificationService.cs @@ -118,8 +118,10 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ ShowChat(new NotificationMessage("Pair Request Received", $"{senderName} wants to directly pair with you.", NotificationType.PairRequest)); } - // Show Lightless notification if configured - if (location == NotificationLocation.LightlessUi || location == NotificationLocation.ChatAndLightlessUi) + // Show Lightless notification if configured and action buttons are enabled + if ((location == NotificationLocation.LightlessUi || location == NotificationLocation.ChatAndLightlessUi) + && _configService.Current.UseLightlessNotifications + && _configService.Current.ShowPairRequestNotificationActions) { var notification = new LightlessNotification { @@ -139,6 +141,11 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ Mediator.Publish(new LightlessNotificationMessage(notification)); } + else if (location != NotificationLocation.Nowhere && location != NotificationLocation.Chat) + { + // Fall back to regular notification without action buttons + HandleNotificationMessage(new NotificationMessage("Pair Request Received", $"{senderName} wants to directly pair with you.", NotificationType.PairRequest)); + } } private uint? GetPairRequestSoundId() => diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index d5520e0..dd7ee84 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -4085,6 +4085,22 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.TreePop(); } + if (_uiShared.MediumTreeNode("Pair Request Notifications", UIColors.Get("PairBlue"))) + { + var showPairRequestActions = _configService.Current.ShowPairRequestNotificationActions; + if (ImGui.Checkbox("Show action buttons on pair requests", ref showPairRequestActions)) + { + _configService.Current.ShowPairRequestNotificationActions = showPairRequestActions; + _configService.Save(); + } + + _uiShared.DrawHelpText( + "When you receive a pair request, show Accept/Decline buttons in the notification."); + + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } + if (_uiShared.MediumTreeNode("Performance Notifications", UIColors.Get("LightlessOrange"))) { var showPerformanceActions = _configService.Current.ShowPerformanceNotificationActions; -- 2.49.1