diff --git a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs index 694c325..f2b9f12 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs @@ -72,9 +72,7 @@ public class LightlessConfig : ILightlessConfiguration public NotificationLocation WarningNotification { get; set; } = NotificationLocation.Both; // Lightless Notification Configuration - // TODO: clean these public bool UseLightlessNotifications { get; set; } = true; - public int DefaultNotificationDurationSeconds { get; set; } = 10; public bool ShowNotificationProgress { get; set; } = true; public NotificationLocation LightlessInfoNotification { get; set; } = NotificationLocation.LightlessUi; public NotificationLocation LightlessWarningNotification { get; set; } = NotificationLocation.LightlessUi; @@ -82,23 +80,45 @@ public class LightlessConfig : ILightlessConfiguration public NotificationLocation LightlessPairRequestNotification { get; set; } = NotificationLocation.LightlessUi; public NotificationLocation LightlessDownloadNotification { get; set; } = NotificationLocation.TextOverlay; + // Basic Settings public float NotificationOpacity { get; set; } = 0.95f; - public bool EnableNotificationAnimations { get; set; } = true; public int MaxSimultaneousNotifications { get; set; } = 5; public bool AutoDismissOnAction { get; set; } = true; public bool DismissNotificationOnClick { get; set; } = false; public bool ShowNotificationTimestamp { get; set; } = false; - public int NotificationOffsetY { get; set; } = 50; + // Position & Layout + public int NotificationOffsetY { get; set; } = 50; + public int NotificationOffsetX { get; set; } = 0; + public float NotificationWidth { get; set; } = 350f; + public float NotificationSpacing { get; set; } = 8f; + public bool NotificationStackUpwards { get; set; } = false; + + // Animation & Effects + public float NotificationAnimationSpeed { get; set; } = 10f; + public float NotificationAccentBarWidth { get; set; } = 3f; + + // Typography + public float NotificationFontScale { get; set; } = 1.0f; + + // Duration per Type + public int InfoNotificationDurationSeconds { get; set; } = 10; + public int WarningNotificationDurationSeconds { get; set; } = 15; + public int ErrorNotificationDurationSeconds { get; set; } = 20; + public int PairRequestDurationSeconds { get; set; } = 180; + public int DownloadNotificationDurationSeconds { get; set; } = 300; + + // Sound Settings public uint CustomInfoSoundId { get; set; } = 2; // Se2 - public uint CustomWarningSoundId { get; set; } = 15; // Se15 - public uint CustomErrorSoundId { get; set; } = 16; // Se16 + 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 bool DisableInfoSound { get; set; } = false; public bool DisableWarningSound { get; set; } = false; public bool DisableErrorSound { get; set; } = false; public bool DisablePairRequestSound { get; set; } = false; - // till here c: + public bool DisableDownloadSound { get; set; } = true; // Disabled by default 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 91f2cbc..1279089 100644 --- a/LightlessSync/Services/NotificationService.cs +++ b/LightlessSync/Services/NotificationService.cs @@ -51,7 +51,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ Title = title, Message = message, Type = type, - Duration = duration ?? TimeSpan.FromSeconds(_configService.Current.DefaultNotificationDurationSeconds), + Duration = duration ?? GetDefaultDurationForType(type), Actions = actions ?? new List(), SoundEffectId = GetSoundEffectId(type, soundEffectId), ShowProgress = _configService.Current.ShowNotificationProgress, @@ -87,9 +87,9 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ var notification = new LightlessNotification { Title = "Pair Request Received", - Message = $"{senderName} wants to pair with you.", + Message = $"{senderName} wants to directly pair with you.", Type = NotificationType.PairRequest, - Duration = TimeSpan.FromSeconds(180), + Duration = TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds), SoundEffectId = !_configService.Current.DisablePairRequestSound ? _configService.Current.PairRequestSoundId : null, Actions = new List { @@ -268,7 +268,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ Title = "Downloading Pair Data", Message = message, Type = NotificationType.Download, - Duration = TimeSpan.FromMinutes(5), + Duration = TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds), ShowProgress = true, Progress = totalProgress }; @@ -284,6 +284,19 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress")); } + private TimeSpan GetDefaultDurationForType(NotificationType type) + { + return type switch + { + NotificationType.Info => TimeSpan.FromSeconds(_configService.Current.InfoNotificationDurationSeconds), + NotificationType.Warning => TimeSpan.FromSeconds(_configService.Current.WarningNotificationDurationSeconds), + NotificationType.Error => TimeSpan.FromSeconds(_configService.Current.ErrorNotificationDurationSeconds), + NotificationType.PairRequest => TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds), + NotificationType.Download => TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds), + _ => TimeSpan.FromSeconds(10) // Fallback for any unknown types + }; + } + private uint? GetSoundEffectId(NotificationType type, uint? overrideSoundId) { if (overrideSoundId.HasValue) @@ -295,6 +308,7 @@ 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, _ => false }; @@ -307,6 +321,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, _ => NotificationSounds.GetDefaultSound(type) }; } @@ -387,7 +402,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ private void ShowLightlessNotification(NotificationMessage msg) { - var duration = msg.TimeShownOnScreen ?? TimeSpan.FromSeconds(_configService.Current.DefaultNotificationDurationSeconds); + 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); } diff --git a/LightlessSync/UI/LightlessNotificationUI.cs b/LightlessSync/UI/LightlessNotificationUI.cs index 3f714a0..34fe884 100644 --- a/LightlessSync/UI/LightlessNotificationUI.cs +++ b/LightlessSync/UI/LightlessNotificationUI.cs @@ -21,15 +21,8 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase private readonly object _notificationLock = new(); private readonly LightlessConfigService _configService; - private const float NotificationWidth = 350f; private const float NotificationMinHeight = 60f; - private const float NotificationMaxHeight = 200f; - private const float NotificationSpacing = 8f; - private const float AnimationSpeed = 10f; - - private const float EdgeXMargin = 0; - private const float EdgeYMargin = 30f; - private const float SlideDistance = 100f; + private const float NotificationMaxHeight = 250f; public LightlessNotificationUI(ILogger logger, LightlessMediator mediator, PerformanceCollectorService performanceCollector, LightlessConfigService configService) : base(logger, mediator, "Lightless Notifications##LightlessNotifications", performanceCollector) @@ -47,7 +40,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase PositionCondition = ImGuiCond.Always; - Size = new Vector2(NotificationWidth, 100); + Size = new Vector2(_configService.Current.NotificationWidth, 100); SizeCondition = ImGuiCond.FirstUseEver; IsOpen = false; RespectCloseHotkey = false; @@ -124,7 +117,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase var viewport = ImGui.GetMainViewport(); // Always position at top (choco doesnt know how to handle top positions how fitting) - var baseX = viewport.WorkPos.X + viewport.WorkSize.X - NotificationWidth; + var baseX = viewport.WorkPos.X + viewport.WorkSize.X - _configService.Current.NotificationWidth - _configService.Current.NotificationOffsetX; var baseY = viewport.WorkPos.Y; // Apply Y offset @@ -139,7 +132,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase if (i < _notifications.Count - 1) { - ImGui.Dummy(new Vector2(0, NotificationSpacing)); + ImGui.Dummy(new Vector2(0, _configService.Current.NotificationSpacing)); } } } @@ -170,11 +163,11 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase if (notification.IsAnimatingIn && notification.AnimationProgress < 1f) { - notification.AnimationProgress = Math.Min(1f, notification.AnimationProgress + deltaTime * AnimationSpeed); + 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 * (AnimationSpeed * 0.7f)); + notification.AnimationProgress = Math.Max(0f, notification.AnimationProgress - deltaTime * (_configService.Current.NotificationAnimationSpeed * 0.7f)); } else if (!notification.IsAnimatingOut && !notification.IsDismissed) { @@ -198,14 +191,10 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase { var alpha = notification.AnimationProgress; - if (_configService.Current.EnableNotificationAnimations && alpha <= 0f) + if (alpha <= 0f) return; - var slideOffset = 0f; - if (_configService.Current.EnableNotificationAnimations) - { - slideOffset = (1f - alpha) * SlideDistance; - } + var slideOffset = (1f - alpha) * 100f; // Fixed slide distance var originalCursorPos = ImGui.GetCursorPos(); ImGui.SetCursorPosX(originalCursorPos.X + slideOffset); @@ -215,7 +204,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); using var child = ImRaii.Child($"notification_{notification.Id}", - new Vector2(NotificationWidth - slideOffset, notificationHeight), + new Vector2(_configService.Current.NotificationWidth - slideOffset, notificationHeight), false, ImGuiWindowFlags.NoScrollbar); if (child.Success) @@ -233,16 +222,16 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase var windowSize = ImGui.GetWindowSize(); var baseOpacity = _configService.Current.NotificationOpacity; - var finalOpacity = _configService.Current.EnableNotificationAnimations ? baseOpacity * alpha : baseOpacity; + var finalOpacity = baseOpacity * alpha; var bgColor = new Vector4(30f/255f, 30f/255f, 30f/255f, finalOpacity); var accentColor = GetNotificationAccentColor(notification.Type); var progressBarColor = UIColors.Get("LightlessBlue"); - var finalAccentAlpha = _configService.Current.EnableNotificationAnimations ? alpha : 1f; - accentColor.W *= finalAccentAlpha; + accentColor.W *= alpha; + // Draw shadow with fixed intensity var shadowOffset = new Vector2(1f, 1f); - var shadowAlpha = _configService.Current.EnableNotificationAnimations ? 0.4f * alpha : 0.4f; + var shadowAlpha = 0.4f * alpha; var shadowColor = new Vector4(0f, 0f, 0f, shadowAlpha); drawList.AddRectFilled( windowPos + shadowOffset, @@ -252,6 +241,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase ); var isHovered = ImGui.IsWindowHovered(); + if (isHovered) { bgColor = bgColor * 1.1f; @@ -274,14 +264,17 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase 3f ); - // Draw accent bar on left side of the notif - var accentWidth = 3f; - drawList.AddRectFilled( - windowPos, - windowPos + new Vector2(accentWidth, windowSize.Y), - ImGui.ColorConvertFloat4ToU32(accentColor), - 3f - ); + // Draw accent bar on left side of the notif (only if width > 0) + var accentWidth = _configService.Current.NotificationAccentBarWidth; + if (accentWidth > 0f) + { + drawList.AddRectFilled( + windowPos, + windowPos + new Vector2(accentWidth, windowSize.Y), + ImGui.ColorConvertFloat4ToU32(accentColor), + 3f + ); + } DrawDurationProgressBar(notification, alpha, windowPos, windowSize, progressBarColor, drawList); @@ -290,8 +283,17 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase private void DrawDurationProgressBar(LightlessNotification notification, float alpha, Vector2 windowPos, Vector2 windowSize, Vector4 progressBarColor, ImDrawListPtr drawList) { - var elapsed = DateTime.UtcNow - notification.CreatedAt; - var progress = Math.Min(1.0f, (float)(elapsed.TotalSeconds / notification.Duration.TotalSeconds)); + // For download notifications, use download progress instead of duration + float progress; + 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 progressY = windowPos.Y + windowSize.Y - progressHeight; @@ -361,21 +363,10 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase ImGui.PopTextWrapPos(); } - if (notification.ShowProgress) - { - ImGui.Spacing(); - var progressColor = GetNotificationAccentColor(notification.Type); - progressColor.W *= alpha; - using (ImRaii.PushColor(ImGuiCol.PlotHistogram, progressColor)) - { - // Use full window width for progress bar - ImGui.ProgressBar(notification.Progress, new Vector2(windowSize.X - padding.X * 2 - 6f, 2f), ""); - } - } - if (notification.Actions.Count > 0) { - ImGui.Spacing(); + var spacingHeight = ImGui.GetStyle().ItemSpacing.Y; + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + spacingHeight); ImGui.SetCursorPosX(contentPos.X); DrawNotificationActions(notification, contentSize.X, alpha); } @@ -504,8 +495,8 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase private float CalculateNotificationHeight(LightlessNotification notification) { - var contentWidth = NotificationWidth - 35f; // Account for padding and accent bar - var height = 20f; // Base height for padding + var contentWidth = _configService.Current.NotificationWidth - 35f; // Account for padding and accent bar + var height = 12f; // Base height for padding (top + bottom) var titleText = notification.Title; if (_configService.Current.ShowNotificationTimestamp) @@ -515,13 +506,14 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase } var titleSize = ImGui.CalcTextSize(titleText, true, contentWidth); - height += titleSize.Y + 4f; // Title height + spacing + height += titleSize.Y; // Title height // Calculate message height if (!string.IsNullOrEmpty(notification.Message)) { - var messageSize = ImGui.CalcTextSize(notification.Message, true, contentWidth); - height += messageSize.Y + 4f; // Message height + spacing + 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 @@ -533,7 +525,9 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase // Add height for action buttons if (notification.Actions.Count > 0) { - height += 28f; + height += ImGui.GetStyle().ItemSpacing.Y; // Spacing before buttons + height += ImGui.GetFrameHeight(); // Button height + height += 12f; // Bottom padding for buttons } // Allow notifications to grow taller but cap at maximum height diff --git a/LightlessSync/UI/Models/LightlessNotification.cs b/LightlessSync/UI/Models/LightlessNotification.cs index 7013a97..4516dde 100644 --- a/LightlessSync/UI/Models/LightlessNotification.cs +++ b/LightlessSync/UI/Models/LightlessNotification.cs @@ -16,8 +16,6 @@ public class LightlessNotification public bool ShowProgress { get; set; } = false; public float Progress { get; set; } = 0f; public bool IsMinimized { get; set; } = false; - - // Animation properties public float AnimationProgress { get; set; } = 0f; public bool IsAnimatingIn { get; set; } = true; public bool IsAnimatingOut { get; set; } = false; diff --git a/LightlessSync/UI/Models/NotificationSounds.cs b/LightlessSync/UI/Models/NotificationSounds.cs index 8b60532..fc74256 100644 --- a/LightlessSync/UI/Models/NotificationSounds.cs +++ b/LightlessSync/UI/Models/NotificationSounds.cs @@ -1,4 +1,4 @@ -using LightlessSync.LightlessConfiguration.Models; +using LightlessSync.LightlessConfiguration.Models; namespace LightlessSync.UI.Models; @@ -16,18 +16,18 @@ public static class NotificationSounds public const uint Se2 = 2; // Higher chime public const uint Se3 = 3; // Bell tone public const uint Se4 = 4; // Harp tone - public const uint Se5 = 5; // Drum / percussion - public const uint Se6 = 6; // Mechanical click + public const uint Se5 = 5; // Mechanical click + public const uint Se6 = 6; // Drum / percussion public const uint Se7 = 7; // Metallic chime public const uint Se8 = 8; // Wooden tone public const uint Se9 = 9; // Wind / flute tone - public const uint Se10 = 10; // Magical sparkle - public const uint Se11 = 11; // Metallic ring - public const uint Se12 = 12; // Deep thud - public const uint Se13 = 13; // "Tell received" ping - public const uint Se14 = 14; // Success fanfare short - public const uint Se15 = 15; // System warning - public const uint Se16 = 16; // Error / failure────────────────────────────────────────── + public const uint Se10 = 11; // Magical sparkle (ID 10 is skipped in game) + public const uint Se11 = 12; // Metallic ring + public const uint Se12 = 13; // Deep thud + public const uint Se13 = 14; // "Tell received" ping + public const uint Se14 = 15; // Success fanfare + public const uint Se15 = 16; // System warning + // Note: Se16 doesn't exist - Se15 is the last available sound /// /// General notification sound () @@ -40,9 +40,9 @@ public static class NotificationSounds public const uint Warning = Se15; /// - /// Error sound () + /// Error sound ( - System warning, used for errors) /// - public const uint Error = Se16; + public const uint Error = Se15; /// /// Success sound () diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 6184a92..38c4186 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -3140,24 +3140,20 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.Separator(); if (_uiShared.MediumTreeNode("Basic Settings", UIColors.Get("LightlessPurple"))) { - - int defaultDuration = _configService.Current.DefaultNotificationDurationSeconds; - if (ImGui.SliderInt("Default Duration (seconds)", ref defaultDuration, 3, 60)) + int maxNotifications = _configService.Current.MaxSimultaneousNotifications; + if (ImGui.SliderInt("Max Simultaneous Notifications", ref maxNotifications, 1, 10)) { - _configService.Current.DefaultNotificationDurationSeconds = defaultDuration; + _configService.Current.MaxSimultaneousNotifications = maxNotifications; _configService.Save(); } - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { - _configService.Current.DefaultNotificationDurationSeconds = 10; + _configService.Current.MaxSimultaneousNotifications = 5; _configService.Save(); } - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Right click to reset to default (10 seconds)."); - - _uiShared.DrawHelpText("How long notifications stay visible by default."); + ImGui.SetTooltip("Right click to reset to default (5)."); + _uiShared.DrawHelpText("Maximum number of notifications that can be shown at once."); bool showTimestamp = _configService.Current.ShowNotificationTimestamp; if (ImGui.Checkbox("Show Timestamps", ref showTimestamp)) @@ -3205,32 +3201,38 @@ public class SettingsUi : WindowMediatorSubscriberBase _uiShared.DrawHelpText("Transparency level of notification windows."); - bool enableAnimations = _configService.Current.EnableNotificationAnimations; - if (ImGui.Checkbox("Enable Animations", ref enableAnimations)) + ImGui.Spacing(); + ImGui.TextUnformatted("Size & Layout"); + + float notifWidth = _configService.Current.NotificationWidth; + if (ImGui.SliderFloat("Notification Width", ref notifWidth, 250f, 600f, "%.0f")) { - _configService.Current.EnableNotificationAnimations = enableAnimations; + _configService.Current.NotificationWidth = notifWidth; _configService.Save(); } - - _uiShared.DrawHelpText("Enable slide-in/out animations for notifications."); - - int maxNotifications = _configService.Current.MaxSimultaneousNotifications; - if (ImGui.SliderInt("Max Simultaneous Notifications", ref maxNotifications, 1, 10)) - { - _configService.Current.MaxSimultaneousNotifications = maxNotifications; - _configService.Save(); - } - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { - _configService.Current.MaxSimultaneousNotifications = 5; + _configService.Current.NotificationWidth = 350f; _configService.Save(); } - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Right click to reset to default (5)."); + ImGui.SetTooltip("Right click to reset to default (350)."); + _uiShared.DrawHelpText("Width of notification windows."); - _uiShared.DrawHelpText("Maximum number of notifications that can be shown at once."); + float notifSpacing = _configService.Current.NotificationSpacing; + if (ImGui.SliderFloat("Notification Spacing", ref notifSpacing, 0f, 30f, "%.0f")) + { + _configService.Current.NotificationSpacing = notifSpacing; + _configService.Save(); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.NotificationSpacing = 8f; + _configService.Save(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (8)."); + _uiShared.DrawHelpText("Gap between stacked notifications."); ImGui.Spacing(); ImGui.TextUnformatted("Position"); @@ -3241,23 +3243,151 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.NotificationOffsetY = offsetY; _configService.Save(); } - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { _configService.Current.NotificationOffsetY = 50; _configService.Save(); } - if (ImGui.IsItemHovered()) ImGui.SetTooltip("Right click to reset to default (50)."); + _uiShared.DrawHelpText("Move notifications down from the top-right corner."); - _uiShared.DrawHelpText("Move notifications down from the top-right corner. 0 aligns to the very top."); + int offsetX = _configService.Current.NotificationOffsetX; + if (ImGui.SliderInt("Horizontal Offset", ref offsetX, 0, 500)) + { + _configService.Current.NotificationOffsetX = offsetX; + _configService.Save(); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.NotificationOffsetX = 0; + _configService.Save(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (0)."); + _uiShared.DrawHelpText("Move notifications left from the right edge."); + + ImGui.Spacing(); + ImGui.TextUnformatted("Animation Settings"); + + float animSpeed = _configService.Current.NotificationAnimationSpeed; + if (ImGui.SliderFloat("Animation Speed", ref animSpeed, 1f, 30f, "%.1f")) + { + _configService.Current.NotificationAnimationSpeed = animSpeed; + _configService.Save(); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.NotificationAnimationSpeed = 10f; + _configService.Save(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (10)."); + _uiShared.DrawHelpText("How fast notifications slide in/out. Higher = faster."); + + ImGui.Spacing(); + ImGui.TextUnformatted("Visual Effects"); + + float accentWidth = _configService.Current.NotificationAccentBarWidth; + if (ImGui.SliderFloat("Accent Bar Width", ref accentWidth, 0f, 10f, "%.1f")) + { + _configService.Current.NotificationAccentBarWidth = accentWidth; + _configService.Save(); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.NotificationAccentBarWidth = 3f; + _configService.Save(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (3)."); + _uiShared.DrawHelpText("Width of the colored accent bar on the left side."); _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); ImGui.TreePop(); } } + ImGui.Separator(); + if (_uiShared.MediumTreeNode("Duration Settings", UIColors.Get("LightlessPurple"))) + { + UiSharedService.ColorTextWrapped("Configure how long each notification type stays visible.", ImGuiColors.DalamudGrey); + ImGuiHelpers.ScaledDummy(5); + + int infoDuration = _configService.Current.InfoNotificationDurationSeconds; + if (ImGui.SliderInt("Info Duration (seconds)", ref infoDuration, 3, 60)) + { + _configService.Current.InfoNotificationDurationSeconds = infoDuration; + _configService.Save(); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.InfoNotificationDurationSeconds = 10; + _configService.Save(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (10)."); + + int warningDuration = _configService.Current.WarningNotificationDurationSeconds; + if (ImGui.SliderInt("Warning Duration (seconds)", ref warningDuration, 3, 60)) + { + _configService.Current.WarningNotificationDurationSeconds = warningDuration; + _configService.Save(); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.WarningNotificationDurationSeconds = 15; + _configService.Save(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (15)."); + + int errorDuration = _configService.Current.ErrorNotificationDurationSeconds; + if (ImGui.SliderInt("Error Duration (seconds)", ref errorDuration, 3, 120)) + { + _configService.Current.ErrorNotificationDurationSeconds = errorDuration; + _configService.Save(); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.ErrorNotificationDurationSeconds = 20; + _configService.Save(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (20)."); + + int pairRequestDuration = _configService.Current.PairRequestDurationSeconds; + if (ImGui.SliderInt("Pair Request Duration (seconds)", ref pairRequestDuration, 30, 600)) + { + _configService.Current.PairRequestDurationSeconds = pairRequestDuration; + _configService.Save(); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.PairRequestDurationSeconds = 180; + _configService.Save(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (180)."); + + int downloadDuration = _configService.Current.DownloadNotificationDurationSeconds; + if (ImGui.SliderInt("Download Duration (seconds)", ref downloadDuration, 60, 600)) + { + _configService.Current.DownloadNotificationDurationSeconds = downloadDuration; + _configService.Save(); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configService.Current.DownloadNotificationDurationSeconds = 300; + _configService.Save(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Right click to reset to default (300)."); + + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.TreePop(); + } + ImGui.Separator(); if (_uiShared.MediumTreeNode("Sound Settings", UIColors.Get("LightlessPurple"))) { @@ -3399,10 +3529,10 @@ public class SettingsUi : WindowMediatorSubscriberBase var soundEffects = new[] { (1u, "Se1 - Soft chime"), (2u, "Se2 - Higher chime"), (3u, "Se3 - Bell tone"), (4u, "Se4 - Harp tone"), - (5u, "Se5 - Drum/percussion"), (6u, "Se6 - Mechanical click"), (7u, "Se7 - Metallic chime"), - (8u, "Se8 - Wooden tone"), (9u, "Se9 - Wind/flute tone"), (10u, "Se10 - Magical sparkle"), - (11u, "Se11 - Metallic ring"), (12u, "Se12 - Deep thud"), (13u, "Se13 - Tell received ping"), - (14u, "Se14 - Success fanfare"), (15u, "Se15 - System warning"), (16u, "Se16 - Error/failure") + (5u, "Se5 - Mechanical click"), (6u, "Se6 - Drum/percussion"), (7u, "Se7 - Metallic chime"), + (8u, "Se8 - Wooden tone"), (9u, "Se9 - Wind/flute tone"), (11u, "Se10 - Magical sparkle"), + (12u, "Se11 - Metallic ring"), (13u, "Se12 - Deep thud"), (14u, "Se13 - Tell received ping"), + (15u, "Se14 - Success fanfare"), (16u, "Se15 - System warning") }; if (ImGui.BeginTable("##SoundTable", 3, @@ -3410,15 +3540,16 @@ public class SettingsUi : WindowMediatorSubscriberBase { ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("Sound", ImGuiTableColumnFlags.WidthStretch, 280 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 80 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); ImGui.TableHeadersRow(); var soundTypes = new[] { ("Info", 0, _configService.Current.CustomInfoSoundId, _configService.Current.DisableInfoSound, 2u), - ("Warning", 1, _configService.Current.CustomWarningSoundId, _configService.Current.DisableWarningSound, 15u), + ("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) + ("Pair Request", 3, _configService.Current.PairRequestSoundId, _configService.Current.DisablePairRequestSound, 5u), + ("Download", 4, _configService.Current.DownloadSoundId, _configService.Current.DisableDownloadSound, 15u) }; foreach (var (typeName, typeIndex, currentSoundId, isDisabled, defaultSoundId) in soundTypes) @@ -3448,56 +3579,38 @@ 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; } _configService.Save(); } - - ImGui.SameLine(); - ImGui.PushID($"test_{typeIndex}"); - if (_uiShared.IconButton(FontAwesomeIcon.Play)) - { - try - { - FFXIVClientStructs.FFXIV.Client.UI.UIGlobals.PlayChatSoundEffect(currentSoundId); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to play test sound"); - } - } - - ImGui.PopID(); - UiSharedService.AttachToolTip("Test this sound"); } // Actions column ImGui.TableSetColumnIndex(2); var availableWidth = ImGui.GetContentRegionAvail().X; - var buttonWidth = (availableWidth - ImGui.GetStyle().ItemSpacing.X) / 2; + var buttonWidth = (availableWidth - ImGui.GetStyle().ItemSpacing.X * 2) / 3; - // Reset button - using var resetId = ImRaii.PushId($"Reset_{typeIndex}"); - bool isDefault = currentSoundId == defaultSoundId; - - using (ImRaii.Disabled(isDefault)) + // Play button + using var playId = ImRaii.PushId($"Play_{typeIndex}"); + using (ImRaii.Disabled(isDisabled)) { using (ImRaii.PushFont(UiBuilder.IconFont)) { - if (ImGui.Button(FontAwesomeIcon.Undo.ToIconString(), new Vector2(buttonWidth, 0))) + if (ImGui.Button(FontAwesomeIcon.Play.ToIconString(), new Vector2(buttonWidth, 0))) { - switch (typeIndex) + try { - case 0: _configService.Current.CustomInfoSoundId = defaultSoundId; break; - case 1: _configService.Current.CustomWarningSoundId = defaultSoundId; break; - case 2: _configService.Current.CustomErrorSoundId = defaultSoundId; break; - case 3: _configService.Current.PairRequestSoundId = defaultSoundId; break; + FFXIVClientStructs.FFXIV.Client.UI.UIGlobals.PlayChatSoundEffect(currentSoundId); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to play test sound"); } - _configService.Save(); } } } - UiSharedService.AttachToolTip(isDefault ? "Sound is already at default value" : "Reset to default sound"); + UiSharedService.AttachToolTip("Test this sound"); // Disable toggle button ImGui.SameLine(); @@ -3520,6 +3633,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; } _configService.Save(); } @@ -3527,6 +3641,31 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.PopStyleColor(3); } UiSharedService.AttachToolTip(isDisabled ? "Sound is disabled - click to enable" : "Sound is enabled - click to disable"); + + // Reset button + ImGui.SameLine(); + using var resetId = ImRaii.PushId($"Reset_{typeIndex}"); + bool isDefault = currentSoundId == defaultSoundId; + + using (ImRaii.Disabled(isDefault)) + { + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + if (ImGui.Button(FontAwesomeIcon.Undo.ToIconString(), new Vector2(buttonWidth, 0))) + { + switch (typeIndex) + { + case 0: _configService.Current.CustomInfoSoundId = defaultSoundId; break; + 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; + } + _configService.Save(); + } + } + } + UiSharedService.AttachToolTip(isDefault ? "Sound is already at default value" : "Reset to default sound"); } ImGui.EndTable(); diff --git a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs index 3227940..38df25a 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs @@ -122,38 +122,25 @@ public partial class ApiController // Fire and forget async operation _ = Task.Run(async () => { + var myCidHash = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256(); + try { - var myCidHash = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256(); - try - { - await TryPairWithContentId(request.HashedCid, myCidHash).ConfigureAwait(false); - _pairRequestService.RemoveRequest(request.HashedCid); - - Mediator.Publish(new NotificationMessage( - "Pair Request Accepted", - $"Sent a pair request back to {senderName}.", - NotificationType.Info, - TimeSpan.FromSeconds(3))); - } - catch (Exception ex) - { - Mediator.Publish(new NotificationMessage( - "Failed to Accept Pair Request", - ex.Message, - NotificationType.Error, - TimeSpan.FromSeconds(5))); - } + await TryPairWithContentId(request.HashedCid, myCidHash).ConfigureAwait(false); + _pairRequestService.RemoveRequest(request.HashedCid); + } + catch (Exception ex) + { + Mediator.Publish(new NotificationMessage( + "Failed to Accept Pair Request", + ex.Message, + NotificationType.Error, + TimeSpan.FromSeconds(5))); } }); }, onDecline: () => { _pairRequestService.RemoveRequest(request.HashedCid); - Mediator.Publish(new NotificationMessage( - "Pair Request Declined", - $"Declined {senderName}'s pair request.", - NotificationType.Info, - TimeSpan.FromSeconds(3))); }); return Task.CompletedTask;